from __future__ import annotations

import hashlib
import json
import uuid
from dataclasses import dataclass
from typing import Any

from sqlalchemy import and_, or_, select
from sqlalchemy.orm import Session, aliased

from app.models.ai_learning import AIDecision, AIDecisionFeedback, WorkflowOutcome
from app.models.expense_case import BusinessEvent, ExpenseCase, ExpenseCaseLink

SUBMISSION_EVENT_TYPES = {"application_submitted", "claim_submitted"}
AUDIT_EVENT_TYPES = {
    "application_pre_review_completed",
    "claim_pre_review_completed",
    "audit_conclusion_recorded",
}
SUPPORTED_EVENT_TYPES = {
    *AUDIT_EVENT_TYPES,
    "application_returned",
    "claim_returned",
    "application_approved",
    "claim_approved",
    "approval_stage_completed",
    "approval_overridden",
    "payment_completed",
}


@dataclass(frozen=True, slots=True)
class WorkflowLearningSpec:
    outcome_type: str
    feedback_type: str | None = None
    verification_status: str = "server_verified"
    training_eligible: bool = False


@dataclass(frozen=True, slots=True)
class WorkflowLearningRecords:
    outcome: WorkflowOutcome
    feedback: AIDecisionFeedback | None


class ExpenseWorkflowLearningService:
    """把服务端费用事件转成可追溯、可幂等复放的 AI 学习证据。"""

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

    def record_event(self, event: BusinessEvent) -> list[WorkflowLearningRecords]:
        """只接受数据库中真实存在且租户、Case、单据关联一致的业务事件。"""

        if str(event.event_type or "").strip() not in SUPPORTED_EVENT_TYPES:
            return []
        trusted_event = self._trusted_event(event)
        records = [
            self._record_for_decision(trusted_event, decision)
            for decision in self._eligible_decisions(trusted_event)
        ]
        self.db.flush()
        return records

    def record_prior_events_for_decision(
        self,
        decision: AIDecision,
    ) -> list[WorkflowLearningRecords]:
        """补录提交决策创建前、同一请求内已落库的预审结论。"""

        trusted_decision = self.db.scalar(
            select(AIDecision).where(
                AIDecision.id == decision.id,
                AIDecision.tenant_id == decision.tenant_id,
                AIDecision.expense_case_id == decision.expense_case_id,
            )
        )
        if trusted_decision is None:
            raise PermissionError("AI 决策不存在或不属于指定租户与费用 Case。")
        anchor = self._decision_anchor(trusted_decision)
        if anchor is None:
            return []

        events = list(
            self.db.scalars(
                select(BusinessEvent)
                .where(
                    BusinessEvent.tenant_id == trusted_decision.tenant_id,
                    BusinessEvent.expense_case_id == trusted_decision.expense_case_id,
                    BusinessEvent.aggregate_type == "expense_claim",
                    BusinessEvent.aggregate_id == anchor.aggregate_id,
                    BusinessEvent.event_type.in_(AUDIT_EVENT_TYPES),
                    BusinessEvent.correlation_id == anchor.correlation_id,
                )
                .order_by(BusinessEvent.occurred_at.asc(), BusinessEvent.id.asc())
            ).all()
        )
        records = [
            self._record_for_decision(self._trusted_event(event), trusted_decision)
            for event in events
        ]
        self.db.flush()
        return records

    def _eligible_decisions(self, event: BusinessEvent) -> list[AIDecision]:
        anchor = aliased(BusinessEvent)
        return list(
            self.db.scalars(
                select(AIDecision)
                .join(
                    anchor,
                    and_(
                        anchor.tenant_id == AIDecision.tenant_id,
                        anchor.expense_case_id == AIDecision.expense_case_id,
                        anchor.id == AIDecision.business_event_id,
                    ),
                )
                .where(
                    AIDecision.tenant_id == event.tenant_id,
                    AIDecision.expense_case_id == event.expense_case_id,
                    AIDecision.subject_type == "expense_claim",
                    AIDecision.status.in_(["accepted", "edited", "executed"]),
                    anchor.event_type.in_(SUBMISSION_EVENT_TYPES),
                    or_(
                        anchor.occurred_at <= event.occurred_at,
                        and_(
                            event.event_type in AUDIT_EVENT_TYPES,
                            anchor.correlation_id == event.correlation_id,
                        ),
                    ),
                )
                .order_by(
                    anchor.occurred_at.desc(),
                    AIDecision.created_at.desc(),
                    AIDecision.id.desc(),
                )
                .limit(1)
            ).all()
        )

    def _trusted_event(self, event: BusinessEvent) -> BusinessEvent:
        trusted = self.db.scalar(
            select(BusinessEvent).where(
                BusinessEvent.id == event.id,
                BusinessEvent.tenant_id == event.tenant_id,
                BusinessEvent.expense_case_id == event.expense_case_id,
            )
        )
        if trusted is None:
            raise PermissionError("业务事件不存在或不属于指定租户与费用 Case。")
        if any(
            trusted_value != supplied_value
            for trusted_value, supplied_value in (
                (trusted.aggregate_type, event.aggregate_type),
                (trusted.aggregate_id, event.aggregate_id),
                (trusted.event_type, event.event_type),
                (trusted.idempotency_key, event.idempotency_key),
            )
        ):
            raise PermissionError("业务事件身份与数据库可信事实不一致。")
        if trusted.aggregate_type != "expense_claim":
            raise ValueError("只有费用单据业务事件可以形成 AI 工作流学习证据。")

        expense_case = self.db.scalar(
            select(ExpenseCase).where(
                ExpenseCase.id == trusted.expense_case_id,
                ExpenseCase.tenant_id == trusted.tenant_id,
            )
        )
        claim_link = self.db.scalar(
            select(ExpenseCaseLink).where(
                ExpenseCaseLink.tenant_id == trusted.tenant_id,
                ExpenseCaseLink.expense_case_id == trusted.expense_case_id,
                ExpenseCaseLink.resource_type == "expense_claim",
                ExpenseCaseLink.resource_id == trusted.aggregate_id,
            )
        )
        if expense_case is None or claim_link is None:
            raise PermissionError("业务事件缺少同租户费用 Case 与单据关联。")
        return trusted

    def _decision_anchor(self, decision: AIDecision) -> BusinessEvent | None:
        if not decision.business_event_id:
            return None
        return self.db.scalar(
            select(BusinessEvent).where(
                BusinessEvent.id == decision.business_event_id,
                BusinessEvent.tenant_id == decision.tenant_id,
                BusinessEvent.expense_case_id == decision.expense_case_id,
                BusinessEvent.event_type.in_(SUBMISSION_EVENT_TYPES),
            )
        )

    def _record_for_decision(
        self,
        event: BusinessEvent,
        decision: AIDecision,
    ) -> WorkflowLearningRecords:
        if (
            decision.tenant_id != event.tenant_id
            or decision.expense_case_id != event.expense_case_id
        ):
            raise PermissionError("工作流结果不能关联其他租户或费用 Case 的 AI 决策。")
        spec = self._event_spec(event)
        safe_result = self._safe_result(event, spec)
        outcome_key = f"workflow-outcome:{decision.id}:{event.id}"
        outcome = self.db.scalar(
            select(WorkflowOutcome).where(
                WorkflowOutcome.tenant_id == event.tenant_id,
                WorkflowOutcome.idempotency_key == outcome_key,
            )
        )
        if outcome is None:
            outcome = WorkflowOutcome(
                id=self._stable_id("workflow-outcome", event.tenant_id, decision.id, event.id),
                tenant_id=event.tenant_id,
                expense_case_id=event.expense_case_id,
                decision_id=decision.id,
                business_event_id=event.id,
                expense_claim_id=decision.expense_claim_id,
                correlation_id=event.correlation_id,
                outcome_type=spec.outcome_type,
                outcome_status="verified",
                actor_id=str(event.actor_id or "system").strip() or "system",
                actor_type=str(event.actor_type or "system").strip() or "system",
                result_json=safe_result,
                idempotency_key=outcome_key,
                content_fingerprint=self._fingerprint(
                    {
                        "decision_id": decision.id,
                        "business_event_id": event.id,
                        "outcome_type": spec.outcome_type,
                        "result": safe_result,
                    }
                ),
                effective_at=event.occurred_at,
            )
            self.db.add(outcome)
        else:
            self._validate_outcome(outcome, event=event, decision=decision, spec=spec)

        training_eligible = spec.training_eligible and self._is_server_verified_decision(
            decision
        )
        feedback = self._feedback(
            event,
            decision,
            spec,
            safe_result,
            training_eligible=training_eligible,
        )
        if feedback is not None and training_eligible:
            decision.training_eligible = True
        return WorkflowLearningRecords(outcome=outcome, feedback=feedback)

    def _feedback(
        self,
        event: BusinessEvent,
        decision: AIDecision,
        spec: WorkflowLearningSpec,
        safe_result: dict[str, Any],
        *,
        training_eligible: bool,
    ) -> AIDecisionFeedback | None:
        if spec.feedback_type is None:
            return None
        feedback_key = f"workflow-feedback:{decision.id}:{event.id}"
        feedback = self.db.scalar(
            select(AIDecisionFeedback).where(
                AIDecisionFeedback.tenant_id == event.tenant_id,
                AIDecisionFeedback.idempotency_key == feedback_key,
            )
        )
        if feedback is not None:
            if (
                feedback.decision_id != decision.id
                or feedback.feedback_type != spec.feedback_type
                or feedback.verification_status != spec.verification_status
                or bool(feedback.training_eligible) != training_eligible
            ):
                raise RuntimeError("AI 工作流反馈幂等状态与可信事件不一致。")
            return feedback

        feedback = AIDecisionFeedback(
            id=self._stable_id("workflow-feedback", event.tenant_id, decision.id, event.id),
            tenant_id=event.tenant_id,
            decision_id=decision.id,
            expense_claim_id=decision.expense_claim_id,
            correlation_id=event.correlation_id,
            feedback_type=spec.feedback_type,
            action_type=spec.outcome_type[:30],
            actor_id=str(event.actor_id or "system").strip() or "system",
            actor_type=str(event.actor_type or "system").strip() or "system",
            evidence_source="server_business_event",
            verification_status=spec.verification_status,
            training_eligible=training_eligible,
            final_value_json={
                "source_event_id": event.id,
                "outcome_type": spec.outcome_type,
                "evidence_fingerprint": safe_result["evidence_fingerprint"],
            },
            changed_fields_json=[],
            idempotency_key=feedback_key,
            content_fingerprint=self._fingerprint(
                {
                    "decision_id": decision.id,
                    "business_event_id": event.id,
                    "feedback_type": spec.feedback_type,
                    "outcome_type": spec.outcome_type,
                }
            ),
            created_at=event.occurred_at,
        )
        self.db.add(feedback)
        return feedback

    @staticmethod
    def _event_spec(event: BusinessEvent) -> WorkflowLearningSpec:
        event_type = str(event.event_type or "").strip()
        payload = event.payload_json if isinstance(event.payload_json, dict) else {}
        if event_type in {"application_returned", "claim_returned"}:
            return WorkflowLearningSpec(
                "workflow_returned",
                "rejected",
                "human_verified",
                True,
            )
        if event_type in {
            "application_approved",
            "claim_approved",
            "approval_stage_completed",
            "approval_overridden",
        }:
            overridden = event_type == "approval_overridden" or (
                payload.get("ai_decision_overridden") is True
            )
            return WorkflowLearningSpec(
                "approval_overridden" if overridden else "approval_passed",
                "rejected" if overridden else "accepted",
                "human_verified",
                True,
            )
        if event_type == "payment_completed":
            return WorkflowLearningSpec("payment_completed")
        if event_type in AUDIT_EVENT_TYPES:
            audit_decision = str(
                payload.get("audit_decision") or payload.get("decision") or ""
            ).strip().lower()
            if audit_decision in {
                "ready",
                "pass",
                "cleared",
                "confirm",
                "resolve",
                "resolved",
            }:
                return WorkflowLearningSpec(
                    "audit_cleared" if audit_decision != "confirm" else "audit_confirmed",
                    "accepted",
                    "server_verified",
                    True,
                )
            if audit_decision in {"needs_fix", "blocked", "flagged"}:
                return WorkflowLearningSpec(
                    "audit_flagged",
                    "rejected",
                    "server_verified",
                    True,
                )
            if audit_decision == "false_positive":
                return WorkflowLearningSpec(
                    "audit_false_positive",
                    "rejected",
                    "human_verified",
                    True,
                )
            return WorkflowLearningSpec("audit_conclusion_recorded")
        raise ValueError("该业务事件不属于可学习的费用工作流结果。")

    @classmethod
    def _safe_result(
        cls,
        event: BusinessEvent,
        spec: WorkflowLearningSpec,
    ) -> dict[str, Any]:
        payload = event.payload_json if isinstance(event.payload_json, dict) else {}
        audit_decision = str(
            payload.get("audit_decision") or payload.get("decision") or ""
        ).strip().lower()
        if audit_decision not in {
            "ready",
            "pass",
            "cleared",
            "confirm",
            "resolve",
            "resolved",
            "needs_fix",
            "blocked",
            "flagged",
            "false_positive",
        }:
            audit_decision = ""
        evidence = {
            "source_event_id": event.id,
            "source_event_type": event.event_type,
            "source_event_version": int(event.event_version or 1),
            "outcome_type": spec.outcome_type,
            "next_status": cls._safe_state(payload.get("next_status")),
            "next_approval_stage": cls._safe_state(payload.get("next_approval_stage")),
            "audit_decision": audit_decision,
            "ai_decision_overridden": payload.get("ai_decision_overridden") is True,
        }
        return {
            **evidence,
            "evidence_source": "server_business_event",
            "evidence_fingerprint": cls._fingerprint(evidence),
        }

    @staticmethod
    def _safe_state(value: object) -> str:
        return str(value or "").strip()[:50]

    @staticmethod
    def _is_server_verified_decision(decision: AIDecision) -> bool:
        evidence = decision.evidence_json if isinstance(decision.evidence_json, dict) else {}
        return str(evidence.get("trust_level") or "").strip() in {
            "server_snapshot_verified",
            "server_rule_verified",
            "server_model_verified",
        }

    @staticmethod
    def _validate_outcome(
        outcome: WorkflowOutcome,
        *,
        event: BusinessEvent,
        decision: AIDecision,
        spec: WorkflowLearningSpec,
    ) -> None:
        if (
            outcome.decision_id != decision.id
            or outcome.business_event_id != event.id
            or outcome.expense_case_id != event.expense_case_id
            or outcome.outcome_type != spec.outcome_type
            or outcome.outcome_status != "verified"
        ):
            raise RuntimeError("AI 工作流结果幂等状态与可信事件不一致。")

    @staticmethod
    def _stable_id(kind: str, *parts: object) -> str:
        value = ":".join([kind, *(str(part) for part in parts)])
        return str(uuid.uuid5(uuid.NAMESPACE_URL, value))

    @staticmethod
    def _fingerprint(payload: dict[str, Any]) -> str:
        serialized = json.dumps(
            payload,
            ensure_ascii=False,
            sort_keys=True,
            separators=(",", ":"),
            default=str,
        )
        return f"sha256:{hashlib.sha256(serialized.encode('utf-8')).hexdigest()}"
