"""Admin APIs for staff profile verification queue (conditional updates + audit)."""

from __future__ import annotations

import uuid
from datetime import datetime, timezone
from typing import Any, Optional

from fastapi import APIRouter, Header, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy import delete, func, select, text, update
from sqlalchemy.exc import IntegrityError, ProgrammingError
from sqlalchemy.ext.asyncio import AsyncSession

from src.audit import AuditAction, record_audit, resolve_actor_fields
from src.auth_api import _policy_admin_emails
from src.auth_jwt import decode_access_token_user_id, decode_bearer_token
from src.initiative_db.engine import get_session, is_postgres_enabled
from src.initiative_db.models import (
    AcademicTitle,
    ApplicationAdminResult,
    ApplicationArtifact,
    ApplicationReviewDocument,
    AuditLog,
    Initiative,
    Unit,
    User,
    UserRoleRow,
    UserStaffProfile,
)
from src.staff_profile_domain import staff_row_for_audit

router = APIRouter(prefix="/admin/user-profiles", tags=["admin-user-profiles"])

SYSTEM_DRAFT_USER_ID = uuid.UUID("00000000-0000-4000-8000-000000000001")
_FRONTEND_ROLES = frozenset({"admin", "editor", "viewer"})


def _jwt_role_strings(authorization: str | None) -> list[str]:
    p = decode_bearer_token(authorization)
    if not p:
        return []
    r = p.get("roles")
    if isinstance(r, list):
        return [str(x) for x in r]
    return []


def _require_admin_uid(authorization: str | None) -> uuid.UUID:
    uid = decode_access_token_user_id(authorization)
    if uid is None:
        raise HTTPException(status_code=401, detail="Đăng nhập để thực hiện thao tác.")
    if "admin" not in _jwt_role_strings(authorization):
        raise HTTPException(status_code=403, detail="Chỉ tài khoản quản trị mới thực hiện được.")
    return uid


class PendingProfileItem(BaseModel):
    userId: str
    email: str
    fullName: str
    employeeId: Optional[str] = None
    jobTitle: Optional[str] = None
    verificationSubmittedAt: Optional[datetime] = None
    version: int


class RegisteredUserItem(BaseModel):
    """Active accounts with staff profile snapshot (admin read-only directory)."""

    userId: str
    email: str
    fullName: str
    phone: Optional[str] = None
    createdAt: datetime
    employeeId: Optional[str] = None
    jobTitle: Optional[str] = None
    unitNameFreetext: Optional[str] = None
    unitCatalogName: Optional[str] = None
    academicTitleLabelVi: Optional[str] = None
    academicTitleOther: Optional[str] = None
    profileVerificationStatus: str = "draft"
    roles: list[str] = Field(default_factory=list)
    adminFromPolicy: bool = False
    policyAdminLocked: bool = False


class ProfileDetailResponse(BaseModel):
    userId: str
    email: str
    fullName: str
    phone: Optional[str] = None
    unitId: Optional[str] = None
    unitCatalogName: Optional[str] = None
    staffProfile: dict[str, Any]


class VerifyBody(BaseModel):
    expectedVersion: int = Field(..., ge=1)


class RejectBody(BaseModel):
    expectedVersion: int = Field(..., ge=1)
    reason: str = Field(..., min_length=1, max_length=2000)


class RemoveUserBody(BaseModel):
    confirmEmail: str = Field(..., min_length=3, max_length=320)


class SetUserRolesBody(BaseModel):
    admin: bool = False
    editor: bool = False
    viewer: bool = False


class UserRolesStateResponse(BaseModel):
    roles: list[str]
    adminFromPolicy: bool
    policyAdminLocked: bool


@router.get("/pending", response_model=list[PendingProfileItem])
async def list_pending(authorization: str | None = Header(None)) -> list[PendingProfileItem]:
    _require_admin_uid(authorization)
    if not is_postgres_enabled():
        raise HTTPException(status_code=503, detail="Cơ sở dữ liệu chưa cấu hình.")

    async with get_session() as session:
        stmt = (
            select(User, UserStaffProfile)
            .join(UserStaffProfile, UserStaffProfile.user_id == User.id)
            .where(UserStaffProfile.profile_verification_status == "pending", User.is_active.is_(True))
            .order_by(UserStaffProfile.verification_submitted_at.asc().nulls_last())
        )
        rows = (await session.execute(stmt)).all()
        out: list[PendingProfileItem] = []
        for user, sp in rows:
            out.append(
                PendingProfileItem(
                    userId=str(user.id),
                    email=user.email,
                    fullName=user.full_name,
                    employeeId=sp.employee_id,
                    jobTitle=sp.job_title,
                    verificationSubmittedAt=sp.verification_submitted_at,
                    version=sp.version,
                )
            )
        return out


