"""Admin CRUD for per-initiative adjudication results (Duyệt / Từ chối)."""

from __future__ import annotations

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

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.audit import AuditAction, record_audit, resolve_actor_fields
from src.initiative_db.models import ApplicationAdminResult, Initiative, User
from src.initiative_db.submissions import (
    _as_submission_item,
    _resolve_initiative_and_latest_draft_for_application_id,
)

__all__ = [
    "get_admin_result_for_application",
    "create_admin_result",
    "update_admin_result",
    "upsert_admin_result",
    "delete_admin_result",
]


def _audit_admin_result_snapshot(rec: ApplicationAdminResult) -> Dict[str, Any]:
    return {
        "id": str(rec.id),
        "decision": rec.decision,
        "feedback": rec.feedback,
        "rationale": rec.rationale,
        "initiativeId": str(rec.initiative_id),
    }


def _iso_utc(dt: Optional[datetime]) -> str:
    if dt is None:
        return ""
    v = dt.astimezone(timezone.utc).replace(microsecond=0).isoformat()
    return v.replace("+00:00", "Z")


def _row_to_api(
    initiative: Initiative,
    payload: dict[str, Any],
    rec: ApplicationAdminResult,
    application_id: str,
) -> Dict[str, Any]:
    return {
        "id": str(rec.id),
        "applicationId": application_id,
        "initiativeId": str(initiative.id),
        "decision": rec.decision,
        "feedback": rec.feedback or "",
        "rationale": rec.rationale,
        "createdAt": _iso_utc(rec.created_at),
        "updatedAt": _iso_utc(rec.updated_at),
        "createdBy": str(rec.created_by) if rec.created_by else None,
        "updatedBy": str(rec.updated_by) if rec.updated_by else None,
        "createdByFullName": None,
        "updatedByFullName": None,
        "applicationName": str(_as_submission_item(initiative, payload).get("name") or ""),
    }


async def _attach_admin_user_full_names(session: AsyncSession, row: Dict[str, Any], rec: ApplicationAdminResult) -> None:
    """Mutates ``row`` in place: resolves ``users.full_name`` for created/updated audit ids."""
    ids = [uid for uid in (rec.created_by, rec.updated_by) if uid is not None]
    if not ids:
        return
    stmt = select(User.id, User.full_name).where(User.id.in_(ids))
    fetched = (await session.execute(stmt)).all()
    by_id = {uid: (fn or "").strip() or None for uid, fn in fetched}
    if rec.created_by is not None:
        row["createdByFullName"] = by_id.get(rec.created_by)
    if rec.updated_by is not None:
        row["updatedByFullName"] = by_id.get(rec.updated_by)


async def get_admin_result_for_application(
    session: AsyncSession,
    application_id: str,
) -> Optional[Dict[str, Any]]:
    try:
        initiative, draft = await _resolve_initiative_and_latest_draft_for_application_id(session, application_id)
    except LookupError:
        return None

    payload = dict(draft.payload) if isinstance(draft.payload, dict) else {}
    stmt = select(ApplicationAdminResult).where(ApplicationAdminResult.initiative_id == initiative.id)
    rec = (await session.execute(stmt)).scalar_one_or_none()
    if rec is None:
        return None

    aid = application_id.strip()
    row = _row_to_api(initiative, payload, rec, aid)
    await _attach_admin_user_full_names(session, row, rec)
    return row


async def create_admin_result(
    session: AsyncSession,
    application_id: str,
    admin_user_id: uuid.UUID,
    *,
    decision: str,
    feedback: str,
    rationale: Optional[str],
) -> Dict[str, Any]:
    initiative, draft = await _resolve_initiative_and_latest_draft_for_application_id(session, application_id)

    payload = dict(draft.payload) if isinstance(draft.payload, dict) else {}
    existing = (
        await session.execute(
            select(ApplicationAdminResult).where(ApplicationAdminResult.initiative_id == initiative.id)
        )
    ).scalar_one_or_none()
    if existing is not None:
        raise ValueError("result_already_exists")

    d = (decision or "").strip().lower()
    if d not in ("approved", "rejected"):
        raise ValueError("invalid_decision")

    rec = ApplicationAdminResult(
        initiative_id=initiative.id,
        decision=d,
        feedback=(feedback or "").strip(),
        rationale=(rationale or "").strip() or None,
        created_by=admin_user_id,
        updated_by=admin_user_id,
    )
    session.add(rec)

    initiative.status = d
    initiative.updated_at = datetime.now(timezone.utc)

    await session.flush()
    await session.refresh(rec)

    row = _row_to_api(initiative, payload, rec, application_id.strip())
    await _attach_admin_user_full_names(session, row, rec)
    ae, ar = await resolve_actor_fields(session, admin_user_id)
    await record_audit(
        session,
        actor_user_id=admin_user_id,
        actor_email=ae,
        actor_role=ar,
        action=AuditAction.create,
        entity_type="application_admin_result",
        entity_id=application_id.strip(),
        after=_audit_admin_result_snapshot(rec),
        metadata={"initiativeId": str(initiative.id)},
    )
    return row


