"""Orchestrator 的工具选择、商业预检与调用记录职责。"""

from __future__ import annotations

import uuid
from time import perf_counter
from typing import Any

from app.core.agent_enums import AgentRunSource
from app.schemas.agent_asset import AgentAssetListItem, AgentAssetRead
from app.schemas.ontology import OntologyParseResult
from app.services.commercial_access_policy import CommercialConflictError
from app.services.commercial_runtime_bridge import CommercialRuntimeBridge


class OrchestratorToolExecutionMixin:
    @staticmethod
    def _resolve_task_type(task_asset: AgentAssetRead | None) -> str:
        if task_asset is None:
            return ""
        config = task_asset.config_json or {}
        task_type = str(config.get("task_type") or "").strip()
        if task_type:
            return task_type.replace("-", "_").replace(".", "_")
        return str(task_asset.code or "").removeprefix("task.hermes.").replace(".", "_")

    @staticmethod
    def _resolve_next_step(
        ontology: OntologyParseResult,
        source: str,
        *,
        context_json: dict[str, Any] | None = None,
    ) -> str:
        if OrchestratorToolExecutionMixin._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]:
        tool_call_id = str(uuid.uuid4())
        permit = CommercialRuntimeBridge(self.db).reserve_tool(
            run_id,
            tool_call_id=tool_call_id,
            tool_type=tool_type,
            tool_name=tool_name,
        )
        gate = permit.gate
        if not gate.allowed:
            error = CommercialConflictError(gate.reason)
            response = fallback_factory(error)
            self.run_service.record_tool_call(
                run_id=run_id,
                tool_call_id=tool_call_id,
                tool_type=tool_type,
                tool_name=tool_name,
                request_json=request_json,
                response_json=response,
                status="blocked",
                duration_ms=0,
                error_message=str(error),
            )
            if self.trace_service:
                self.trace_service.record_tool_event_safe(
                    run_id,
                    tool_type,
                    tool_name,
                    request_json,
                    response,
                    "blocked",
                    0,
                    context_json,
                    str(error),
                )
            return response, True
        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_call_id=tool_call_id,
                tool_type=tool_type,
                tool_name=tool_name,
                request_json=request_json,
                response_json=response,
                status="succeeded",
                duration_ms=duration_ms,
            )
            if self.trace_service:
                self.trace_service.record_tool_event_safe(
                    run_id,
                    tool_type,
                    tool_name,
                    request_json,
                    response,
                    "succeeded",
                    duration_ms,
                    context_json,
                )
            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_call_id=tool_call_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),
            )
            if self.trace_service:
                self.trace_service.record_tool_event_safe(
                    run_id,
                    tool_type,
                    tool_name,
                    request_json,
                    response,
                    "failed",
                    duration_ms,
                    context_json,
                    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 expected and expected == tool_type.lower():
            raise RuntimeError(f"simulated {tool_type} failure")