@router.get("/registry", response_model=list[RegisteredUserItem])
async def list_registered_users(authorization: str | None = Header(None)) -> list[RegisteredUserItem]:
    """All active user accounts (successful registration) with HR fields for review / export."""
    _require_admin_uid(authorization)
    if not is_postgres_enabled():
        raise HTTPException(status_code=503, detail="Cơ sở dữ liệu chưa cấu hình.")

    async with get_session() as session:
        stmt = (
            select(User, UserStaffProfile, Unit, AcademicTitle)
            .outerjoin(UserStaffProfile, UserStaffProfile.user_id == User.id)
            .outerjoin(Unit, Unit.id == User.unit_id)
            .outerjoin(AcademicTitle, AcademicTitle.code == UserStaffProfile.academic_title_code)
            .where(User.is_active.is_(True))
            .order_by(User.created_at.desc())
        )
        rows = (await session.execute(stmt)).all()
        by_user_roles: dict[uuid.UUID, list[UserRoleRow]] = {}
        if rows:
            uids = [r[0].id for r in rows]
            role_stmt = select(UserRoleRow).where(UserRoleRow.user_id.in_(uids))
            for rr in (await session.execute(role_stmt)).scalars().all():
                by_user_roles.setdefault(rr.user_id, []).append(rr)
        policy = _policy_admin_emails()
        out: list[RegisteredUserItem] = []
        for user, sp, unit, title in rows:
            status = "draft"
            if sp is not None:
                status = sp.profile_verification_status or "draft"
            ur = by_user_roles.get(user.id, [])
            role_set = sorted({str(x.role) for x in ur if str(x.role) in _FRONTEND_ROLES})
            admin_fp = any(
                str(x.role) == "admin" and bool(x.admin_from_email_policy) for x in ur
            )
            email_norm = user.email.strip().lower()
            policy_lock = email_norm in policy
            out.append(
                RegisteredUserItem(
                    userId=str(user.id),
                    email=user.email,
                    fullName=user.full_name,
                    phone=user.phone,
                    createdAt=user.created_at,
                    employeeId=sp.employee_id if sp else None,
                    jobTitle=sp.job_title if sp else None,
                    unitNameFreetext=sp.unit_name_freetext if sp else None,
                    unitCatalogName=unit.name if unit is not None else None,
                    academicTitleLabelVi=title.label_vi if title is not None else None,
                    academicTitleOther=sp.academic_title_other if sp else None,
                    profileVerificationStatus=status,
                    roles=role_set,
                    adminFromPolicy=admin_fp,
                    policyAdminLocked=policy_lock,
                )
            )
        return out


async def _detail(session: AsyncSession, user_id: uuid.UUID) -> ProfileDetailResponse | None:
    stmt = (
        select(User, UserStaffProfile)
        .join(UserStaffProfile, UserStaffProfile.user_id == User.id)
        .where(User.id == user_id)
    )
    row = (await session.execute(stmt)).first()
    if row is None:
        return None
    user, sp = row
    unit_name: str | None = None
    if user.unit_id is not None:
        u = await session.get(Unit, user.unit_id)
        if u is not None:
            unit_name = u.name
    staff = {
        "employeeId": sp.employee_id,
        "academicTitleCode": sp.academic_title_code,
        "academicTitleOther": sp.academic_title_other,
        "unitNameFreetext": sp.unit_name_freetext,
        "jobTitle": sp.job_title,
        "profileVerificationStatus": sp.profile_verification_status,
        "verificationSubmittedAt": sp.verification_submitted_at,
        "verifiedAt": sp.verified_at,
        "verifiedByUserId": str(sp.verified_by_user_id) if sp.verified_by_user_id else None,
        "rejectionReason": sp.rejection_reason,
        "version": sp.version,
    }
    return ProfileDetailResponse(
        userId=str(user.id),
        email=user.email,
        fullName=user.full_name,
        phone=user.phone,
        unitId=str(user.unit_id) if user.unit_id else None,
        unitCatalogName=unit_name,
        staffProfile=staff,
    )


