"""User administration (admin-only) + technician lookup for assignment dropdowns."""
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session

from app.api.deps import client_ip, get_current_user, require_admin
from app.core.security import hash_password
from app.core.time import utcnow
from app.db.session import get_db
from app.models.enums import UserRole
from app.models.user import User
from app.schemas.auth import (
    UserBrief,
    UserCreate,
    UserOut,
    UserPasswordReset,
    UserUpdate,
)
from app.schemas.common import Message, Page
from app.services import audit

router = APIRouter(prefix="/users", tags=["users"])


def _get_user_or_404(db: Session, user_id: int) -> User:
    user = db.scalar(select(User).where(User.id == user_id, User.is_deleted.is_(False)))
    if user is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    return user


def _assert_mutable(user: User) -> None:
    """The system developer account is neither editable nor deletable."""
    if user.role == UserRole.SYSTEM_DEVELOPER:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="The system developer account is protected",
        )


def _assert_email_free(db: Session, email: str, exclude_id: int | None = None) -> None:
    stmt = select(User).where(User.email == email, User.is_deleted.is_(False))
    if exclude_id is not None:
        stmt = stmt.where(User.id != exclude_id)
    if db.scalar(stmt) is not None:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT, detail="Email is already registered"
        )


@router.get("", response_model=Page[UserOut])
def list_users(
    db: Session = Depends(get_db),
    _: User = Depends(require_admin),
    search: str | None = Query(None, description="Match name, email, or employee code"),
    role: UserRole | None = None,
    is_active: bool | None = None,
    page: int = Query(1, ge=1),
    page_size: int = Query(25, ge=1, le=200),
) -> Page[UserOut]:
    stmt = select(User).where(User.is_deleted.is_(False))
    if search:
        pattern = f"%{search.strip()}%"
        stmt = stmt.where(
            or_(
                User.full_name.ilike(pattern),
                User.email.ilike(pattern),
                User.employee_code.ilike(pattern),
            )
        )
    if role is not None:
        stmt = stmt.where(User.role == role)
    if is_active is not None:
        stmt = stmt.where(User.is_active.is_(is_active))

    total = db.scalar(select(func.count()).select_from(stmt.subquery())) or 0
    rows = db.scalars(
        stmt.order_by(User.full_name).offset((page - 1) * page_size).limit(page_size)
    ).all()
    return Page.build([UserOut.model_validate(r) for r in rows], total, page, page_size)


@router.get("/technicians", response_model=list[UserBrief])
def list_technicians(
    db: Session = Depends(get_db),
    _: User = Depends(get_current_user),
) -> list[UserBrief]:
    """Assignable staff — field staff plus managers — available to any authenticated
    user for dropdowns."""
    rows = db.scalars(
        select(User)
        .where(
            User.role.in_([UserRole.FIELD_STAFF, UserRole.MANAGER]),
            User.is_active.is_(True),
            User.is_deleted.is_(False),
        )
        .order_by(User.full_name)
    ).all()
    return [UserBrief.model_validate(r) for r in rows]


@router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(
    payload: UserCreate,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_admin),
) -> User:
    email = payload.email.lower()
    _assert_email_free(db, email)
    if payload.role == UserRole.SYSTEM_DEVELOPER:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="The system developer role cannot be assigned via the API",
        )

    user = User(
        full_name=payload.full_name,
        email=email,
        phone=payload.phone,
        role=payload.role,
        employee_code=payload.employee_code,
        is_active=payload.is_active,
        password_hash=hash_password(payload.password),
    )
    db.add(user)
    db.flush()
    audit.record(
        db,
        user_id=current_user.id,
        action="create",
        entity_type="user",
        entity_id=user.id,
        payload={"email": email, "role": payload.role.value},
        ip_address=client_ip(request),
    )
    db.commit()
    db.refresh(user)
    return user


@router.get("/{user_id}", response_model=UserOut)
def get_user(
    user_id: int, db: Session = Depends(get_db), _: User = Depends(require_admin)
) -> User:
    return _get_user_or_404(db, user_id)


@router.patch("/{user_id}", response_model=UserOut)
def update_user(
    user_id: int,
    payload: UserUpdate,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_admin),
) -> User:
    user = _get_user_or_404(db, user_id)
    _assert_mutable(user)
    changes = payload.model_dump(exclude_unset=True)

    if "email" in changes and changes["email"]:
        changes["email"] = changes["email"].lower()
        _assert_email_free(db, changes["email"], exclude_id=user.id)

    # Guard against an admin locking themselves out.
    if user.id == current_user.id:
        if changes.get("is_active") is False:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="You cannot deactivate yourself"
            )
        if "role" in changes and changes["role"] != UserRole.ADMIN:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="You cannot remove your own admin role",
            )

    for field, value in changes.items():
        setattr(user, field, value)

    audit.record(
        db,
        user_id=current_user.id,
        action="update",
        entity_type="user",
        entity_id=user.id,
        payload={k: (v.value if hasattr(v, "value") else v) for k, v in changes.items()},
        ip_address=client_ip(request),
    )
    db.commit()
    db.refresh(user)
    return user


@router.post("/{user_id}/reset-password", response_model=Message)
def reset_password(
    user_id: int,
    payload: UserPasswordReset,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_admin),
) -> Message:
    user = _get_user_or_404(db, user_id)
    _assert_mutable(user)
    user.password_hash = hash_password(payload.new_password)
    audit.record(
        db,
        user_id=current_user.id,
        action="reset_password",
        entity_type="user",
        entity_id=user.id,
        ip_address=client_ip(request),
    )
    db.commit()
    return Message(detail=f"Password reset for {user.email}")


@router.delete("/{user_id}", response_model=Message)
def delete_user(
    user_id: int,
    request: Request,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_admin),
) -> Message:
    """Soft delete — history rows reference users via SET NULL, so records survive."""
    user = _get_user_or_404(db, user_id)
    _assert_mutable(user)
    if user.id == current_user.id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="You cannot delete your own account"
        )

    remaining_admins = db.scalar(
        select(func.count())
        .select_from(User)
        .where(
            User.role == UserRole.ADMIN,
            User.is_active.is_(True),
            User.is_deleted.is_(False),
            User.id != user.id,
        )
    )
    if user.role == UserRole.ADMIN and not remaining_admins:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot delete the last admin"
        )

    user.is_deleted = True
    user.deleted_at = utcnow()
    user.is_active = False
    audit.record(
        db,
        user_id=current_user.id,
        action="delete",
        entity_type="user",
        entity_id=user.id,
        ip_address=client_ip(request),
    )
    db.commit()
    return Message(detail=f"User {user.email} deleted")
