"""Shared validation + DTO helpers for user staff profiles (no FastAPI imports)."""

from __future__ import annotations

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

from src.initiative_db.models import User, UserStaffProfile

EMPLOYEE_ID_PATTERN = re.compile(r"^[A-Z0-9-]{3,32}$")

STAFF_PROFILE_AUDIT_KEYS: frozenset[str] = frozenset(
    {
        "employee_id",
        "academic_title_code",
        "academic_title_other",
        "unit_id",
        "unit_name_freetext",
        "job_title",
        "profile_verification_status",
        "verification_submitted_at",
        "verified_at",
        "verified_by_user_id",
        "rejection_reason",
        "version",
    }
)


def normalize_employee_id(raw: str | None) -> str | None:
    if raw is None:
        return None
    s = str(raw).strip().upper()
    return s or None


def assert_employee_id_shape(emp: str | None) -> None:
    if emp is None:
        return
    if not EMPLOYEE_ID_PATTERN.match(emp):
        raise ValueError("Mã nhân sự không hợp lệ (3–32 ký tự A-Z, 0-9, gạch ngang).")


def assert_unit_exclusive(user: User, sp: UserStaffProfile) -> None:
    if user.unit_id is not None and sp.unit_name_freetext:
        t = sp.unit_name_freetext.strip()
        if t:
            raise ValueError("Chọn đơn vị trong danh mục hoặc nhập tên tự do — không dùng cùng lúc.")


def staff_row_for_audit(sp: UserStaffProfile, user_unit_id: Optional[uuid.UUID]) -> dict[str, Any]:
    """Whitelist snapshot for audit_events.before/after (JSONB)."""
    out: dict[str, Any] = {
        "employee_id": sp.employee_id,
        "academic_title_code": sp.academic_title_code,
        "academic_title_other": sp.academic_title_other,
        "unit_id": str(user_unit_id) if user_unit_id else None,
        "unit_name_freetext": sp.unit_name_freetext,
        "job_title": sp.job_title,
        "profile_verification_status": sp.profile_verification_status,
        "verification_submitted_at": _iso(sp.verification_submitted_at),
        "verified_at": _iso(sp.verified_at),
        "verified_by_user_id": str(sp.verified_by_user_id) if sp.verified_by_user_id else None,
        "rejection_reason": sp.rejection_reason,
        "version": sp.version,
    }
    return {k: v for k, v in out.items() if k in STAFF_PROFILE_AUDIT_KEYS}


def _iso(dt: datetime | None) -> str | None:
    if dt is None:
        return None
    if dt.tzinfo is None:
        return dt.replace(tzinfo=timezone.utc).isoformat()
    return dt.isoformat()


def material_staff_fields_changed(
    before: Mapping[str, Any],
    after: Mapping[str, Any],
) -> bool:
    keys = (
        "employee_id",
        "academic_title_code",
        "academic_title_other",
        "unit_id",
        "unit_name_freetext",
        "job_title",
    )
    return any(before.get(k) != after.get(k) for k in keys)


def apply_reverify_from_verified(sp: UserStaffProfile, now: datetime) -> None:
    """Strict policy: verified profile returns to pending when institutional fields change."""
    sp.profile_verification_status = "pending"
    sp.verification_submitted_at = now
    sp.verified_at = None
    sp.verified_by_user_id = None
    sp.rejection_reason = None


def assert_complete_for_submission(user: User, sp: UserStaffProfile) -> None:
    emp = normalize_employee_id(sp.employee_id)
    if not emp:
        raise ValueError("Cần mã số nhân sự trước khi gửi xác minh.")
    assert_employee_id_shape(emp)

    if not sp.academic_title_code:
        raise ValueError("Chọn học hàm / học vị.")
    if sp.academic_title_code == "other":
        if not sp.academic_title_other or not str(sp.academic_title_other).strip():
            raise ValueError("Nhập nội dung khi chọn «Khác».")

    has_unit = user.unit_id is not None or (
        sp.unit_name_freetext and len(sp.unit_name_freetext.strip()) > 0
    )
    if not has_unit:
        raise ValueError("Chọn đơn vị công tác hoặc nhập tên đơn vị.")

    if not sp.job_title or not str(sp.job_title).strip():
        raise ValueError("Nhập chức vụ công tác.")