@router.get("/{user_id}", response_model=ProfileDetailResponse)
async def get_profile_detail(
    user_id: uuid.UUID, authorization: str | None = Header(None)
) -> ProfileDetailResponse:
    _require_admin_uid(authorization)
    if not is_postgres_enabled():
        raise HTTPException(status_code=503, detail="Cơ sở dữ liệu chưa cấu hình.")

    async with get_session() as session:
        detail = await _detail(session, user_id)
        if detail is None:
            raise HTTPException(status_code=404, detail="Không tìm thấy người dùng.")
        return detail


@router.patch("/{user_id}/roles", response_model=UserRolesStateResponse)
async def set_user_roles(
    user_id: uuid.UUID,
    body: SetUserRolesBody,
    authorization: str | None = Header(None),
) -> UserRolesStateResponse:
    """Replace app-facing roles (admin / editor / viewer) for a user."""
    admin_id = _require_admin_uid(authorization)
    if user_id == SYSTEM_DRAFT_USER_ID:
        raise HTTPException(status_code=400, detail="Không thể sửa vai trò tài khoản hệ thống.")

    if not is_postgres_enabled():
        raise HTTPException(status_code=503, detail="Cơ sở dữ liệu chưa cấu hình.")

    desired: set[str] = set()
    if body.admin:
        desired.add("admin")
    if body.editor:
        desired.add("editor")
    if body.viewer:
        desired.add("viewer")
    if not desired:
        raise HTTPException(status_code=400, detail="Chọn ít nhất một vai trò.")

    policy = _policy_admin_emails()

    async with get_session() as session:
        user = await session.get(User, user_id)
        if user is None or not user.is_active:
            raise HTTPException(status_code=404, detail="Không tìm thấy người dùng.")

        email_norm = user.email.strip().lower()
        if email_norm in policy and "admin" not in desired:
            raise HTTPException(
                status_code=400,
                detail="Không thể gỡ quyền Quản trị: email thuộc danh sách quản trị hệ thống.",
            )
        if user_id == admin_id and "admin" not in desired:
            raise HTTPException(
                status_code=400,
                detail="Không thể tự gỡ quyền Quản trị của chính mình.",
            )

        stmt = select(UserRoleRow).where(UserRoleRow.user_id == user_id)
        existing = list((await session.execute(stmt)).scalars().all())
        current_front = {str(r.role) for r in existing if str(r.role) in _FRONTEND_ROLES}
        before_roles = sorted(current_front)

        to_remove = current_front - desired
        to_add = desired - current_front

        for role in to_remove:
            await session.execute(
                delete(UserRoleRow).where(
                    UserRoleRow.user_id == user_id,
                    UserRoleRow.role == role,
                )
            )

        for role in to_add:
            session.add(
                UserRoleRow(
                    user_id=user_id,
                    role=role,
                    admin_from_email_policy=bool(role == "admin" and email_norm in policy),
                )
            )

        if to_remove or to_add:
            user.credential_version = int(user.credential_version or 0) + 1
            user.updated_at = datetime.now(timezone.utc)

        after_roles = sorted(desired)
        if before_roles != after_roles:
            actor_email, actor_role = await resolve_actor_fields(session, admin_id)
            await record_audit(
                session,
                actor_user_id=admin_id,
                actor_email=actor_email,
                actor_role=actor_role,
                action=AuditAction.update,
                entity_type="user_roles",
                entity_id=str(user_id),
                before={"roles": before_roles},
                after={"roles": after_roles},
                metadata={"action": "set_roles"},
            )

        await session.flush()
        stmt2 = select(UserRoleRow).where(UserRoleRow.user_id == user_id)
        final_rows = list((await session.execute(stmt2)).scalars().all())
        role_list = sorted({str(r.role) for r in final_rows if str(r.role) in _FRONTEND_ROLES})
        admin_fp = any(
            str(r.role) == "admin" and bool(r.admin_from_email_policy) for r in final_rows
        )
        return UserRolesStateResponse(
            roles=role_list,
            adminFromPolicy=admin_fp,
            policyAdminLocked=(email_norm in policy),
        )


