from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
from fastapi import HTTPException, status
from typing import Optional
from passlib.context import CryptContext
from . import repository
from .schema import (
    UserCreate,
    UserUpdate,
    NotificationCreate,
    NotificationUpdate,
)
from organization import repository as org_repo
from utils.utils import utc_now

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def hash_password(password: str) -> str:
    return pwd_context.hash(password)


def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)


def create_user(db: Session, data: UserCreate):
    # Check if email already exists
    existing_user = repository.get_by_email(db, data.email)
    if existing_user:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User with this email already exists.",
        )

    # Validate department exists if provided
    if data.department_id:
        department = org_repo.get_by_id(db, data.department_id)
        if not department:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Referenced department does not exist.",
            )

    # Hash password and create user
    user_data = data.model_dump(exclude={"password"})
    user_data["password_hash"] = hash_password(data.password)

    try:
        user = repository.create(db, user_data)
        db.commit()
        db.refresh(user)
        return user
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Resource already exists or violates unique constraint.",
            )
        if "foreign key" in msg:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Referenced resource does not exist.",
            )
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Data integrity error.",
        )
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def list_users(
    db: Session,
    limit: int,
    offset: int,
    role: Optional[str] = None,
    is_active: Optional[bool] = None,
    department_id: Optional[str] = None,
    search: Optional[str] = None,
) -> dict:
    filters = {}
    if role is not None:
        filters["role"] = role
    if is_active is not None:
        filters["is_active"] = is_active
    if department_id is not None:
        filters["department_id"] = department_id
    if search:
        filters["search"] = search

    items = repository.list_all(db, limit=limit, offset=offset, **filters)
    total = repository.count_all(db, **filters)
    return {"items": items, "total": total, "limit": limit, "offset": offset}


def get_user_by_id(db: Session, user_id: str):
    user = repository.get_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found.",
        )
    return user


def get_user_details(db: Session, user_id: str):
    user = repository.get_with_details(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found.",
        )
    return user


def update_user(db: Session, user_id: str, data: UserUpdate):
    user = repository.get_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found.",
        )

    update_data = data.model_dump(exclude_unset=True)

    # Validate department exists if being updated
    if "department_id" in update_data and update_data["department_id"]:
        department = org_repo.get_by_id(db, update_data["department_id"])
        if not department:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Referenced department does not exist.",
            )

    # Check for email uniqueness if being updated
    if "email" in update_data:
        existing_user = repository.get_by_email(db, update_data["email"])
        if existing_user and existing_user.id != user_id:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="User with this email already exists.",
            )

    try:
        updated_user = repository.update(db, user_id, update_data)
        db.commit()
        db.refresh(updated_user)
        return updated_user
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "unique" in msg or "duplicate" in msg:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Resource already exists or violates unique constraint.",
            )
        if "foreign key" in msg:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Referenced resource does not exist.",
            )
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Data integrity error.",
        )
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def delete_user(db: Session, user_id: str):
    user = repository.get_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="User not found.",
        )

    try:
        repository.delete(db, user_id)
        db.commit()
        return {"message": "User deleted successfully"}
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "foreign key" in msg:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Cannot delete user while referenced by other resources.",
            )
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Data integrity error.",
        )
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


# Notification handlers
def create_notification(db: Session, data: NotificationCreate):
    # Validate user exists
    user = repository.get_by_id(db, data.user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Referenced user does not exist.",
        )

    notification_data = data.model_dump()

    try:
        notification = repository.notification_repository.create(db, notification_data)
        db.commit()
        db.refresh(notification)
        return notification
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "foreign key" in msg:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Referenced resource does not exist.",
            )
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Data integrity error.",
        )
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def list_notifications(
    db: Session,
    limit: int,
    offset: int,
    user_id: Optional[str] = None,
    is_read: Optional[bool] = None,
    notification_type: Optional[str] = None,
) -> dict:
    filters = {}
    if user_id is not None:
        filters["user_id"] = user_id
    if is_read is not None:
        filters["is_read"] = is_read
    if notification_type is not None:
        filters["notification_type"] = notification_type

    items = repository.notification_repository.list_all(db, limit=limit, offset=offset, **filters)
    total = repository.notification_repository.count_all(db, **filters)
    return {"items": items, "total": total, "limit": limit, "offset": offset}


def get_notification_by_id(db: Session, notification_id: str):
    notification = repository.notification_repository.get_by_id(db, notification_id)
    if not notification:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Notification not found.",
        )
    return notification


def update_notification(db: Session, notification_id: str, data: NotificationUpdate):
    notification = repository.notification_repository.get_by_id(db, notification_id)
    if not notification:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Notification not found.",
        )

    update_data = data.model_dump(exclude_unset=True)

    try:
        updated_notification = repository.notification_repository.update(
            db, notification_id, update_data
        )
        db.commit()
        db.refresh(updated_notification)
        return updated_notification
    except IntegrityError as exc:
        db.rollback()
        msg = str(exc.orig).lower() if exc.orig else ""
        if "foreign key" in msg:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Referenced resource does not exist.",
            )
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Data integrity error.",
        )
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def delete_notification(db: Session, notification_id: str):
    notification = repository.notification_repository.get_by_id(db, notification_id)
    if not notification:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Notification not found.",
        )

    try:
        repository.notification_repository.delete(db, notification_id)
        db.commit()
        return {"message": "Notification deleted successfully"}
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def mark_notification_read(db: Session, notification_id: str):
    notification = repository.notification_repository.get_by_id(db, notification_id)
    if not notification:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Notification not found.",
        )

    if notification.is_read:
        return notification

    update_data = {"is_read": True, "read_at": utc_now()}

    try:
        updated_notification = repository.notification_repository.update(
            db, notification_id, update_data
        )
        db.commit()
        db.refresh(updated_notification)
        return updated_notification
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def mark_all_notifications_read(db: Session, user_id: str):
    user = repository.get_by_id(db, user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="User not found.",
        )

    unread_notifications = repository.notification_repository.list_all(
        db, limit=1000, offset=0, user_id=user_id, is_read=False
    )

    if not unread_notifications:
        return {"message": "No unread notifications", "updated_count": 0}

    try:
        read_time = utc_now()
        update_data = {"is_read": True, "read_at": read_time}
        for notification in unread_notifications:
            repository.notification_repository.update(db, notification.id, update_data)

        db.commit()
        return {
            "message": "All notifications marked as read",
            "updated_count": len(unread_notifications),
        }
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise