from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, selectinload

from app.api.deps import AuthSession, require_client_access_manager_session
from app.db.session import get_db
from app.models.client_access import ClientAccount, ClientFeatureAccess, ClientUser, ClientUserModulePermission
from app.schemas.client_access import (
    ClientAccessRead,
    ClientFeatureUpdate,
    ClientUserCreate,
    ClientUserModulePermissionUpdate,
    ClientUserUpdate,
)
from app.services.client_access_service import (
    ACCESS_LEVEL_ORDER,
    MODULE_INDEX,
    default_access_level_for_role,
    ensure_user_module_permissions,
    list_client_accounts,
    record_audit_event,
    serialize_client_account,
)

router = APIRouter(prefix="/api/client-access", tags=["client-access"])


def _authorized_client_scope(db: Session, session: AuthSession) -> list[ClientAccount]:
    clients = list_client_accounts(db)
    if session.role in {"admin", "internal"}:
        return clients
    return [client for client in clients if client.id == session.client_account_id]


def _get_client_or_404(db: Session, client_id: int, session: AuthSession) -> ClientAccount:
    clients = _authorized_client_scope(db, session)
    client = next((client for client in clients if client.id == client_id), None)
    if client is None:
        raise HTTPException(status_code=404, detail="Client account not found")
    return client


def _get_user_or_404(db: Session, user_id: int, session: AuthSession) -> ClientUser:
    user = db.scalar(
        select(ClientUser)
        .where(ClientUser.id == user_id)
        .options(selectinload(ClientUser.module_permissions))
    )
    if user is None:
        raise HTTPException(status_code=404, detail="Client user not found")
    _get_client_or_404(db, user.client_account_id, session)
    return user


def _read_client_account(db: Session, client_id: int, session: AuthSession) -> dict:
    client = next((item for item in _authorized_client_scope(db, session) if item.id == client_id), None)
    if client is None:
        raise HTTPException(status_code=404, detail="Client account not found")
    return serialize_client_account(client)


def _actor_metadata(session: AuthSession) -> dict[str, str]:
    if session.role in {"admin", "internal"}:
        return {
            "actor_type": "lean_admin",
            "actor_name": session.name,
            "actor_email": session.email,
            "actor_role": session.client_role or "admin",
        }
    return {
        "actor_type": "client_superadmin",
        "actor_name": session.name,
        "actor_email": session.email,
        "actor_role": session.client_role or "client",
    }


@router.get("", response_model=list[ClientAccessRead])
def get_client_access(
    limit: int = Query(default=100, ge=1, le=200),
    db: Session = Depends(get_db),
    session: AuthSession = Depends(require_client_access_manager_session),
):
    return [serialize_client_account(client) for client in _authorized_client_scope(db, session)[:limit]]


@router.post("/users", response_model=ClientAccessRead, status_code=status.HTTP_201_CREATED)
def create_client_user(
    payload: ClientUserCreate,
    db: Session = Depends(get_db),
    session: AuthSession = Depends(require_client_access_manager_session),
):
    client = _get_client_or_404(db, payload.client_account_id, session)
    user = ClientUser(tenant_id=client.tenant_id, **payload.model_dump())
    db.add(user)
    db.flush()
    ensure_user_module_permissions(db, user)
    record_audit_event(
        db,
        tenant_id=client.tenant_id,
        client_account_id=client.id,
        action="user.created",
        target_type="client_user",
        target_id=user.id,
        module_key="client_access",
        summary=f"{user.full_name} was created with the {user.role} role.",
        **_actor_metadata(session),
    )

    try:
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="A user with that email already exists for this client") from exc

    return _read_client_account(db, payload.client_account_id, session)


@router.patch("/users/{user_id}", response_model=ClientAccessRead)
def update_client_user(
    user_id: int,
    payload: ClientUserUpdate,
    db: Session = Depends(get_db),
    session: AuthSession = Depends(require_client_access_manager_session),
):
    user = _get_user_or_404(db, user_id, session)
    changes = payload.model_dump(exclude_unset=True)
    original_role = user.role

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

    if "role" in changes and changes["role"] != original_role:
        for permission in user.module_permissions:
            permission.access_level = default_access_level_for_role(user.role, permission.module_key)

    record_audit_event(
        db,
        tenant_id=user.tenant_id,
        client_account_id=user.client_account_id,
        action="user.updated",
        target_type="client_user",
        target_id=user.id,
        module_key="client_access",
        summary=f"{user.full_name} access was updated.",
        **_actor_metadata(session),
    )

    try:
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="A user with that email already exists for this client") from exc

    return _read_client_account(db, user.client_account_id, session)


@router.patch("/users/{user_id}/module-permissions/{module_key}", response_model=ClientAccessRead)
def update_client_user_module_permission(
    user_id: int,
    module_key: str,
    payload: ClientUserModulePermissionUpdate,
    db: Session = Depends(get_db),
    session: AuthSession = Depends(require_client_access_manager_session),
):
    user = _get_user_or_404(db, user_id, session)
    if payload.access_level not in ACCESS_LEVEL_ORDER:
        raise HTTPException(status_code=422, detail="Invalid access level")
    permission = db.scalar(
        select(ClientUserModulePermission).where(
            ClientUserModulePermission.client_user_id == user.id,
            ClientUserModulePermission.module_key == module_key,
        )
    )
    if permission is None:
        if module_key not in MODULE_INDEX:
            raise HTTPException(status_code=404, detail="Module permission not found")
        permission = ClientUserModulePermission(
            tenant_id=user.tenant_id,
            client_account_id=user.client_account_id,
            client_user_id=user.id,
            module_key=module_key,
            access_level=default_access_level_for_role(user.role, module_key),
        )
        db.add(permission)
        db.flush()

    permission.access_level = payload.access_level
    module_name = MODULE_INDEX.get(module_key, {}).get("module_name", module_key.replace("_", " ").title())
    record_audit_event(
        db,
        tenant_id=user.tenant_id,
        client_account_id=user.client_account_id,
        action="module_permission.updated",
        target_type="client_user_module_permission",
        target_id=permission.id,
        module_key=module_key,
        summary=f"{user.full_name} now has {payload.access_level} access to {module_name}.",
        **_actor_metadata(session),
    )
    db.commit()
    return _read_client_account(db, user.client_account_id, session)


@router.patch("/features/{feature_id}", response_model=ClientAccessRead)
def update_client_feature(
    feature_id: int,
    payload: ClientFeatureUpdate,
    db: Session = Depends(get_db),
    session: AuthSession = Depends(require_client_access_manager_session),
):
    feature = db.scalar(select(ClientFeatureAccess).where(ClientFeatureAccess.id == feature_id))
    if feature is None:
        raise HTTPException(status_code=404, detail="Client feature not found")

    _get_client_or_404(db, feature.client_account_id, session)
    feature.enabled = payload.enabled
    record_audit_event(
        db,
        tenant_id=feature.tenant_id,
        client_account_id=feature.client_account_id,
        action="feature.updated",
        target_type="client_feature",
        target_id=feature.id,
        module_key=feature.feature_key,
        summary=f"{feature.feature_name} was {'enabled' if payload.enabled else 'disabled'}.",
        **_actor_metadata(session),
    )
    db.commit()
    return _read_client_account(db, feature.client_account_id, session)