@router.post("/{user_id}/verify")
async def verify_profile(
    user_id: uuid.UUID,
    body: VerifyBody,
    authorization: str | None = Header(None),
) -> dict[str, str]:
    admin_id = _require_admin_uid(authorization)
    if not is_postgres_enabled():
        raise HTTPException(status_code=503, detail="Cơ sở dữ liệu chưa cấu hình.")

    now = datetime.now(timezone.utc)
    async with get_session() as session:
        sp = await session.get(UserStaffProfile, user_id)
        user = await session.get(User, user_id)
        if sp is None or user is None:
            raise HTTPException(status_code=404, detail="Không tìm thấy hồ sơ.")
        before = staff_row_for_audit(sp, user.unit_id)

        stmt = (
            update(UserStaffProfile)
            .where(
                UserStaffProfile.user_id == user_id,
                UserStaffProfile.profile_verification_status == "pending",
                UserStaffProfile.version == body.expectedVersion,
            )
            .values(
                profile_verification_status="verified",
                verified_at=now,
                verified_by_user_id=admin_id,
                rejection_reason=None,
                version=UserStaffProfile.version + 1,
                updated_at=now,
            )
        )
        res = await session.execute(stmt)
        if res.rowcount == 0:
            raise HTTPException(
                status_code=409,
                detail="Không thể xác minh: trạng thái đã đổi hoặc phiên bản không khớp (vui lòng tải lại).",
            )

        await session.refresh(sp)
        after = staff_row_for_audit(sp, user.unit_id)
        actor_email, actor_role = await resolve_actor_fields(session, admin_id)
        await record_audit(
            session,
            actor_user_id=admin_id,
            actor_email=actor_email,
            actor_role=actor_role,
            action=AuditAction.update,
            entity_type="user_profile",
            entity_id=str(user_id),
            before=before,
            after=after,
            metadata={"action": "verify"},
        )
    return {"status": "verified"}


@router.post("/{user_id}/reject")
async def reject_profile(
    user_id: uuid.UUID,
    body: RejectBody,
    authorization: str | None = Header(None),
) -> dict[str, str]:
    admin_id = _require_admin_uid(authorization)
    if not is_postgres_enabled():
        raise HTTPException(status_code=503, detail="Cơ sở dữ liệu chưa cấu hình.")

    reason = body.reason.strip()
    if not reason:
        raise HTTPException(status_code=400, detail="Cần lý do từ chối.")

    now = datetime.now(timezone.utc)
    async with get_session() as session:
        sp = await session.get(UserStaffProfile, user_id)
        user = await session.get(User, user_id)
        if sp is None or user is None:
            raise HTTPException(status_code=404, detail="Không tìm thấy hồ sơ.")
        before = staff_row_for_audit(sp, user.unit_id)

        stmt = (
            update(UserStaffProfile)
            .where(
                UserStaffProfile.user_id == user_id,
                UserStaffProfile.profile_verification_status == "pending",
                UserStaffProfile.version == body.expectedVersion,
            )
            .values(
                profile_verification_status="rejected",
                verified_at=None,
                verified_by_user_id=None,
                rejection_reason=reason,
                version=UserStaffProfile.version + 1,
                updated_at=now,
            )
        )
        res = await session.execute(stmt)
        if res.rowcount == 0:
            raise HTTPException(
                status_code=409,
                detail="Không thể từ chối: trạng thái đã đổi hoặc phiên bản không khớp (vui lòng tải lại).",
            )

        await session.refresh(sp)
        after = staff_row_for_audit(sp, user.unit_id)
        actor_email, actor_role = await resolve_actor_fields(session, admin_id)
        await record_audit(
            session,
            actor_user_id=admin_id,
            actor_email=actor_email,
            actor_role=actor_role,
            action=AuditAction.update,
            entity_type="user_profile",
            entity_id=str(user_id),
            before=before,
            after=after,
            metadata={"action": "reject"},
        )
    return {"status": "rejected"}


