from __future__ import annotations

from datetime import UTC, datetime
from typing import Any

from app.api.deps import CurrentUserContext
from app.models.financial_record import ExpenseClaim
from app.services.expense_claim_errors import ExpenseClaimSubmissionBlockedError
from app.services.expense_claim_historical_evidence import (
    ExpenseClaimHistoricalEvidenceService,
)
from app.services.expense_claim_pre_review_decision import build_pre_review_decision
from app.services.expense_claim_risk_flags import dedupe_claim_risk_flags
from app.services.expense_claim_risk_stage import (
    risk_business_stage_for_claim,
    with_risk_business_stage,
)


class ExpenseClaimPreReviewMixin:
    def pre_review_claim(
        self,
        claim_id: str,
        current_user: CurrentUserContext,
        *,
        correlation_id: str | None = None,
        idempotency_key: str | None = None,
    ) -> ExpenseClaim | None:
        claim = self.get_claim(claim_id, current_user)
        if claim is None:
            return None

        self._ensure_draft_claim(claim)
        self._access_policy.backfill_claim_identity_from_current_user(claim, current_user)
        is_application_claim = self._is_expense_application_claim(claim)
        if not is_application_claim:
            self._sync_claim_from_items(claim)
        missing_fields = (
            self._validate_application_claim_for_submission(claim)
            if is_application_claim
            else self._validate_claim_for_submission(claim)
        )
        if missing_fields:
            raise ExpenseClaimSubmissionBlockedError(missing_fields)

        before_json = self._serialize_claim(claim)
        pre_review_flag = self.refresh_claim_pre_review_state(
            claim,
            is_application_claim=is_application_claim,
            reviewed_at=datetime.now(UTC),
            tenant_id=current_user.tenant_id,
        )
        if pre_review_flag is None:
            raise RuntimeError("无法生成费用预审结果。")
        claim.approval_stage = "待提交" if not is_application_claim else claim.approval_stage
        claim.submitted_at = None

        normalized_correlation_id = self._expense_cases.normalize_correlation_id(
            correlation_id or idempotency_key or str(pre_review_flag.get("review_id") or "")
        )
        try:
            self._record_pre_review_event(
                claim,
                pre_review_flag=pre_review_flag,
                current_user=current_user,
                is_application_claim=is_application_claim,
                correlation_id=normalized_correlation_id,
            )
            self.db.commit()
            self.db.refresh(claim)
        except Exception:
            self.db.rollback()
            raise

        self.audit_service.log_action(
            actor=current_user.name or current_user.username,
            action="expense_claim.pre_review",
            resource_type="expense_claim",
            resource_id=claim.id,
            before_json=before_json,
            after_json=self._serialize_claim(claim),
        )
        return claim

    @staticmethod
    def _build_ai_pre_review_flag(
        *,
        decision_payload: dict[str, Any],
        business_stage: str,
        historical_case_evidence: list[dict[str, Any]] | None = None,
    ) -> dict[str, Any]:
        decision = str(decision_payload.get("decision") or "ready_with_review")
        passed = decision != "needs_fix"
        blocking_count = int(decision_payload.get("blocking_count") or 0)

        return with_risk_business_stage(
            {
                "source": "ai_pre_review",
                "event_type": "expense_claim_ai_pre_review",
                "severity": "info" if passed else "high",
                "label": "自动检测通过"
                if decision == "ready"
                else ("自动检测待复核" if passed else "自动检测未通过"),
                "message": str(decision_payload.get("message") or ""),
                "status": "passed" if passed else "failed",
                "passed": passed,
                "blocking_risk_count": blocking_count,
                **decision_payload,
                "historical_case_evidence": list(historical_case_evidence or []),
                "next_action": "next_step" if passed else "risk_explanation_required",
                "created_at": str(decision_payload.get("reviewed_at") or ""),
            },
            business_stage,
        )

    @staticmethod
    def _replace_ai_pre_review_flag(
        risk_flags: list[Any],
        next_flag: dict[str, Any],
    ) -> list[Any]:
        preserved_flags = [
            flag
            for flag in list(risk_flags or [])
            if not (
                isinstance(flag, dict) and str(flag.get("source") or "").strip() == "ai_pre_review"
            )
        ]
        return [*preserved_flags, next_flag]

    def refresh_claim_pre_review_state(
        self,
        claim: ExpenseClaim,
        *,
        is_application_claim: bool | None = None,
        reviewed_at: datetime | None = None,
        tenant_id: str = "",
    ) -> dict[str, Any] | None:
        """业务变更事务内刷新预审快照，不提交、不单独写事件。"""
        return self._refresh_claim_pre_review_flags(
            claim,
            is_application_claim=is_application_claim,
            reviewed_at=reviewed_at,
            tenant_id=tenant_id,
        )

    def _refresh_claim_pre_review_flags(
        self,
        claim: ExpenseClaim,
        *,
        is_application_claim: bool | None = None,
        reviewed_at: datetime | None = None,
        tenant_id: str = "",
    ) -> dict[str, Any] | None:
        if claim is None:
            return None

        if is_application_claim is None:
            is_application_claim = self._is_expense_application_claim(claim)
        reviewed_at = reviewed_at or datetime.now(UTC)

        if is_application_claim:
            preserved_flags = [
                flag
                for flag in list(claim.risk_flags_json or [])
                if not (
                    isinstance(flag, dict)
                    and str(flag.get("source") or "").strip() == "submission_review"
                    and str(flag.get("hit_source") or "").strip() == "rule_center"
                )
            ]
            application_review = self.evaluate_platform_risk_rules(
                claim,
                business_stage="expense_application",
                tenant_id=tenant_id,
            )
            review_flags = dedupe_claim_risk_flags(
                [*preserved_flags, *list(application_review.get("flags") or [])]
            )
            platform_rule_set_fingerprint = str(
                application_review.get("rule_set_fingerprint") or ""
            )
        else:
            review_result = self._run_ai_submission_review(claim)
            review_flags = list(review_result.get("risk_flags") or [])
            platform_rule_set_fingerprint = str(review_result.get("rule_set_fingerprint") or "")

        business_stage = risk_business_stage_for_claim(
            is_application_claim=is_application_claim,
        )
        decision_payload = build_pre_review_decision(
            claim,
            risk_flags=review_flags,
            business_stage=business_stage,
            platform_rule_set_fingerprint=platform_rule_set_fingerprint,
            reviewed_at=reviewed_at,
        )
        historical_case_evidence = ExpenseClaimHistoricalEvidenceService(self.db).retrieve(
            claim,
            tenant_id=tenant_id,
            business_stage=business_stage,
            findings=list(decision_payload.get("findings") or []),
        )
        pre_review_flag = self._build_ai_pre_review_flag(
            decision_payload=decision_payload,
            business_stage=business_stage,
            historical_case_evidence=historical_case_evidence,
        )
        claim.risk_flags_json = self._replace_ai_pre_review_flag(
            review_flags,
            pre_review_flag,
        )
        if not is_application_claim:
            claim.approval_stage = "\u5f85\u63d0\u4ea4"
        claim.submitted_at = None
        return pre_review_flag

    def _record_pre_review_event(
        self,
        claim: ExpenseClaim,
        *,
        pre_review_flag: dict[str, Any],
        current_user: CurrentUserContext,
        is_application_claim: bool,
        correlation_id: str,
    ):
        review_id = str(pre_review_flag.get("review_id") or "").strip()
        if not review_id:
            raise ValueError("预审结果缺少 review_id。")
        return self._expense_cases.record_claim_event(
            claim,
            event_type=(
                "application_pre_review_completed"
                if is_application_claim
                else "claim_pre_review_completed"
            ),
            actor_id=current_user.username or current_user.name,
            tenant_id=current_user.tenant_id,
            correlation_id=correlation_id,
            idempotency_key=f"pre-review:{review_id}",
            update_case_state=False,
            extra_payload={
                "review_id": review_id,
                "input_fingerprint": str(pre_review_flag.get("input_fingerprint") or ""),
                "rule_set_fingerprint": str(pre_review_flag.get("rule_set_fingerprint") or ""),
                "review_context_fingerprint": str(
                    pre_review_flag.get("review_context_fingerprint") or ""
                ),
                "decision": str(pre_review_flag.get("decision") or ""),
                "review_status": str(pre_review_flag.get("status") or ""),
                "passed": bool(pre_review_flag.get("passed")),
                "blocking_risk_count": int(pre_review_flag.get("blocking_risk_count") or 0),
                "business_stage": str(pre_review_flag.get("business_stage") or ""),
                "message": str(pre_review_flag.get("message") or ""),
            },
        )
