from __future__ import annotations

from dataclasses import dataclass
from time import perf_counter
from typing import Any

from app.core.agent_enums import AgentRunSource, AgentRunStatus, AgentToolType
from app.schemas.agent_asset import AgentAssetListItem, AgentAssetRead
from app.schemas.ontology import OntologyParseResult
from app.schemas.orchestrator import OrchestratorRequest
from app.schemas.user_agent import UserAgentRequest, UserAgentResponse


@dataclass(slots=True)
class ExecutionOutcome:
    status: str
    result: dict[str, Any]
    degraded: bool
    tool_count: int
    failed_tool_count: int


class OrchestratorExecutionEngine:
    def __init__(
        self,
        *,
        run_service,
        expense_claim_service,
        knowledge_service,
        user_agent_service,
        database_query_builder,
    ) -> None:
        self.run_service = run_service
        self.expense_claim_service = expense_claim_service
        self.knowledge_service = knowledge_service
        self.user_agent_service = user_agent_service
        self.database_query_builder = database_query_builder

    def _execute_user_agent(
        self,
        *,
        payload: OrchestratorRequest,
        run_id: str,
        ontology: OntologyParseResult,
        capabilities: dict[str, list[AgentAssetListItem | AgentAssetRead]],
        requires_confirmation: bool,
        context_json: dict[str, Any],
    ) -> ExecutionOutcome:
        selected_capability_codes = self._flatten_capability_codes(capabilities)
        if requires_confirmation:
            response, degraded = self._invoke_tool(
                run_id=run_id,
                tool_type=AgentToolType.LLM.value,
                tool_name="user_agent.confirmation_placeholder",
                request_json={
                    "message": payload.message,
                    "permission_level": ontology.permission.level,
                },
                context_json=context_json,
                executor=lambda: {
                    "confirmation_title": "操作需要确认",
                    "message": f"{ontology.permission.reason} 当前仅返回确认摘要，不直接执行动作。",
                },
                fallback_factory=lambda exc: {
                    "confirmation_title": "操作需要确认",
                    "message": f"确认摘要生成失败，已阻断自动执行：{exc}",
                },
            )
            return ExecutionOutcome(
                status=AgentRunStatus.BLOCKED.value,
                result={**response, "degraded": degraded},
                degraded=degraded,
                tool_count=1,
                failed_tool_count=1 if degraded else 0,
            )

        next_step = self._resolve_next_step(
            ontology,
            payload.source,
            context_json=context_json,
        )
        if next_step == "query_database":
            tool_payload, degraded = self._invoke_tool(
                run_id=run_id,
                tool_type=AgentToolType.DATABASE.value,
                tool_name=self._database_tool_name(ontology.scenario),
                request_json=self._build_ontology_json(ontology),
                context_json=context_json,
                executor=lambda: self.database_query_builder.build_database_answer(
                    ontology,
                    user_id=payload.user_id,
                    context_json=context_json,
                    message=payload.message or "",
                ),
                fallback_factory=lambda exc: {
                    "message": f"数据库查询暂时不可用，已返回降级说明：{exc}",
                    "degraded": True,
                },
            )
            result = self._build_user_agent_result(
                self.user_agent_service.respond(
                    UserAgentRequest(
                        run_id=run_id,
                        user_id=payload.user_id,
                        message=payload.message or "",
                        ontology=ontology,
                        context_json=context_json,
                        tool_payload=tool_payload,
                        selected_capability_codes=selected_capability_codes,
                        degraded=degraded,
                        requires_confirmation=requires_confirmation,
                    )
                ),
                degraded=degraded,
            )
            return ExecutionOutcome(
                status=AgentRunStatus.SUCCEEDED.value,
                result=result,
                degraded=degraded,
                tool_count=1,
                failed_tool_count=1 if degraded else 0,
            )

        if next_step == "search_knowledge":
            tool_payload, degraded = self._invoke_tool(
                run_id=run_id,
                tool_type=AgentToolType.DATABASE.value,
                tool_name="knowledge.search",
                request_json=self._build_ontology_json(ontology),
                context_json=context_json,
                executor=lambda: self._build_knowledge_answer(
                    message=payload.message or "",
                    ontology=ontology,
                    capabilities=capabilities,
                    context_json=context_json,
                ),
                fallback_factory=lambda exc: {
                    "message": f"知识检索暂时不可用，建议稍后重试：{exc}",
                    "degraded": True,
                },
            )
            result = self._build_user_agent_result(
                self.user_agent_service.respond(
                    UserAgentRequest(
                        run_id=run_id,
                        user_id=payload.user_id,
                        message=payload.message or "",
                        ontology=ontology,
                        context_json=context_json,
                        tool_payload=tool_payload,
                        selected_capability_codes=selected_capability_codes,
                        degraded=degraded,
                        requires_confirmation=requires_confirmation,
                    )
                ),
                degraded=degraded,
            )
            return ExecutionOutcome(
                status=AgentRunStatus.SUCCEEDED.value,
                result=result,
                degraded=degraded,
                tool_count=1,
                failed_tool_count=1 if degraded else 0,
            )

        if next_step == "run_rule":
            tool_payload, degraded = self._invoke_tool(
                run_id=run_id,
                tool_type=AgentToolType.RULE_ENGINE.value,
                tool_name=self._rule_tool_name(capabilities),
                request_json=self._build_ontology_json(ontology),
                context_json=context_json,
                executor=lambda: self._build_rule_answer(ontology),
                fallback_factory=lambda exc: {
                    "message": f"规则检查暂时不可用，已返回人工复核建议：{exc}",
                    "degraded": True,
                },
            )
            result = self._build_user_agent_result(
                self.user_agent_service.respond(
                    UserAgentRequest(
                        run_id=run_id,
                        user_id=payload.user_id,
                        message=payload.message or "",
                        ontology=ontology,
                        context_json=context_json,
                        tool_payload=tool_payload,
                        selected_capability_codes=selected_capability_codes,
                        degraded=degraded,
                        requires_confirmation=requires_confirmation,
                    )
                ),
                degraded=degraded,
            )
            return ExecutionOutcome(
                status=AgentRunStatus.SUCCEEDED.value,
                result=result,
                degraded=degraded,
                tool_count=1,
                failed_tool_count=1 if degraded else 0,
            )

        tool_type = AgentToolType.LLM.value
        tool_name = "user_agent.draft_placeholder"
        executor = lambda: {
            "message": (
                f"已生成 {ontology.scenario} 场景草稿，"
                "占位能力后续由 Day 5 User Agent 接管。"
            ),
            "draft_only": True,
        }
        fallback_factory = lambda exc: {
            "message": f"内容整理暂时不可用，请稍后再试：{exc}",
            "degraded": True,
        }

        if ontology.scenario == "expense" or self._is_expense_review_action(context_json):
            is_persistence_action = self._is_expense_persistence_action(context_json)
            tool_type = (
                AgentToolType.DATABASE.value
                if is_persistence_action
                else AgentToolType.LLM.value
            )
            tool_name = (
                "database.expense_claims.save_or_submit"
                if is_persistence_action
                else "user_agent.expense_review_preview"
            )
            executor = lambda: self.expense_claim_service.save_or_submit_from_ontology(
                run_id=run_id,
                user_id=payload.user_id,
                message=payload.message or "",
                ontology=ontology,
                context_json=context_json,
            )
            fallback_factory = lambda exc: {
                "message": (
                    f"报销草稿落库失败，请稍后再试：{exc}"
                    if is_persistence_action
                    else f"报销内容预览生成失败，请稍后再试：{exc}"
                ),
                "degraded": True,
            }

        tool_payload, degraded = self._invoke_tool(
            run_id=run_id,
            tool_type=tool_type,
            tool_name=tool_name,
            request_json=self._build_ontology_json(ontology),
            context_json=context_json,
            executor=executor,
            fallback_factory=fallback_factory,
        )
        result = self._build_user_agent_result(
            self.user_agent_service.respond(
                UserAgentRequest(
                    run_id=run_id,
                    user_id=payload.user_id,
                    message=payload.message or "",
                    ontology=ontology,
                    context_json=context_json,
                    tool_payload=tool_payload,
                    selected_capability_codes=selected_capability_codes,
                    degraded=degraded,
                    requires_confirmation=requires_confirmation,
                )
            ),
            degraded=degraded,
        )
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result=result,
            degraded=degraded,
            tool_count=1,
            failed_tool_count=1 if degraded else 0,
        )

    def _execute_hermes(
        self,
        *,
        payload: OrchestratorRequest,
        run_id: str,
        ontology: OntologyParseResult,
        capabilities: dict[str, list[AgentAssetListItem | AgentAssetRead]],
        requires_confirmation: bool,
        task_asset: AgentAssetRead | None,
        context_json: dict[str, Any],
    ) -> ExecutionOutcome:
        if requires_confirmation:
            return ExecutionOutcome(
                status=AgentRunStatus.BLOCKED.value,
                result={
                    "message": "Hermes 不会自动执行需要确认的高风险动作，已阻断。",
                    "degraded": False,
                },
                degraded=False,
                tool_count=0,
                failed_tool_count=0,
            )

        rule_response, rule_degraded = self._invoke_tool(
            run_id=run_id,
            tool_type=AgentToolType.RULE_ENGINE.value,
            tool_name=self._rule_tool_name(capabilities),
            request_json=self._build_ontology_json(ontology),
            context_json=context_json,
            executor=lambda: self._build_rule_answer(ontology),
            fallback_factory=lambda exc: {
                "message": f"规则巡检失败，已降级为待人工复核：{exc}",
                "degraded": True,
            },
        )
        mcp_response, mcp_degraded = self._invoke_tool(
            run_id=run_id,
            tool_type=AgentToolType.MCP.value,
            tool_name=self._mcp_tool_name(capabilities),
            request_json={
                "task_code": task_asset.code if task_asset is not None else "",
                "scenario": ontology.scenario,
            },
            context_json=context_json,
            executor=lambda: self._build_mcp_answer(task_asset, ontology),
            fallback_factory=lambda exc: {
                "message": f"MCP 调用失败，已使用缓存快照降级：{exc}",
                "fallback": "used_cached_snapshot",
            },
        )
        degraded = rule_degraded or mcp_degraded
        failed_tool_count = int(rule_degraded) + int(mcp_degraded)
        result = {
            "message": self._build_hermes_message(
                task_asset=task_asset,
                ontology=ontology,
                rule_response=rule_response,
                mcp_response=mcp_response,
                degraded=degraded,
            ),
            "report_type": task_asset.code if task_asset is not None else "hermes_runtime",
            "degraded": degraded,
        }
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result=result,
            degraded=degraded,
            tool_count=2,
            failed_tool_count=failed_tool_count,
        )

    @staticmethod
    def _resolve_next_step(
        ontology: OntologyParseResult,
        source: str,
        *,
        context_json: dict[str, Any] | None = None,
    ) -> str:
        if OrchestratorExecutionEngine._is_expense_review_action(context_json or {}):
            return "create_draft"
        if ontology.clarification_required:
            return "ask_clarification"
        if ontology.intent == "draft":
            return "create_draft"
        if ontology.scenario == "knowledge" or ontology.intent == "explain":
            return "search_knowledge"
        if ontology.intent == "risk_check" or source == AgentRunSource.SCHEDULE.value:
            return "run_rule"
        if ontology.intent in {"query", "compare"}:
            return "query_database"
        return "create_draft"

    @staticmethod
    def _is_expense_review_action(context_json: dict[str, Any]) -> bool:
        review_action = str((context_json or {}).get("review_action") or "").strip()
        return review_action in {
            "save_draft",
            "next_step",
            "edit_review",
            "link_to_existing_draft",
            "create_new_claim_from_documents",
        }

    @staticmethod
    def _is_expense_persistence_action(context_json: dict[str, Any]) -> bool:
        review_action = str((context_json or {}).get("review_action") or "").strip()
        return review_action in {
            "save_draft",
            "next_step",
            "link_to_existing_draft",
            "create_new_claim_from_documents",
        }

    @staticmethod
    def _flatten_capability_codes(
        capabilities: dict[str, list[AgentAssetListItem | AgentAssetRead]],
    ) -> list[str]:
        codes: list[str] = []
        for items in capabilities.values():
            for item in items[:2]:
                if item.code not in codes:
                    codes.append(item.code)
        return codes

    def _rank_assets(
        self,
        items: list[AgentAssetListItem],
        ontology: OntologyParseResult,
    ) -> list[AgentAssetListItem]:
        def score(item: AgentAssetListItem) -> tuple[int, str]:
            item_tags = {str(value) for value in item.scenario_json or []}
            weight = 0
            if ontology.scenario in item_tags:
                weight += 3
            if ontology.intent in item_tags:
                weight += 2
            for risk_flag in ontology.risk_flags:
                if risk_flag in item_tags:
                    weight += 4
            return weight, item.code

        ranked = sorted(items, key=score, reverse=True)
        if not ranked:
            return []
        scored = [item for item in ranked if score(item)[0] > 0]
        return scored or ranked[:1]

    def _invoke_tool(
        self,
        *,
        run_id: str,
        tool_type: str,
        tool_name: str,
        request_json: dict[str, Any],
        context_json: dict[str, Any],
        executor,
        fallback_factory,
    ) -> tuple[dict[str, Any], bool]:
        started = perf_counter()
        try:
            self._maybe_raise_simulated_failure(tool_type, context_json)
            response = executor()
            duration_ms = int((perf_counter() - started) * 1000)
            self.run_service.record_tool_call(
                run_id=run_id,
                tool_type=tool_type,
                tool_name=tool_name,
                request_json=request_json,
                response_json=response,
                status="succeeded",
                duration_ms=duration_ms,
            )
            return response, False
        except Exception as exc:
            duration_ms = int((perf_counter() - started) * 1000)
            response = fallback_factory(exc)
            self.run_service.record_tool_call(
                run_id=run_id,
                tool_type=tool_type,
                tool_name=tool_name,
                request_json=request_json,
                response_json=response,
                status="failed",
                duration_ms=duration_ms,
                error_message=str(exc),
            )
            return response, True

    @staticmethod
    def _maybe_raise_simulated_failure(tool_type: str, context_json: dict[str, Any]) -> None:
        expected = str(context_json.get("simulate_tool_failure") or "").strip().lower()
        if not expected:
            return
        if expected == tool_type.lower():
            raise RuntimeError(f"simulated {tool_type} failure")

    @staticmethod
    def _build_user_query_result(
        ontology: OntologyParseResult,
        response: dict[str, Any],
    ) -> dict[str, Any]:
        if ontology.scenario == "expense":
            return {
                "message": (
                    f"已路由到 User Agent，占位查询结果：命中 {response['record_count']} 笔报销，"
                    f"金额合计 {response['total_amount']} 元。"
                ),
                "data": response,
            }
        if ontology.scenario == "accounts_receivable":
            return {
                "message": (
                    f"已路由到 User Agent，占位查询结果：命中 {response['record_count']} 条应收，"
                    f"未回款金额 {response['outstanding_amount']} 元。"
                ),
                "data": response,
            }
        return {
            "message": (
                f"已路由到 User Agent，占位查询结果：命中 {response['record_count']} 条应付，"
                f"待付金额 {response['outstanding_amount']} 元。"
            ),
            "data": response,
        }

    @staticmethod
    def _build_user_agent_result(
        response: UserAgentResponse,
        *,
        degraded: bool,
    ) -> dict[str, Any]:
        result = {
            "message": response.answer,
            "answer": response.answer,
            "citations": [item.model_dump() for item in response.citations],
            "suggested_actions": [item.model_dump() for item in response.suggested_actions],
            "risk_flags": response.risk_flags,
            "requires_confirmation": response.requires_confirmation,
            "degraded": degraded,
        }
        if response.query_payload is not None:
            result["query_payload"] = response.query_payload.model_dump()
        if response.draft_payload is not None:
            result["draft_payload"] = response.draft_payload.model_dump()
        if response.review_payload is not None:
            result["review_payload"] = response.review_payload.model_dump()
        return result

    def _build_knowledge_answer(
        self,
        *,
        message: str,
        ontology: OntologyParseResult,
        capabilities: dict[str, list[AgentAssetListItem | AgentAssetRead]],
        context_json: dict[str, Any],
    ) -> dict[str, Any]:
        del ontology, capabilities
        conversation_history = context_json.get("conversation_history")
        if not isinstance(conversation_history, list):
            conversation_history = None
        payload = self.knowledge_service.search_knowledge(
            message,
            conversation_history=conversation_history,
            limit=8,
        )
        references = [str(item).strip() for item in list(payload.get("references") or []) if str(item).strip()]
        if references:
            payload["references"] = references
        return payload

    @staticmethod
    def _build_rule_answer(ontology: OntologyParseResult) -> dict[str, Any]:
        risk_text = (
            "、".join(ontology.risk_flags)
            if ontology.risk_flags
            else "未识别到明确风险标签"
        )
        return {
            "message": f"已完成占位规则检查，风险标签：{risk_text}。",
            "risk_flags": ontology.risk_flags,
        }

    @staticmethod
    def _build_mcp_answer(
        task_asset: AgentAssetRead | None,
        ontology: OntologyParseResult,
    ) -> dict[str, Any]:
        return {
            "message": (
                f"已调用占位 MCP 快照，任务={task_asset.code if task_asset else 'none'}，"
                f"scenario={ontology.scenario}。"
            ),
            "snapshot": "stubbed",
        }

    @staticmethod
    def _build_hermes_message(
        *,
        task_asset: AgentAssetRead | None,
        ontology: OntologyParseResult,
        rule_response: dict[str, Any],
        mcp_response: dict[str, Any],
        degraded: bool,
    ) -> str:
        task_code = task_asset.code if task_asset is not None else "task.unspecified"
        suffix = "，其中部分能力已降级。" if degraded else "。"
        return (
            f"Hermes 占位执行完成：任务 {task_code}，"
            f"场景 {ontology.scenario}，规则结果={rule_response.get('message', '')}，"
            f"MCP 结果={mcp_response.get('message', '')}{suffix}"
        )

    @staticmethod
    def _database_tool_name(scenario: str) -> str:
        if scenario == "expense":
            return "database.expense_claims.lookup"
        if scenario == "accounts_receivable":
            return "database.accounts_receivable.lookup"
        return "database.accounts_payable.lookup"

    @staticmethod
    def _rule_tool_name(
        capabilities: dict[str, list[AgentAssetListItem | AgentAssetRead]],
    ) -> str:
        if capabilities["rules"]:
            return capabilities["rules"][0].code
        return "rule_engine.default_risk_check"

    @staticmethod
    def _mcp_tool_name(
        capabilities: dict[str, list[AgentAssetListItem | AgentAssetRead]],
    ) -> str:
        if capabilities["mcps"]:
            return capabilities["mcps"][0].code
        return "mcp.default_snapshot"

    @staticmethod
    def _build_ontology_json(ontology: OntologyParseResult) -> dict[str, Any]:
        return {
            "scenario": ontology.scenario,
            "intent": ontology.intent,
            "entities": [item.model_dump() for item in ontology.entities],
            "time_range": ontology.time_range.model_dump(),
            "metrics": [item.model_dump() for item in ontology.metrics],
            "constraints": [item.model_dump() for item in ontology.constraints],
            "risk_flags": ontology.risk_flags,
            "permission": ontology.permission.model_dump(),
        }