@router.post("/{user_id}/remove")
async def remove_user_account(
    user_id: uuid.UUID,
    body: RemoveUserBody,
    authorization: str | None = Header(None),
) -> dict[str, str]:
    """
    Permanently delete a user row (cascades roles, staff profile, OTP tokens, etc.).
    Blocked for admins, the system draft user, self-delete, and accounts that still own initiatives.
    """
    admin_id = _require_admin_uid(authorization)
    if user_id == admin_id:
        raise HTTPException(status_code=400, detail="Không thể xóa chính mình.")
    if user_id == SYSTEM_DRAFT_USER_ID:
        raise HTTPException(status_code=400, detail="Không thể xóa tài khoản hệ thống.")

    if not is_postgres_enabled():
        raise HTTPException(status_code=503, detail="Cơ sở dữ liệu chưa cấu hình.")

    confirm = body.confirmEmail.strip().lower()
    if not confirm:
        raise HTTPException(status_code=400, detail="Cần nhập email để xác nhận.")

    async with get_session() as session:
        user = await session.get(User, user_id)
        if user is None:
            raise HTTPException(status_code=404, detail="Không tìm thấy người dùng.")
        if user.email.strip().lower() != confirm:
            raise HTTPException(status_code=400, detail="Email xác nhận không khớp tài khoản.")

        admin_stmt = select(UserRoleRow.user_id).where(
            UserRoleRow.user_id == user_id,
            UserRoleRow.role == "admin",
        )
        if (await session.execute(admin_stmt)).first() is not None:
            raise HTTPException(status_code=403, detail="Không thể xóa tài khoản quản trị.")

        own_count = (
            await session.execute(
                select(func.count()).select_from(Initiative).where(Initiative.owner_id == user_id)
            )
        ).scalar_one()
        if own_count and int(own_count) > 0:
            raise HTTPException(
                status_code=409,
                detail="Tài khoản còn sáng kiến/đơn (owner). Xóa hoặc chuyển dữ liệu trước.",
            )

        await session.execute(
            update(UserStaffProfile)
            .where(UserStaffProfile.verified_by_user_id == user_id)
            .values(verified_by_user_id=None)
        )
        await session.execute(
            update(ApplicationArtifact)
            .where(ApplicationArtifact.uploaded_by == user_id)
            .values(uploaded_by=None)
        )
        await session.execute(
            update(ApplicationReviewDocument)
            .where(ApplicationReviewDocument.created_by == user_id)
            .values(created_by=None)
        )
        await session.execute(
            update(ApplicationAdminResult)
            .where(ApplicationAdminResult.created_by == user_id)
            .values(created_by=None)
        )
        await session.execute(
            update(ApplicationAdminResult)
            .where(ApplicationAdminResult.updated_by == user_id)
            .values(updated_by=None)
        )
        await session.execute(update(AuditLog).where(AuditLog.actor_id == user_id).values(actor_id=None))

        async with session.begin_nested():
            try:
                await session.execute(
                    text("UPDATE authors SET user_id = NULL WHERE user_id = CAST(:uid AS uuid)"),
                    {"uid": str(user_id)},
                )
            except ProgrammingError:
                pass

        before_user = {
            "userId": str(user.id),
            "email": user.email,
            "fullName": user.full_name,
        }
        actor_email, actor_role = await resolve_actor_fields(session, admin_id)
        await record_audit(
            session,
            actor_user_id=admin_id,
            actor_email=actor_email,
            actor_role=actor_role,
            action=AuditAction.delete,
            entity_type="user",
            entity_id=str(user_id),
            before=before_user,
            after=None,
            metadata={"action": "admin_remove_user"},
        )

        # Delete staff profile before User: ORM would otherwise try to NULL user_staff_profiles.user_id,
        # which is the row's primary key (AssertionError on flush).
        sp_row = await session.get(UserStaffProfile, user_id)
        if sp_row is not None:
            await session.delete(sp_row)
            await session.flush()

        try:
            await session.delete(user)
            await session.flush()
        except IntegrityError:
            await session.rollback()
            raise HTTPException(
                status_code=409,
                detail="Không thể xóa: tài khoản còn được tham chiếu (ví dụ: minh chứng, đánh giá hội đồng).",
            ) from None

    return {"status": "deleted"}