from __future__ import annotations

from dataclasses import dataclass

from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.models.employee import Employee
from app.models.risk_disposition import RiskDisposition
from app.services.expense_claim_access_policy import ExpenseClaimAccessPolicy

RISK_WAIVER_DECISION_ACTIONS = frozenset({"approve_waiver", "reject_waiver"})

_DECISION_ROLE_CODES = {"finance", "executive"}
_ACTIVE_EMPLOYMENT_STATUSES = {"active", "employed", "在职", "正常"}


class RiskWaiverDecisionPermissionError(RuntimeError):
    pass


@dataclass(frozen=True, slots=True)
class RiskWaiverDecisionEligibility:
    allowed: bool
    actor_id: str = ""
    actor_name: str = ""
    reason: str = ""


class RiskWaiverDecisionPolicy:
    """集中校验风险豁免决定人的租户、在职、角色和职责分离边界。"""

    def __init__(self, db: Session) -> None:
        self.db = db

    def actor_identity(
        self,
        current_user: CurrentUserContext | None,
        *,
        fallback_id: str,
        fallback_name: str,
    ) -> tuple[str, str]:
        employee = self._resolve_actor_employee(current_user)
        if employee is None:
            return (
                _text(fallback_id) or "anonymous",
                _text(fallback_name) or _text(fallback_id) or "anonymous",
            )
        return employee.id, _text(employee.name) or _text(fallback_name) or employee.id

    def require_decider(
        self,
        disposition: RiskDisposition,
        *,
        current_user: CurrentUserContext | None,
        tenant_id: str,
    ) -> tuple[str, str]:
        eligibility = self.evaluate_decider(
            disposition,
            current_user=current_user,
            tenant_id=tenant_id,
        )
        if not eligibility.allowed:
            raise RiskWaiverDecisionPermissionError(eligibility.reason)
        return eligibility.actor_id, eligibility.actor_name

    def evaluate_decider(
        self,
        disposition: RiskDisposition,
        *,
        current_user: CurrentUserContext | None,
        tenant_id: str,
    ) -> RiskWaiverDecisionEligibility:
        if current_user is None:
            return RiskWaiverDecisionEligibility(
                False,
                reason="风险豁免决定必须由已登录处理人执行。",
            )
        if _tenant(current_user.tenant_id) != tenant_id:
            return RiskWaiverDecisionEligibility(
                False,
                reason="不能跨租户处理风险豁免决定。",
            )

        employee = self._resolve_actor_employee(current_user)
        if employee is None:
            return RiskWaiverDecisionEligibility(
                False,
                reason="风险豁免决定必须由企业在职员工执行。",
            )
        employment_status = _text(employee.employment_status).lower()
        if employment_status not in _ACTIVE_EMPLOYMENT_STATUSES:
            return RiskWaiverDecisionEligibility(
                False,
                reason="当前处理人不是在职员工，不能决定风险豁免。",
            )

        role_codes = ExpenseClaimAccessPolicy.normalize_role_codes(current_user)
        role_codes.update(
            _text(role.role_code).lower()
            for role in list(employee.roles or [])
            if _text(role.role_code)
        )
        if not role_codes & _DECISION_ROLE_CODES:
            return RiskWaiverDecisionEligibility(
                False,
                reason="只有具有 finance 或 executive 明确角色的在职处理人可以决定风险豁免。",
            )

        requester_id = _text(disposition.waiver_requester_id)
        if not requester_id:
            return RiskWaiverDecisionEligibility(
                False,
                reason="历史豁免申请缺少申请人身份，不能执行豁免决定。",
            )
        actor_aliases = {
            employee.id.casefold(),
            _text(employee.email).casefold(),
            _text(employee.employee_no).casefold(),
            _text(current_user.employee_id).casefold(),
            _text(current_user.username).casefold(),
        }
        actor_aliases.discard("")
        if requester_id.casefold() in actor_aliases:
            return RiskWaiverDecisionEligibility(
                False,
                reason="风险豁免申请人不能批准或拒绝自己的申请。",
            )
        return RiskWaiverDecisionEligibility(
            True,
            actor_id=employee.id,
            actor_name=_text(employee.name) or employee.id,
        )

    def _resolve_actor_employee(
        self,
        current_user: CurrentUserContext | None,
    ) -> Employee | None:
        if current_user is None:
            return None
        employee_id = _text(current_user.employee_id)
        if employee_id:
            employee = self.db.get(Employee, employee_id)
            if employee is not None:
                return employee
        return ExpenseClaimAccessPolicy(self.db).resolve_current_employee(current_user)


def _tenant(value: str) -> str:
    return _text(value) or "default"


def _text(value: object) -> str:
    return str(value or "").strip()
