from __future__ import annotations

import uuid
from datetime import UTC, datetime
from typing import Any

from app.api.deps import CurrentUserContext
from app.services.expense_claim_workflow_constants import (
    APPROVAL_DONE_STAGE,
    BUDGET_MANAGER_APPROVAL_STAGE,
    DIRECT_MANAGER_APPROVAL_STAGE,
    FINANCE_APPROVAL_STAGE,
    PAYMENT_PAID_STAGE,
    PAYMENT_PAID_STATUS,
    PAYMENT_PENDING_STAGE,
    PAYMENT_PENDING_STATUS,
)


class ExpenseClaimApprovalFlowMixin:
    def approve_claim(
        self,
        claim_id: str,
        current_user: CurrentUserContext,
        *,
        opinion: str | None = None,
    ):
        claim = self.get_claim(claim_id, current_user)
        if claim is None:
            return None

        normalized_status = str(claim.status or "").strip().lower()
        if normalized_status != "submitted":
            raise ValueError("只有审批中的单据可以审批通过。")

        previous_stage = str(claim.approval_stage or "").strip()
        is_application_claim = self._is_expense_application_claim(claim)
        next_budget_manager = None
        if previous_stage == DIRECT_MANAGER_APPROVAL_STAGE:
            if not self._access_policy.can_approve_claim(current_user, claim):
                raise ValueError("只有当前直属领导审批人可以审批通过该单据。")
            approval_source = "manual_approval"
            event_type = "expense_application_approval" if is_application_claim else "expense_claim_approval"
            label = "领导审批通过"
            if is_application_claim:
                next_budget_manager = self._access_policy.resolve_department_budget_manager(claim)
                next_status = "submitted"
                next_stage = BUDGET_MANAGER_APPROVAL_STAGE
                default_message = "{operator} 已确认直属领导审核，流转至预算管理者审批。"
            else:
                next_status = "submitted"
                next_stage = FINANCE_APPROVAL_STAGE
                default_message = "{operator} 已审批通过，流转至{next_stage}。"
        elif previous_stage == BUDGET_MANAGER_APPROVAL_STAGE:
            if not is_application_claim:
                raise ValueError("只有费用申请需要预算管理者审批。")
            if not self._access_policy.can_approve_claim(current_user, claim):
                raise ValueError("只有当前预算管理者可以审批通过该费用申请。")
            approval_source = "budget_approval"
            event_type = "expense_application_budget_approval"
            label = "预算管理者审核通过"
            next_status = "approved"
            next_stage = APPROVAL_DONE_STAGE
            default_message = "{operator} 已完成预算审核，申请流程完成并生成报销草稿。"
        elif previous_stage == FINANCE_APPROVAL_STAGE:
            if is_application_claim:
                raise ValueError("费用申请需先完成预算管理者审批。")
            if not self._access_policy.can_approve_claim(current_user, claim):
                raise ValueError("只有财务人员可以完成财务终审。")
            approval_source = "finance_approval"
            event_type = "expense_claim_finance_approval"
            label = "财务审核通过"
            next_status = PAYMENT_PENDING_STATUS
            next_stage = PAYMENT_PENDING_STAGE
            default_message = "{operator} 已完成财务审核，进入待付款。"
        else:
            raise ValueError("当前节点不支持审批通过。")

        approval_opinion = str(opinion or "").strip()
        if previous_stage in {DIRECT_MANAGER_APPROVAL_STAGE, BUDGET_MANAGER_APPROVAL_STAGE} and not approval_opinion:
            approval_opinion = "同意"

        before_json = self._serialize_claim(claim)
        operator = self._access_policy.resolve_current_user_display_name(current_user)
        budget_flags: list[dict[str, Any]] = []
        if approval_source == "finance_approval" and not is_application_claim:
            consumed_budget_flag = self._consume_budget_for_finance_approval(claim, current_user)
            if consumed_budget_flag is not None:
                budget_flags.append(consumed_budget_flag)
        approval_flag = {
            "source": approval_source,
            "event_type": event_type,
            "approval_event_id": str(uuid.uuid4()),
            "severity": "info",
            "label": label,
            "message": approval_opinion or default_message.format(operator=operator, next_stage=next_stage),
            "opinion": approval_opinion,
            "operator": operator,
            "operator_username": current_user.username,
            "operator_role_codes": [
                str(item).strip().lower()
                for item in current_user.role_codes
                if str(item).strip()
            ],
            "previous_status": str(claim.status or "").strip(),
            "previous_approval_stage": previous_stage,
            "next_status": next_status,
            "next_approval_stage": next_stage,
            "created_at": datetime.now(UTC).isoformat(),
        }
        if next_budget_manager is not None:
            approval_flag.update(
                {
                    "next_approver_name": str(next_budget_manager.name or "").strip(),
                    "next_approver_employee_id": next_budget_manager.id,
                    "next_approver_grade": str(next_budget_manager.grade or "").strip(),
                    "next_approver_role_code": "budget_monitor",
                }
            )

        claim.status = next_status
        claim.approval_stage = next_stage
        if claim.submitted_at is None:
            claim.submitted_at = datetime.now(UTC)
        if is_application_claim and previous_stage == BUDGET_MANAGER_APPROVAL_STAGE:
            approval_flag["leader_opinion"] = self._resolve_latest_approval_opinion(
                claim,
                source="manual_approval",
            )
            approval_flag["budget_opinion"] = approval_opinion
            generated_draft = self._create_reimbursement_draft_from_application(
                application_claim=claim,
                approval_flag=approval_flag,
                operator=operator,
            )
            transferred_budget_flag = self._transfer_application_budget_to_reimbursement(
                application_claim=claim,
                draft_claim=generated_draft,
                current_user=current_user,
            )
            if transferred_budget_flag is not None:
                budget_flags.append(transferred_budget_flag)
                generated_draft.risk_flags_json = self._append_budget_flags(
                    generated_draft.risk_flags_json,
                    transferred_budget_flag,
                )
        claim.risk_flags_json = self._append_budget_flags(
            [*list(claim.risk_flags_json or []), approval_flag],
            budget_flags,
        )

        self.db.commit()
        self.db.refresh(claim)

        self.audit_service.log_action(
            actor=operator,
            action="expense_claim.approve",
            resource_type="expense_claim",
            resource_id=claim.id,
            before_json=before_json,
            after_json=self._serialize_claim(claim),
        )

        return claim

    def mark_claim_paid(
        self,
        claim_id: str,
        current_user: CurrentUserContext,
    ):
        claim = self.get_claim(claim_id, current_user)
        if claim is None:
            return None

        normalized_status = str(claim.status or "").strip().lower()
        if normalized_status == PAYMENT_PAID_STATUS:
            raise ValueError("该报销单已付款，无需重复确认。")
        if normalized_status != PAYMENT_PENDING_STATUS:
            raise ValueError("只有待付款状态的报销单可以确认已付款。")
        if not self._access_policy.can_mark_claim_paid(current_user, claim):
            raise ValueError("只有财务人员或高级财务人员可以确认付款，且不能处理本人单据。")

        before_json = self._serialize_claim(claim)
        operator = self._access_policy.resolve_current_user_display_name(current_user)
        previous_stage = str(claim.approval_stage or "").strip()
        payment_flag = {
            "source": "payment",
            "event_type": "expense_claim_payment_completed",
            "payment_event_id": str(uuid.uuid4()),
            "severity": "info",
            "label": "付款完成",
            "message": f"{operator} 已确认付款，报销单进入已付款。",
            "operator": operator,
            "operator_username": current_user.username,
            "operator_role_codes": [
                str(item).strip().lower()
                for item in current_user.role_codes
                if str(item).strip()
            ],
            "previous_status": str(claim.status or "").strip(),
            "previous_approval_stage": previous_stage,
            "next_status": PAYMENT_PAID_STATUS,
            "next_approval_stage": PAYMENT_PAID_STAGE,
            "created_at": datetime.now(UTC).isoformat(),
        }

        claim.status = PAYMENT_PAID_STATUS
        claim.approval_stage = PAYMENT_PAID_STAGE
        claim.risk_flags_json = [*list(claim.risk_flags_json or []), payment_flag]

        self.db.commit()
        self.db.refresh(claim)

        self.audit_service.log_action(
            actor=operator,
            action="expense_claim.mark_paid",
            resource_type="expense_claim",
            resource_id=claim.id,
            before_json=before_json,
            after_json=self._serialize_claim(claim),
        )

        return claim

    @staticmethod
    def _resolve_latest_approval_opinion(claim, *, source: str) -> str:
        for flag in reversed(list(claim.risk_flags_json or [])):
            if not isinstance(flag, dict):
                continue
            if str(flag.get("source") or "").strip() != source:
                continue
            opinion = str(flag.get("opinion") or flag.get("message") or "").strip()
            if opinion:
                return opinion
        return ""
