from __future__ import annotations

import re
from typing import Any

from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
from app.core.agent_enums import AgentRunStatus
from app.schemas.expense_application_preview import ExpenseApplicationPreviewDecisionCreate
from app.schemas.ontology import OntologyParseResult
from app.schemas.orchestrator import OrchestratorRequest
from app.schemas.reimbursement import ExpenseApplicationPreviewActionPayload
from app.schemas.user_agent import UserAgentRequest
from app.services.expense_application_memory import ExpenseApplicationMemoryService
from app.services.expense_application_preview_decisions import PreviewDecisionConflictError
from app.services.expense_application_preview_workflow import ExpenseApplicationPreviewWorkflow
from app.services.expense_application_snapshot import (
    canonical_preview_fields,
    content_fingerprint,
    preview_response_fields,
    safe_fact_snapshot,
)
from app.services.orchestrator_execution import ExecutionOutcome, OrchestratorExecutionEngine
from app.services.user_agent import UserAgentService
from app.services.user_agent_application import (
    APPLICATION_SAVE_DRAFT_KEYWORDS,
    APPLICATION_SHORT_CONFIRMATIONS,
    APPLICATION_SUBMIT_KEYWORDS,
)


class OrchestratorExpenseApplicationWorkflow:
    """为通用 Orchestrator 提供认证的申请预览签发、消费和会话状态编排。"""

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

    def execute(
        self,
        *,
        payload: OrchestratorRequest,
        current_user: CurrentUserContext,
        run_id: str,
        conversation_id: str | None,
        ontology: OntologyParseResult,
        context_json: dict[str, Any],
        selected_capability_codes: list[str],
    ) -> ExecutionOutcome | None:
        application_context = dict(context_json or {})
        decision_state = self._resolve_decision_state(application_context)
        # 申请事实只允许来自服务端会话历史、当前输入和登录态，不能反向信任客户端 preview。
        application_context.pop("application_preview", None)
        application_context.pop("application_preview_decision", None)
        request = UserAgentRequest(
            run_id=run_id,
            user_id=current_user.username,
            message=payload.message or "",
            ontology=ontology,
            context_json=application_context,
            tool_payload={},
            selected_capability_codes=selected_capability_codes,
            degraded=False,
            requires_confirmation=False,
        )
        facts = self.user_agent_service._resolve_expense_application_facts(request)
        memory_applications = ExpenseApplicationMemoryService(
            self.db
        ).apply_active_transport_memory(facts, current_user)
        step = self.user_agent_service._resolve_expense_application_step(request, facts)
        requested_action = self._resolve_requested_action(payload.message, decision_state)
        if requested_action:
            decision_preview = decision_state.get("application_preview")
            if isinstance(decision_preview, dict):
                for key, value in canonical_preview_fields(decision_preview).items():
                    if not str(facts.get(key) or "").strip():
                        facts[key] = value
            step = "draft" if requested_action == "save_draft" else "submitted"
        if step not in {"preview", "draft", "submitted"}:
            return None

        current_preview = {"fields": preview_response_fields(facts)}
        if step == "preview":
            return self._issue_or_refresh_preview(
                request=request,
                facts=facts,
                decision_state=decision_state,
                current_preview=current_preview,
                memory_applications=memory_applications,
                current_user=current_user,
                conversation_id=conversation_id,
                context_json=context_json,
            )
        return self._execute_action(
            payload=payload,
            current_user=current_user,
            conversation_id=conversation_id,
            application_context=application_context,
            context_json=context_json,
            decision_state=decision_state,
            current_preview=current_preview,
            facts=facts,
            step=step,
        )

    def _issue_or_refresh_preview(
        self,
        *,
        request: UserAgentRequest,
        facts: dict[str, Any],
        decision_state: dict[str, Any],
        current_preview: dict[str, Any],
        memory_applications: list[Any],
        current_user: CurrentUserContext,
        conversation_id: str | None,
        context_json: dict[str, Any],
    ) -> ExecutionOutcome:
        resolved_request = request.model_copy(
            update={"message": self._build_facts_message(facts)}
        )
        preview_response = self.user_agent_service._build_expense_application_response(
            resolved_request,
            risk_flags=[],
        )
        result = OrchestratorExecutionEngine._build_user_agent_result(
            preview_response,
            degraded=False,
        )
        if self._is_issued_state(decision_state):
            issued_preview = {
                **current_preview,
                "decisionId": str(decision_state.get("decision_id") or "").strip(),
                "decisionSource": str(decision_state.get("decision_source") or "").strip(),
                "decisionExpiresAt": str(decision_state.get("expires_at") or "").strip(),
            }
            decision_id = issued_preview["decisionId"]
            decision_source = issued_preview["decisionSource"]
            expires_at = issued_preview["decisionExpiresAt"]
        else:
            issue_response = ExpenseApplicationPreviewWorkflow(self.db).issue(
                ExpenseApplicationPreviewDecisionCreate(
                    message=self._build_facts_message(facts),
                    conversation_id=conversation_id,
                    request_id=self._build_issue_request_id(conversation_id, facts),
                ),
                current_user,
            )
            issued_preview = dict(issue_response.application_preview or {})
            decision_id = issue_response.decision_id
            decision_source = issue_response.decision_source
            expires_at = issue_response.expires_at.isoformat()
        if memory_applications:
            issued_preview["memoryApplications"] = [
                item.model_dump(mode="json") for item in memory_applications
            ]

        context_json["application_preview_decision"] = {
            "status": "issued",
            "decision_id": decision_id,
            "decision_source": decision_source,
            "expires_at": expires_at,
            "application_preview": issued_preview,
        }
        result.update(
            {
                "application_preview": issued_preview,
                "decision_id": decision_id,
                "decision_source": decision_source,
                "decision_expires_at": expires_at,
            }
        )
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result=result,
            degraded=False,
            tool_count=0,
            failed_tool_count=0,
        )

    def _execute_action(
        self,
        *,
        payload: OrchestratorRequest,
        current_user: CurrentUserContext,
        conversation_id: str | None,
        application_context: dict[str, Any],
        context_json: dict[str, Any],
        decision_state: dict[str, Any],
        current_preview: dict[str, Any],
        facts: dict[str, Any],
        step: str,
    ) -> ExecutionOutcome:
        action_type = "save_draft" if step == "draft" else "submit"
        decision_id = str(decision_state.get("decision_id") or "").strip()
        if not self._is_issued_state(decision_state) or not decision_id:
            context_json["application_preview_decision"] = {"status": "missing"}
            return self._build_blocked_outcome(
                "当前申请还没有经过服务端核对，请重新生成申请预览后再继续。"
            )

        action_preview = {
            **current_preview,
            "decisionId": decision_id,
            "decisionSource": str(decision_state.get("decision_source") or "").strip(),
            "decisionExpiresAt": str(decision_state.get("expires_at") or "").strip(),
        }
        action_context = {**application_context, "application_preview": action_preview}
        if action_type == "save_draft":
            action_context["application_action"] = "save_draft"
            action_context["application_save_mode"] = True

        try:
            action_response = ExpenseApplicationPreviewWorkflow(self.db).execute(
                ExpenseApplicationPreviewActionPayload(
                    source=payload.source,
                    user_id=current_user.username,
                    conversation_id=conversation_id,
                    action_type=action_type,
                    decision_id=decision_id,
                    request_id=self._build_action_request_id(decision_id, action_type),
                    message=self._build_action_message(action_type, facts),
                    context_json=action_context,
                ),
                current_user,
            )
        except (PreviewDecisionConflictError, ValueError) as error:
            context_json["application_preview_decision"] = {
                "status": "invalid",
                "reason": str(error),
            }
            return self._build_blocked_outcome(str(error))

        result = action_response.result.model_dump(mode="json")
        result["degraded"] = False
        next_decision_id = str(action_response.result.decision_id or "").strip()
        if next_decision_id:
            self._store_next_decision(
                context_json=context_json,
                action_preview=action_preview,
                decision_id=next_decision_id,
                expires_at=action_response.result.decision_expires_at,
                result=result,
            )
        else:
            context_json["application_preview_decision"] = {
                "status": "consumed",
                "decision_id": decision_id,
                "action_type": action_type,
            }
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result=result,
            degraded=False,
            tool_count=0,
            failed_tool_count=0,
        )

    @staticmethod
    def _store_next_decision(
        *,
        context_json: dict[str, Any],
        action_preview: dict[str, Any],
        decision_id: str,
        expires_at: Any,
        result: dict[str, Any],
    ) -> None:
        next_preview = {
            **action_preview,
            "decisionId": decision_id,
            "decisionSource": "server_draft",
            "decisionExpiresAt": expires_at.isoformat() if expires_at else "",
        }
        context_json["application_preview_decision"] = {
            "status": "issued",
            "decision_id": decision_id,
            "decision_source": "server_draft",
            "expires_at": next_preview["decisionExpiresAt"],
            "application_preview": next_preview,
        }
        result["application_preview"] = next_preview

    @staticmethod
    def _resolve_decision_state(context_json: dict[str, Any]) -> dict[str, Any]:
        state = context_json.get("application_preview_decision")
        if isinstance(state, dict):
            return dict(state)
        conversation_state = context_json.get("conversation_state")
        if isinstance(conversation_state, dict):
            state = conversation_state.get("application_preview_decision")
            if isinstance(state, dict):
                return dict(state)
        return {}

    @staticmethod
    def _is_issued_state(state: dict[str, Any]) -> bool:
        return (
            str(state.get("status") or "").strip() == "issued"
            and bool(str(state.get("decision_id") or "").strip())
        )

    @classmethod
    def _resolve_requested_action(
        cls,
        message: str | None,
        decision_state: dict[str, Any],
    ) -> str:
        if not cls._is_issued_state(decision_state):
            return ""
        compact_message = re.sub(r"\s+", "", str(message or ""))
        if any(keyword in compact_message for keyword in APPLICATION_SAVE_DRAFT_KEYWORDS):
            return "save_draft"
        if (
            any(keyword in compact_message for keyword in APPLICATION_SUBMIT_KEYWORDS)
            or compact_message in APPLICATION_SHORT_CONFIRMATIONS
        ):
            return "submit"
        return ""

    @staticmethod
    def _build_facts_message(facts: dict[str, Any]) -> str:
        labels = (
            ("application_type", "申请类型"),
            ("time", "申请时间"),
            ("location", "地点"),
            ("reason", "事由"),
            ("days", "天数"),
            ("transport_mode", "出行方式"),
            ("amount", "系统预估费用"),
        )
        return "\n".join(
            f"{label}：{value}"
            for key, label in labels
            if (value := str(facts.get(key) or "").strip())
        )

    @classmethod
    def _build_action_message(cls, action_type: str, facts: dict[str, Any]) -> str:
        action_text = "保存草稿" if action_type == "save_draft" else "确认提交"
        return f"{cls._build_facts_message(facts)}\n\n{action_text}"

    @staticmethod
    def _build_issue_request_id(
        conversation_id: str | None,
        facts: dict[str, Any],
    ) -> str:
        fingerprint = content_fingerprint(safe_fact_snapshot(facts)).split(":", 1)[-1][:24]
        conversation_key = str(conversation_id or "new").strip() or "new"
        return f"orchestrator-preview:{conversation_key}:{fingerprint}"[:120]

    @staticmethod
    def _build_action_request_id(decision_id: str, action_type: str) -> str:
        return f"orchestrator-action:{decision_id}:{action_type}"[:120]

    @staticmethod
    def _build_blocked_outcome(message: str) -> ExecutionOutcome:
        normalized_message = str(message or "").strip() or "申请预览状态已失效，请重新生成。"
        return ExecutionOutcome(
            status=AgentRunStatus.BLOCKED.value,
            result={
                "message": normalized_message,
                "answer": normalized_message,
                "suggested_actions": [],
                "risk_flags": [],
                "requires_confirmation": True,
                "degraded": False,
            },
            degraded=False,
            tool_count=0,
            failed_tool_count=0,
        )
