from __future__ import annotations

from dataclasses import asdict, dataclass
from typing import Any

from sqlalchemy.orm import Session

from app.api.deps import CurrentUserContext
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
from app.services.digital_employee_finance_report_task import (
    DigitalEmployeeFinanceReportTaskService,
)
from app.services.hermes_employee_profile_scanner import HermesEmployeeProfileScannerService
from app.services.hermes_risk_clue_collector import HermesRiskClueCollectorService
from app.services.hermes_risk_scanner import HermesRiskScannerService
from app.services.knowledge import KnowledgeService
from app.services.knowledge_sync import KnowledgeSyncDispatchService
from app.services.knowledge_tenant_scope import require_knowledge_tenant_id
from app.services.orchestrator_tool_execution import OrchestratorToolExecutionMixin


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


class OrchestratorExecutionTaskMixin:
    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,
            )

        digital_employee_outcome = self._execute_digital_employee_task(
            payload=payload,
            run_id=run_id,
            task_asset=task_asset,
            context_json=context_json,
        )
        if digital_employee_outcome is not None:
            return digital_employee_outcome

        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,
        )

    def _execute_digital_employee_task(
        self,
        *,
        payload: OrchestratorRequest,
        run_id: str,
        task_asset: AgentAssetRead | None,
        context_json: dict[str, Any],
    ) -> ExecutionOutcome | None:
        task_type = self._resolve_task_type(task_asset)
        tenant_id = self._trusted_run_tenant_id(run_id)
        if task_type == "global_risk_scan":
            return self._execute_risk_graph_scan(
                run_id=run_id,
                context_json=context_json,
                tenant_id=tenant_id,
            )
        if task_type == "employee_behavior_profile_scan":
            return self._execute_employee_profile_scan(
                run_id=run_id,
                context_json=context_json,
                tenant_id=tenant_id,
            )
        if task_type == "finance_policy_knowledge_organize":
            return self._execute_finance_policy_knowledge_sync(
                payload=payload,
                run_id=run_id,
                task_asset=task_asset,
                context_json=context_json,
                tenant_id=tenant_id,
            )
        if task_type == "risk_clue_collect":
            return self._execute_risk_clue_collect(
                run_id=run_id,
                context_json=context_json,
                tenant_id=tenant_id,
            )
        if task_type == "finance_report_orchestration":
            return self._execute_finance_report_orchestration(
                run_id=run_id,
                context_json=context_json,
                tenant_id=tenant_id,
            )
        return None

    def _execute_risk_graph_scan(
        self,
        *,
        run_id: str,
        context_json: dict[str, Any],
        tenant_id: str,
    ) -> ExecutionOutcome:
        summary, degraded = self._invoke_tool(
            run_id=run_id,
            tool_type=AgentToolType.RULE_ENGINE.value,
            tool_name="digital_employee.financial_risk_graph.scan",
            request_json={"task_type": "global_risk_scan"},
            context_json=context_json,
            executor=lambda: HermesRiskScannerService(self.db).scan_global_risks(
                run_id=run_id,
                tenant_id=tenant_id,
            ),
            fallback_factory=lambda exc: {
                "message": f"财务风险图谱巡检失败，已转人工检查：{exc}",
                "degraded": True,
            },
        )
        message = (
            str(summary.get("message") or "").strip()
            or "财务风险图谱巡检完成："
            f"扫描 {summary.get('scanned_claim_count', 0)} 张单据，"
            f"生成 {summary.get('risk_observation_count', 0)} 条风险观察。"
        )
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result={
                "message": message,
                "report_type": "global_risk_scan",
                "summary": summary,
                "degraded": degraded,
            },
            degraded=degraded,
            tool_count=1,
            failed_tool_count=1 if degraded else 0,
        )

    def _execute_employee_profile_scan(
        self,
        *,
        run_id: str,
        context_json: dict[str, Any],
        tenant_id: str,
    ) -> ExecutionOutcome:
        summary, degraded = self._invoke_tool(
            run_id=run_id,
            tool_type=AgentToolType.DATABASE.value,
            tool_name="digital_employee.employee_behavior_profile.scan",
            request_json={"task_type": "employee_behavior_profile_scan"},
            context_json=context_json,
            executor=lambda: HermesEmployeeProfileScannerService(self.db).scan_employee_profiles(
                log_id=run_id,
                tenant_id=tenant_id,
            ),
            fallback_factory=lambda exc: {
                "message": f"员工行为画像巡检失败，已保留失败记录：{exc}",
                "degraded": True,
            },
        )
        message = (
            str(summary.get("message") or "").strip()
            or "员工行为画像巡检完成："
            f"目标 {summary.get('target_employee_count', 0)} 人，"
            f"生成 {summary.get('snapshot_count', 0)} 条快照，"
            f"重点关注 {summary.get('high_attention_employee_count', 0)} 人。"
        )
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result={
                "message": message,
                "report_type": "employee_behavior_profile_scan",
                "summary": summary,
                "degraded": degraded,
            },
            degraded=degraded,
            tool_count=1,
            failed_tool_count=1 if degraded else 0,
        )

    def _execute_finance_policy_knowledge_sync(
        self,
        *,
        payload: OrchestratorRequest,
        run_id: str,
        task_asset: AgentAssetRead | None,
        context_json: dict[str, Any],
        tenant_id: str,
    ) -> ExecutionOutcome:
        config = task_asset.config_json if task_asset is not None else {}
        username = str(
            context_json.get("requested_by_username")
            or context_json.get("actor")
            or payload.user_id
            or "digital_employee"
        ).strip()
        display_name = str(context_json.get("requested_by_name") or username).strip()
        force = bool(context_json.get("force") or config.get("force"))
        changed_only = bool(config.get("changed_only", True)) and not force

        dispatch, degraded = self._invoke_tool(
            run_id=run_id,
            tool_type=AgentToolType.DATABASE.value,
            tool_name="digital_employee.finance_policy_knowledge.sync",
            request_json={
                "task_type": "finance_policy_knowledge_organize",
                "folder": config.get("folder"),
                "changed_only": changed_only,
                "force": force,
            },
            context_json=context_json,
            executor=lambda: asdict(
                KnowledgeSyncDispatchService(self.db).queue_sync(
                    current_user=CurrentUserContext(
                        username=username or "digital_employee",
                        name=display_name or username or "数字员工",
                        role_codes=["admin"],
                        is_admin=True,
                        tenant_id=tenant_id,
                    ),
                    folder=str(config.get("folder") or "").strip() or None,
                    source=AgentRunSource.SCHEDULE.value,
                    force=force,
                    changed_only=changed_only,
                )
            ),
            fallback_factory=lambda exc: {
                "message": f"知识制度整理任务入队失败：{exc}",
                "degraded": True,
            },
        )
        message = str(dispatch.get("summary") or "").strip() or "知识制度整理任务已提交。"
        if dispatch.get("agent_run_id"):
            message = f"{message} 日志编号：{dispatch['agent_run_id']}"
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result={
                "message": message,
                "report_type": "finance_policy_knowledge_organize",
                "summary": dispatch,
                "degraded": degraded,
            },
            degraded=degraded,
            tool_count=1,
            failed_tool_count=1 if degraded else 0,
        )

    def _execute_risk_clue_collect(
        self,
        *,
        run_id: str,
        context_json: dict[str, Any],
        tenant_id: str,
    ) -> ExecutionOutcome:
        summary, degraded = self._invoke_tool(
            run_id=run_id,
            tool_type=AgentToolType.DATABASE.value,
            tool_name="digital_employee.risk_clue.collect",
            request_json={"task_type": "risk_clue_collect"},
            context_json=context_json,
            executor=lambda: HermesRiskClueCollectorService(self.db).collect_risk_clues(
                run_id=run_id,
                tenant_id=tenant_id,
            ),
            fallback_factory=lambda exc: {
                "message": f"风险线索归集失败，已保留失败记录：{exc}",
                "degraded": True,
            },
        )
        message = (
            str(summary.get("message") or "").strip()
            or "风险线索归集完成："
            f"读取 {summary.get('fact_count', 0)} 条事实，"
            f"整理 {summary.get('rule_hit_count', 0)} 条规则命中，"
            f"输出 {summary.get('risk_clue_count', 0)} 条待复核线索。"
        )
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result={
                "message": message,
                "report_type": "risk_clue_collect",
                "summary": summary,
                "degraded": degraded,
            },
            degraded=degraded,
            tool_count=1,
            failed_tool_count=1 if degraded else 0,
        )

    def _execute_finance_report_orchestration(
        self,
        *,
        run_id: str,
        context_json: dict[str, Any],
        tenant_id: str,
    ) -> ExecutionOutcome:
        report_type = str(context_json.get("report_type") or "weekly").strip().lower()
        if report_type not in {"weekly", "quarterly", "annual"}:
            report_type = "weekly"
        summary, degraded = self._invoke_tool(
            run_id=run_id,
            tool_type=AgentToolType.DATABASE.value,
            tool_name="digital_employee.finance_report.orchestrate",
            request_json={
                "task_type": "finance_report_orchestration",
                "report_type": report_type,
            },
            context_json=context_json,
            executor=lambda: DigitalEmployeeFinanceReportTaskService(self.db).generate_report(
                report_type=report_type,  # type: ignore[arg-type]
                send_email=bool(context_json.get("send_email", True)),
                dry_run_email=bool(context_json.get("dry_run_email", False)),
                source=AgentRunSource.SCHEDULE.value,
                run_id=run_id,
                record_tool_call=False,
                tenant_id=tenant_id,
            ),
            fallback_factory=lambda exc: {
                "message": f"财务报告生成失败，已保留失败记录：{exc}",
                "degraded": True,
            },
        )

        message = (
            str(summary.get("message") or "").strip()
            or "财务报告编排完成："
            f"{summary.get('title', '财务经营报告')}，"
            f"邮件状态 {(summary.get('delivery') or {}).get('status', 'skipped')}。"
        )
        return ExecutionOutcome(
            status=AgentRunStatus.SUCCEEDED.value,
            result={
                "message": message,
                "report_type": "finance_report_orchestration",
                "summary": summary,
                "degraded": degraded,
            },
            degraded=degraded,
            tool_count=1,
            failed_tool_count=1 if degraded else 0,
        )