async def update_admin_result(
    session: AsyncSession,
    application_id: str,
    admin_user_id: uuid.UUID,
    *,
    decision: str,
    feedback: str,
    rationale: Optional[str],
) -> Dict[str, Any]:
    initiative, draft = await _resolve_initiative_and_latest_draft_for_application_id(session, application_id)

    payload = dict(draft.payload) if isinstance(draft.payload, dict) else {}
    stmt = select(ApplicationAdminResult).where(ApplicationAdminResult.initiative_id == initiative.id)
    rec = (await session.execute(stmt)).scalar_one_or_none()
    if rec is None:
        raise LookupError("result_not_found")

    before_audit = _audit_admin_result_snapshot(rec)

    d = (decision or "").strip().lower()
    if d not in ("approved", "rejected"):
        raise ValueError("invalid_decision")

    rec.decision = d
    rec.feedback = (feedback or "").strip()
    rec.rationale = (rationale or "").strip() or None
    rec.updated_by = admin_user_id
    rec.updated_at = datetime.now(timezone.utc)

    initiative.status = d
    initiative.updated_at = datetime.now(timezone.utc)

    await session.flush()
    await session.refresh(rec)

    row = _row_to_api(initiative, payload, rec, application_id.strip())
    await _attach_admin_user_full_names(session, row, rec)
    ae, ar = await resolve_actor_fields(session, admin_user_id)
    await record_audit(
        session,
        actor_user_id=admin_user_id,
        actor_email=ae,
        actor_role=ar,
        action=AuditAction.update,
        entity_type="application_admin_result",
        entity_id=application_id.strip(),
        before=before_audit,
        after=_audit_admin_result_snapshot(rec),
        metadata={"initiativeId": str(initiative.id)},
    )
    return row


async def upsert_admin_result(
    session: AsyncSession,
    application_id: str,
    admin_user_id: uuid.UUID,
    *,
    decision: str,
    feedback: str,
    rationale: Optional[str],
) -> Dict[str, Any]:
    """
    Create or replace admin adjudication in one transaction (idempotent PUT semantics).
    Keeps ``initiatives.status`` in sync with ``decision``.
    """
    initiative, draft = await _resolve_initiative_and_latest_draft_for_application_id(session, application_id)

    payload = dict(draft.payload) if isinstance(draft.payload, dict) else {}
    stmt = select(ApplicationAdminResult).where(ApplicationAdminResult.initiative_id == initiative.id)
    rec = (await session.execute(stmt)).scalar_one_or_none()

    d = (decision or "").strip().lower()
    if d not in ("approved", "rejected"):
        raise ValueError("invalid_decision")

    fb = (feedback or "").strip()
    rat = (rationale or "").strip() or None
    now = datetime.now(timezone.utc)

    audit_action = AuditAction.create
    before_snap: Dict[str, Any] | None = None
    if rec is None:
        rec = ApplicationAdminResult(
            initiative_id=initiative.id,
            decision=d,
            feedback=fb,
            rationale=rat,
            created_by=admin_user_id,
            updated_by=admin_user_id,
        )
        session.add(rec)
    else:
        audit_action = AuditAction.update
        before_snap = _audit_admin_result_snapshot(rec)
        rec.decision = d
        rec.feedback = fb
        rec.rationale = rat
        rec.updated_by = admin_user_id
        rec.updated_at = now

    initiative.status = d
    initiative.updated_at = now

    await session.flush()
    await session.refresh(rec)

    row = _row_to_api(initiative, payload, rec, application_id.strip())
    await _attach_admin_user_full_names(session, row, rec)
    ae, ar = await resolve_actor_fields(session, admin_user_id)
    await record_audit(
        session,
        actor_user_id=admin_user_id,
        actor_email=ae,
        actor_role=ar,
        action=audit_action,
        entity_type="application_admin_result",
        entity_id=application_id.strip(),
        before=before_snap,
        after=_audit_admin_result_snapshot(rec),
        metadata={"initiativeId": str(initiative.id)},
    )
    return row


async def delete_admin_result(
    session: AsyncSession,
    application_id: str,
    *,
    actor_user_id: uuid.UUID,
) -> None:
    initiative, _draft = await _resolve_initiative_and_latest_draft_for_application_id(session, application_id)

    stmt = select(ApplicationAdminResult).where(ApplicationAdminResult.initiative_id == initiative.id)
    rec = (await session.execute(stmt)).scalar_one_or_none()
    if rec is None:
        raise LookupError("result_not_found")

    snap = _audit_admin_result_snapshot(rec)
    aid = application_id.strip()
    await session.delete(rec)

    initiative.status = "submitted"
    initiative.updated_at = datetime.now(timezone.utc)

    await session.flush()
    ae, ar = await resolve_actor_fields(session, actor_user_id)
    await record_audit(
        session,
        actor_user_id=actor_user_id,
        actor_email=ae,
        actor_role=ar,
        action=AuditAction.delete,
        entity_type="application_admin_result",
        entity_id=aid,
        before=snap,
        metadata={"initiativeId": str(initiative.id)},
    )
