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, AuditlogCreate, AuditlogUpdate
from department import repository as department_repo

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)


# User handlers
def create_user(db: Session, data: UserCreate) -> dict:
    if repository.get_by_email(db, data.email):
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User with this email already exists"
        )
    
    if data.department_id:
        dept = department_repo.get_by_id(db, data.department_id)
        if not dept:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Department does not exist"
            )
    
    data_dict = data.model_dump(exclude={"password"})
    data_dict["password_hash"] = hash_password(data.password)
    
    try:
        user = repository.create(db, data_dict)
        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.")
        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 get_user(db: Session, user_id: str) -> dict:
    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 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 is not None:
        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 update_user(db: Session, user_id: str, data: UserUpdate) -> dict:
    user = repository.get_by_id(db, user_id)
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    
    data_dict = data.model_dump(exclude_unset=True)
    
    if "email" in data_dict and data_dict["email"] != user.email:
        existing = repository.get_by_email(db, data_dict["email"])
        if existing:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="User with this email already exists"
            )
    
    if "department_id" in data_dict and data_dict["department_id"] is not None:
        dept = department_repo.get_by_id(db, data_dict["department_id"])
        if not dept:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Department does not exist"
            )
    
    try:
        updated = repository.update(db, user_id, data_dict)
        db.commit()
        db.refresh(updated)
        return updated
    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.")
        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) -> dict:
    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="User cannot be deleted while referenced by other records"
            )
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Data integrity error.")
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise


def get_user_details(db: Session, user_id: str) -> dict:
    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 {
        "id": user.id,
        "email": user.email,
        "first_name": user.first_name,
        "last_name": user.last_name,
        "role": user.role,
        "phone": user.phone,
        "department_id": user.department_id,
        "is_active": user.is_active,
        "created_at": user.created_at,
        "updated_at": user.updated_at,
        "department_name": user.department.name if user.department else None
    }


# Auditlog handlers
def create_auditlog(db: Session, data: AuditlogCreate) -> dict:
    if data.user_id:
        user = repository.get_by_id(db, data.user_id)
        if not user:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="User does not exist"
            )
    
    data_dict = data.model_dump()
    
    try:
        auditlog = repository.create_auditlog(db, data_dict)
        db.commit()
        db.refresh(auditlog)
        return auditlog
    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 get_auditlog(db: Session, auditlog_id: str) -> dict:
    auditlog = repository.get_auditlog_by_id(db, auditlog_id)
    if not auditlog:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Audit log not found")
    return auditlog


def list_auditlogs(db: Session, limit: int, offset: int, user_id: Optional[str] = None, entity_type: Optional[str] = None, entity_id: Optional[str] = None, action: Optional[str] = None) -> dict:
    filters = {}
    if user_id is not None:
        filters["user_id"] = user_id
    if entity_type is not None:
        filters["entity_type"] = entity_type
    if entity_id is not None:
        filters["entity_id"] = entity_id
    if action is not None:
        filters["action"] = action
    
    items = repository.list_auditlogs(db, limit=limit, offset=offset, **filters)
    total = repository.count_auditlogs(db, **filters)
    return {"items": items, "total": total, "limit": limit, "offset": offset}


def update_auditlog(db: Session, auditlog_id: str, data: AuditlogUpdate) -> dict:
    auditlog = repository.get_auditlog_by_id(db, auditlog_id)
    if not auditlog:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Audit log not found")
    
    data_dict = data.model_dump(exclude_unset=True)
    
    if "user_id" in data_dict and data_dict["user_id"] is not None:
        user = repository.get_by_id(db, data_dict["user_id"])
        if not user:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="User does not exist"
            )
    
    try:
        updated = repository.update_auditlog(db, auditlog_id, data_dict)
        db.commit()
        db.refresh(updated)
        return updated
    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_auditlog(db: Session, auditlog_id: str) -> dict:
    auditlog = repository.get_auditlog_by_id(db, auditlog_id)
    if not auditlog:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Audit log not found")
    
    try:
        repository.delete_auditlog(db, auditlog_id)
        db.commit()
        return {"message": "Audit log deleted successfully"}
    except HTTPException:
        db.rollback()
        raise
    except Exception:
        db.rollback()
        raise