class OrchestratorExecutionHelperMixin:
    @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,
        *,
        run_id: str,
        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 = KnowledgeService(
            db=self.db,
            tenant_id=self._trusted_run_tenant_id(run_id),
        ).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(),
        }


class OrchestratorExecutionEngine(
    OrchestratorExecutionTaskMixin,
    OrchestratorToolExecutionMixin,
    OrchestratorExecutionHelperMixin,
):
    def __init__(
        self,
        *,
        db: Session,
        run_service,
        expense_claim_service,
        knowledge_service,
        user_agent_service,
        database_query_builder,
        trace_service=None,
    ) -> None:
        self.db = db
        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
        self.trace_service = trace_service

    def _trusted_run_tenant_id(self, run_id: str) -> str:
        run = self.run_service.get_run(run_id)
        if run is None:
            raise ValueError("知识操作关联的 Agent Run 不存在。")
        route_tenant_id = require_knowledge_tenant_id(
            (run.route_json or {}).get("tenant_id")
        )
        ontology_tenant_raw = (run.ontology_json or {}).get("tenant_id")
        if ontology_tenant_raw is not None:
            ontology_tenant_id = require_knowledge_tenant_id(ontology_tenant_raw)
            if ontology_tenant_id != route_tenant_id:
                raise ValueError("Agent Run 租户上下文不一致，知识操作已拒绝。")
        return route_tenant_id

    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(
                    run_id=run_id,
                    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,
        )
