from __future__ import annotations

import json
import re
from datetime import UTC, datetime, timedelta
from decimal import Decimal, InvalidOperation
from typing import Any

from sqlalchemy import or_, select
from sqlalchemy.orm import selectinload

from app.api.deps import CurrentUserContext
from app.core.agent_enums import AgentAssetStatus, AgentAssetType
from app.models.employee import Employee
from app.models.financial_record import ExpenseClaim
from app.schemas.agent_asset import AgentAssetListItem
from app.schemas.reimbursement import TravelReimbursementCalculatorRequest
from app.schemas.user_agent import (
    UserAgentCitation,
    UserAgentDraftPayload,
    UserAgentExpenseQueryRecord,
    UserAgentQueryPayload,
    UserAgentQueryStatusGroup,
    UserAgentReviewAction,
    UserAgentReviewClaimGroup,
    UserAgentReviewDocumentCard,
    UserAgentReviewDocumentField,
    UserAgentReviewEditField,
    UserAgentReviewPayload,
    UserAgentReviewRiskBrief,
    UserAgentReviewSlotCard,
    UserAgentRequest,
    UserAgentSuggestedAction,
)
from app.services.agent_assets import AgentAssetService
from app.services.expense_claims import ExpenseClaimService
from app.services.expense_rule_runtime import ExpenseRuleRuntimeService, RuntimeTravelPolicy, resolve_document_type_label
from app.services.risk_ontology_bridge import resolve_rule_codes_for_risk_check
from app.services.runtime_chat_commercial import trusted_runtime_chat_operation_context
from app.services.travel_reimbursement_calculator import TravelReimbursementCalculatorService
from app.services.user_agent_constants import *


class UserAgentResponseMixin:

    def _build_fallback_answer(
        self,
        payload: UserAgentRequest,
        *,
        citations: list[UserAgentCitation],
        draft_payload: UserAgentDraftPayload | None,
    ) -> str:
        if str(payload.tool_payload.get("result_type") or "").strip() == "knowledge_search":
            return self._build_explain_answer(payload, citations)

        if payload.ontology.intent in {"query", "compare"}:
            return self._build_query_answer(payload)

        if payload.ontology.intent == "risk_check":
            return self._build_risk_answer(payload, citations)

        if payload.ontology.intent == "draft":
            tool_message = str(payload.tool_payload.get("message") or "").strip()
            if payload.tool_payload.get("draft_limit_reached"):
                return tool_message or "您当前已保存 3 个草稿，请先完成已保存的草稿，才能再次新建草稿。"
            if tool_message and (
                str(payload.tool_payload.get("claim_id") or "").strip()
                or str(payload.tool_payload.get("claim_no") or "").strip()
            ):
                return tool_message
        if payload.ontology.intent == "draft" and draft_payload is not None:
            return (
                f"已生成 {draft_payload.title}，当前仅返回待人工确认的草稿内容，"
                "仍需人工确认后再进入正式流程。"
            )

        return self._build_explain_answer(payload, citations)


    def _build_guided_answer(self, payload: UserAgentRequest) -> str | None:
        if not self._is_generic_expense_prompt(payload):
            return self._build_implicit_expense_draft_guidance(payload)

        attachment_names = self._resolve_attachment_names(payload)
        ocr_summary = str(payload.context_json.get("ocr_summary") or "").strip()
        attachment_hint = ""
        if ocr_summary:
            attachment_hint = f" 我已读取附件 OCR 摘要：{ocr_summary}"
        elif attachment_names:
            attachment_hint = (
                f" 我已带入 {len(attachment_names)} 份附件名称，但目前还不能直接读取附件内容，"
                "还需要您补充关键信息。"
            )

        return (
            "可以帮您发起报销。请补充费用类型、发生时间、金额、事由和相关对象，"
            "或者直接上传票据附件，我会继续帮您判断能否报销、还缺哪些材料，并整理出待核对信息。"
            f"{attachment_hint}"
        )


    def _build_implicit_expense_draft_guidance(
        self,
        payload: UserAgentRequest,
    ) -> str | None:
        if not self._is_implicit_expense_draft_request(payload):
            return None

        amount_text = next(
            (item.value for item in payload.ontology.entities if item.type == "amount"),
            "",
        )
        expense_type = next(
            (
                EXPENSE_TYPE_LABELS.get(item.normalized_value, item.value)
                for item in payload.ontology.entities
                if item.type == "expense_type"
            ),
            "报销",
        )
        time_text = payload.ontology.time_range.raw or "本次"
        amount_hint = f"，金额 {amount_text}" if amount_text else ""

        return (
            f"已识别到一笔{time_text}的{expense_type}支出{amount_hint}。"
            "如果需要继续整理报销核对信息，还需要补充客户单位、参与人员、费用明细和票据附件。"
            "您也可以继续上传发票或图片，我会把这些信息带入后续对话。"
        )


    def _generate_answer_with_model(
        self,
        payload: UserAgentRequest,
        *,
        citations: list[UserAgentCitation],
        suggested_actions: list[UserAgentSuggestedAction],
        risk_flags: list[str],
        draft_payload: UserAgentDraftPayload | None,
        fallback_answer: str,
    ) -> str | None:
        messages = self._build_model_messages(
            payload,
            citations=citations,
            suggested_actions=suggested_actions,
            risk_flags=risk_flags,
            draft_payload=draft_payload,
            fallback_answer=fallback_answer,
        )
        answer = self._sanitize_model_answer(
            self.runtime_chat_service.complete(
                messages,
                max_tokens=800 if payload.ontology.scenario == "knowledge" else 420,
                temperature=0.2,
                timeout_seconds=(
                    KNOWLEDGE_MODEL_TIMEOUT_SECONDS
                    if payload.ontology.scenario == "knowledge"
                    else None
                ),
                slot_timeouts=(
                    {
                        "main": KNOWLEDGE_MODEL_MAIN_TIMEOUT_SECONDS,
                        "backup": KNOWLEDGE_MODEL_BACKUP_TIMEOUT_SECONDS,
                    }
                    if payload.ontology.scenario == "knowledge"
                    else None
                ),
                max_attempts=1 if payload.ontology.scenario == "knowledge" else None,
                operation_context=trusted_runtime_chat_operation_context(
                    self.db,
                    run_id=payload.run_id,
                    attempt_scope="user-agent-response",
                ),
            )
        )
        return self._reject_unsupported_location_inference(payload, answer)


    def _sanitize_model_answer(self, answer: str | None) -> str | None:
        if not answer:
            return None

        cleaned = re.sub(r"<think>.*?</think>", "", answer, flags=re.DOTALL | re.IGNORECASE)
        cleaned = cleaned.strip()
        leaked_reasoning_markers = (
            "用户问的是",
            "让我分析一下",
            "实体识别",
            "从对话历史来看",
            "从tool_payload来看",
            "现在问题是",
            "我需要：",
            "关键是我",
        )
        if any(marker in cleaned[:500] for marker in leaked_reasoning_markers):
            return None
        return cleaned or None


    @staticmethod
    def _extract_query_location(message: str) -> str:
        match = re.search(r"(?:去|到|前往)([\u4e00-\u9fff]{2,8})(?:出差|开会|培训)", str(message or ""))
        return match.group(1) if match else ""


    def _reject_unsupported_location_inference(
        self,
        payload: UserAgentRequest,
        answer: str | None,
    ) -> str | None:
        del payload
        return answer


    def _build_model_messages(
        self,
        payload: UserAgentRequest,
        *,
        citations: list[UserAgentCitation],
        suggested_actions: list[UserAgentSuggestedAction],
        risk_flags: list[str],
        draft_payload: UserAgentDraftPayload | None,
        fallback_answer: str,
    ) -> list[dict[str, str]]:
        knowledge_question = (
            self._resolve_knowledge_question(payload)
            if payload.ontology.scenario == "knowledge"
            else ""
        )
        facts = {
            "run_id": payload.run_id,
            "user_message": payload.message,
            "ontology": payload.ontology.model_dump(mode="json"),
            "context": {
                "entry_source": payload.context_json.get("entry_source"),
                "user_name": payload.context_json.get("name"),
                "user_role": payload.context_json.get("role"),
                "user_department": payload.context_json.get("department_name")
                or payload.context_json.get("department"),
                "user_position": payload.context_json.get("position"),
                "user_grade": payload.context_json.get("grade"),
                "employee_no": payload.context_json.get("employee_no"),
                "manager_name": payload.context_json.get("manager_name"),
                "employee_location": payload.context_json.get("employee_location"),
                "cost_center": payload.context_json.get("cost_center"),
                "finance_owner_name": payload.context_json.get("finance_owner_name"),
                "employee_risk_profile": payload.context_json.get("employee_risk_profile", {}),
                "user_role_codes": payload.context_json.get("role_codes", []),
                "is_admin": bool(payload.context_json.get("is_admin")),
                "request_context": payload.context_json.get("request_context"),
                "attachment_count": payload.context_json.get("attachment_count"),
                "attachment_names": self._resolve_attachment_names(payload),
                "ocr_summary": payload.context_json.get("ocr_summary", ""),
                "ocr_documents": payload.context_json.get("ocr_documents", []),
                "conversation_id": payload.context_json.get("conversation_id"),
                "conversation_scenario": payload.context_json.get("conversation_scenario"),
                "conversation_intent": payload.context_json.get("conversation_intent"),
                "draft_claim_id": payload.context_json.get("draft_claim_id"),
                "conversation_history": self._resolve_conversation_history(payload),
            },
            "tool_payload": self._build_model_tool_payload(
                payload.tool_payload,
                question=knowledge_question,
            ),
            "citations": [item.model_dump(mode="json") for item in citations],
            "suggested_actions": [item.model_dump(mode="json") for item in suggested_actions],
            "risk_flags": risk_flags,
            "draft_payload": draft_payload.model_dump(mode="json") if draft_payload is not None else None,
            "selected_capability_codes": payload.selected_capability_codes,
            "requires_confirmation": payload.requires_confirmation,
        }
        if payload.ontology.scenario == "knowledge":
            facts["knowledge_evidence_blocks"] = self._build_knowledge_evidence_blocks(
                payload.tool_payload,
                question=knowledge_question,
            )
            facts["knowledge_answer_evidence"] = [
                {
                    "title": str(item.get("title") or "").strip(),
                    "heading": str(item.get("heading") or "").strip(),
                    "kind": str(item.get("kind") or "").strip(),
                    "content": str(item.get("content") or "").strip(),
                }
                for item in self._build_knowledge_answer_evidence(payload)
            ]

        if payload.ontology.scenario == "knowledge":
            answer_style_instruction = (
                "你是财务制度知识问答助手。只能依据 facts.tool_payload.hits、facts.knowledge_answer_evidence、citations 与 conversation_history 回答，"
                "不要扩展成通用助手。优先直接回答，不要复述思考过程，不要输出 JSON、代码块或可见思考过程。"
                "禁止使用“已命中”“答案整理阶段”“稍后重试”。"
                "最终答复必须使用 Markdown，优先包含“## 结论”“## 依据”“## 说明”这三个二级标题；"
                "如果某一部分没有内容，可以省略该标题。"
                "回答风格要像一位真正熟悉制度的财务伙伴：先直接回应用户的核心问题，再用一张简洁表格或短段落说明依据，"
                "最后补充最重要的注意事项。不要写成“已检索到内容”的系统回执，也不要把命中片段连缀成答案。"
                "必须优先回答用户当前这句话本身，不能把制度标题、制度全文或完整标准表当成主答案。"
                "回答前先判断召回内容是否真的能回答当前问题；如果不能，必须明确说当前知识库没有找到直接依据，"
                "不要改答相邻主题，也不要用相似条款硬凑答案。"
                "如果用户问的是某次具体行程“一共能报多少”，就先给“当前已能确认的金额”，再用一张很短的表说明项目、"
                "适用标准、计算式和结果；如果总额还缺少住宿晚数、实际票据或其他必要条件，就明确写出“暂不能确认的部分”。"
                "只有用户明确在问“标准有哪些”或“制度全文怎么规定”时，才展开完整标准表。"
                "如果命中的知识已经足够支持计算、比较或归纳，就直接给出结论；金额、标准、天数、补贴等问题要把计算过程写清楚。"
                "适合时请使用 Markdown 二级标题、短段落和表格，让回答更清晰；表格必须保证每一行列数一致，不要出现空白残列。"
                "只能陈述 hits 中明确出现的事实，不能用常识、外部知识或主观推断补齐缺失条件。"
                "回答前先在全部 hits 中寻找与问题最直接相关的章节、表格或条目，不能只依赖排在最前面的片段。"
                "如果 facts.knowledge_answer_evidence 中已经给出更短的高相关证据，优先基于这些证据组织答案，再回看原始 hits 补上下文。"
                "如果某个表格在检索片段中已经被摊平成连续文本，只有在行、列和数值对应关系能够从片段本身明确确认时才能据此计算；"
                "如果列对应关系不清楚，必须说明表格结构在当前片段中不够清晰，不能把第一列或相邻数字想当然套给用户。"
                "如果 hits 中出现“结构化表格补充”，它表示知识归纳阶段已经把原文表格重新整理过，"
                "优先使用这类结构化表格来理解行列关系，再回看原文确认上下文。"
                "facts.knowledge_evidence_blocks 中保留了原始换行和定宽排版；遇到表格时，优先按这些证据块阅读，"
                "必须按表头从左到右逐列对应数值，不能把第一列的数值直接套给后面的列名。"
                "如果完成计算或归纳仍缺少某个关键映射关系、适用条件或数值依据，必须明确说明当前知识库还缺哪一项信息，再给出已能确认的部分。"
                "如果用户问题里没有明确给出某个套用条件，而 hits 或 evidence 里也没有明确出现，就不能自己补一个默认值。"
                "当问题涉及追问时，必须结合 conversation_history 延续上一轮上下文，而不是重新泛化成制度全文摘录。"
                "不要大段粘贴原始命中文本；只提炼与问题直接相关的规则、条件、金额和注意事项。"
                "如果依据仍然不足，明确指出缺少哪一项信息，再给出当前能确认的部分。"
            )
        else:
            answer_style_instruction = "用 2 到 4 段完成回答，优先给结论，再补充最关键的依据与下一步建议。"

        personalization_instruction = (
            "如果 context.user_name 存在，并且当前问题与员工本人适用标准、报销额度、审批权限、职级待遇有关，"
            "开头应自然称呼一次用户，例如“曹笑竹，您好”。"
            "如果需要根据员工身份判断标准，优先参考 context.user_grade 与 context.user_position。"
            "如果问题与用户身份无关，就不要生硬加入姓名、职级或岗位。"
        )

        system_prompt = (
            "你是 X-Financial 的专业财务 AI 助手。"
            "回答必须准确、自然、可执行，不要泄露中间推理。"
            "当知识问题有命中依据时，先给结论，再给结构化说明。"
            "不要把制度全文原样搬出来，不要把检索片段当作最终答案直接粘贴。"
            "如果使用表格，确保列名简洁、数值明确。"
            f"{personalization_instruction}"
            f"{answer_style_instruction}"
        )
        user_prompt = (
            "请严格依据下面的 facts 生成最终答复：\n"
            f"{json.dumps(facts, ensure_ascii=False, indent=2)}"
        )
        return [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt},
        ]


    def _build_query_answer(self, payload: UserAgentRequest) -> str:
        scenario = payload.ontology.scenario
        data = payload.tool_payload
        subject = self._resolve_subject(payload)

        if scenario == "expense":
            query_payload = self._build_query_payload(payload)
            scope_label = str(data.get("scope_label") or subject).strip() or subject
            if query_payload is None:
                return f"当前没有查到{scope_label}。您可以补充时间范围、单号或状态继续筛选。"

            window_prefix = (
                f"{query_payload.window_start_date} 至 {query_payload.window_end_date}"
                if query_payload.recent_window_applied
                and query_payload.window_start_date
                and query_payload.window_end_date
                else (
                    f"近 {query_payload.window_days} 日内"
                    if query_payload.recent_window_applied and query_payload.window_days
                    else "当前条件下"
                )
            )
            if query_payload.record_count <= 0:
                if query_payload.older_record_count > 0 and query_payload.window_days:
                    return (
                        f"{window_prefix}没有查到{query_payload.scope_label}。"
                        f"另有 {query_payload.older_record_count} 笔超过 {query_payload.window_days} 日的单据，"
                        "请前往个人报销中心查看。"
                    )
                return f"{window_prefix}没有查到{query_payload.scope_label}。您可以补充时间范围、单号或状态继续筛选。"

            answer_parts = [
                f"已按您的筛选条件查询{query_payload.scope_label}。",
                f"下面先列出最近 {query_payload.preview_count} 条记录，点击任一单据即可查看详情。",
                f"本次共命中 {query_payload.record_count} 笔，金额合计 {query_payload.total_amount:.2f} 元。",
            ]

            hint_parts: list[str] = []

            if query_payload.older_record_count > 0 and query_payload.window_days:
                hint_parts.append(
                    f"另有 {query_payload.older_record_count} 笔超过 {query_payload.window_days} 日的单据，"
                    "请前往个人报销中心查看。"
                )

            return " ".join(answer_parts + hint_parts).strip()

        if scenario == "accounts_receivable":
            record_count = int(data.get("record_count") or 0)
            outstanding_amount = float(data.get("outstanding_amount") or 0)
            return (
                f"{subject}共命中 {record_count} 条应收，未回款金额 {outstanding_amount:.2f} 元。"
                "建议结合账龄和客户分布继续排查逾期风险。"
            )

        if scenario == "accounts_payable":
            record_count = int(data.get("record_count") or 0)
            outstanding_amount = float(data.get("outstanding_amount") or 0)
            return (
                f"{subject}共命中 {record_count} 条应付，待付金额 {outstanding_amount:.2f} 元。"
                "如需推进动作，建议先生成付款建议草稿并发起人工确认。"
            )

        return "已完成当前查询，但暂时没有更多结构化结果可展示。"


    def _build_query_payload(
        self,
        payload: UserAgentRequest,
    ) -> UserAgentQueryPayload | None:
        if payload.ontology.scenario != "expense" or payload.ontology.intent not in {"query", "compare"}:
            return None

        result_type = str(payload.tool_payload.get("result_type") or "").strip()
        if result_type and result_type != "expense_claim_list":
            return None

        records: list[UserAgentExpenseQueryRecord] = []
        for item in payload.tool_payload.get("records") or []:
            if not isinstance(item, dict):
                continue
            amount = float(item.get("amount") or 0)
            records.append(
                UserAgentExpenseQueryRecord(
                    claim_id=str(item.get("claim_id") or "").strip(),
                    claim_no=str(item.get("claim_no") or "").strip() or "未编号",
                    employee_name=str(item.get("employee_name") or "").strip(),
                    expense_type=str(item.get("expense_type") or "").strip(),
                    expense_type_label=str(item.get("expense_type_label") or "").strip()
                    or EXPENSE_TYPE_LABELS.get(str(item.get("expense_type") or "").strip(), "报销"),
                    amount=round(amount, 2),
                    status=str(item.get("status") or "").strip(),
                    status_label=str(item.get("status_label") or "").strip()
                    or EXPENSE_STATUS_LABELS.get(str(item.get("status") or "").strip(), "处理中"),
                    status_group=str(item.get("status_group") or "").strip() or "other",
                    status_group_label=str(item.get("status_group_label") or "").strip()
                    or EXPENSE_STATUS_GROUP_LABELS.get(str(item.get("status_group") or "").strip(), "其他状态"),
                    approval_stage=str(item.get("approval_stage") or "").strip() or None,
                    document_date=str(item.get("document_date") or "").strip(),
                    occurred_at=str(item.get("occurred_at") or "").strip(),
                    reason=str(item.get("reason") or "").strip(),
                    location=str(item.get("location") or "").strip(),
                    risk_flags=[
                        flag
                        for flag in list(item.get("risk_flags") or [])
                        if isinstance(flag, dict)
                    ],
                )
            )

        status_groups: list[UserAgentQueryStatusGroup] = []
        for item in payload.tool_payload.get("status_groups") or []:
            if not isinstance(item, dict):
                continue
            status_groups.append(
                UserAgentQueryStatusGroup(
                    key=str(item.get("key") or "").strip() or "other",
                    label=str(item.get("label") or "").strip() or "其他状态",
                    count=max(0, int(item.get("count") or 0)),
                )
            )

        return UserAgentQueryPayload(
            result_type="expense_claim_list",
            scope_label=str(payload.tool_payload.get("scope_label") or self._resolve_subject(payload)).strip() or "报销单",
            title=str(payload.tool_payload.get("title") or "").strip(),
            recent_window_applied=bool(payload.tool_payload.get("recent_window_applied")),
            window_days=(
                int(payload.tool_payload["window_days"])
                if payload.tool_payload.get("window_days") not in {None, ""}
                else None
            ),
            window_start_date=(
                str(payload.tool_payload.get("window_start_date") or "").strip() or None
            ),
            window_end_date=(
                str(payload.tool_payload.get("window_end_date") or "").strip() or None
            ),
            record_count=max(0, int(payload.tool_payload.get("record_count") or 0)),
            preview_count=max(0, int(payload.tool_payload.get("preview_count") or len(records))),
            preview_limit=max(1, int(payload.tool_payload.get("preview_limit") or 5)),
            older_record_count=max(0, int(payload.tool_payload.get("older_record_count") or 0)),
            has_more_in_window=bool(payload.tool_payload.get("has_more_in_window") or payload.tool_payload.get("has_more")),
            total_amount=round(float(payload.tool_payload.get("total_amount") or 0), 2),
            status_groups=status_groups,
            records=records,
        )


    def _build_explain_answer(
        self,
        payload: UserAgentRequest,
        citations: list[UserAgentCitation],
    ) -> str:
        if str(payload.tool_payload.get("result_type") or "").strip() == "knowledge_search":
            if citations or list(payload.tool_payload.get("hits") or []):
                return self._build_knowledge_search_answer(payload, citations)

            tool_message = str(payload.tool_payload.get("message") or "").strip()
            if tool_message:
                return tool_message

        if citations:
            titles = "、".join(item.title for item in citations[:2])
            summary = citations[0].excerpt or "请结合制度全文进一步确认。"
            return f"已检索到相关依据：{titles}。核心说明：{summary}"

        return (
            f"当前还没有与“{SCENARIO_LABELS.get(payload.ontology.scenario, '当前问题')}”"
            "强匹配的已上线规则引用，建议先人工复核或补充更具体的单据上下文。"
        )


    def _build_risk_answer(
        self,
        payload: UserAgentRequest,
        citations: list[UserAgentCitation],
    ) -> str:
        risk_flags = self._resolve_risk_flags(payload)
        platform_messages = self._evaluate_platform_risk_messages(payload)
        if not risk_flags and not platform_messages:
            return "当前未识别到明确风险标签，建议继续查看原始明细或补充更多上下文。"

        reasons = [
            f"{flag}：{RISK_REASON_MAP.get(flag, f'{flag} 需要人工进一步确认。')}"
            for flag in risk_flags
        ]
        if platform_messages:
            reasons.extend(platform_messages)
        citation_text = (
            f" 参考规则：{'、'.join(item.title for item in citations[:2])}。"
            if citations
            else ""
        )
        signal_count = len(risk_flags) + (1 if platform_messages else 0)
        return (
            f"本次识别到 {signal_count} 类风险信号。"
            f"触发原因：{'；'.join(reasons)}。"
            "建议先复核明细、附件和审批链，再决定是否继续处理。"
            f"{citation_text}"
        )


    def _evaluate_platform_risk_messages(self, payload: UserAgentRequest) -> list[str]:
        claim_id = str(payload.tool_payload.get("claim_id") or "").strip()
        if not claim_id:
            return []

        claim = self.db.scalar(
            select(ExpenseClaim)
            .where(ExpenseClaim.id == claim_id)
            .options(selectinload(ExpenseClaim.items))
        )
        if claim is None:
            return []

        rule_codes = resolve_rule_codes_for_risk_check(
            payload.ontology,
            query_text=payload.message,
        )
        claim_service = ExpenseClaimService(self.db)
        business_stage = (
            "expense_application"
            if claim_service._is_expense_application_claim(claim)
            else "reimbursement"
        )
        review = claim_service.evaluate_platform_risk_rules(
            claim,
            rule_codes=rule_codes,
            business_stage=business_stage,
        )
        messages: list[str] = []
        for flag in review.get("flags") or []:
            if not isinstance(flag, dict):
                continue
            message = str(flag.get("message") or "").strip()
            if message and message not in messages:
                messages.append(message)
        return messages


    def _build_draft_payload(self, payload: UserAgentRequest) -> UserAgentDraftPayload:
        scenario_label = SCENARIO_LABELS.get(payload.ontology.scenario, "业务")
        subject = self._resolve_subject(payload)
        claim_id = str(payload.tool_payload.get("claim_id") or "").strip() or None
        claim_no = str(payload.tool_payload.get("claim_no") or "").strip() or None
        claim_status = str(payload.tool_payload.get("status") or "").strip() or None
        approval_stage = str(payload.tool_payload.get("approval_stage") or "").strip() or None
        if claim_id and (claim_no is None or claim_status is None or approval_stage is None):
            claim = self.db.get(ExpenseClaim, claim_id)
            if claim is not None:
                claim_no = claim_no or str(claim.claim_no or "").strip() or None
                claim_status = claim_status or str(claim.status or "").strip() or None
                approval_stage = approval_stage or str(claim.approval_stage or "").strip() or None
        is_submitted = claim_status == "submitted"
        title = f"{scenario_label}处理意见草稿"
        if claim_no:
            title = f"{scenario_label}{'报销单' if is_submitted else '草稿'} {claim_no}"
        if is_submitted:
            body = (
                f"主题：{subject}\n"
                f"结论：报销单已提交，当前节点为 {approval_stage or '审批中'}。\n"
                "建议：后续可在个人报销列表中跟踪审批进度，必要时再补充说明或附件。\n"
                f"原始问题：{payload.message}"
            )
        else:
            body = (
                f"主题：{subject}\n"
                "结论：已根据当前语义解析结果生成草稿，尚未自动执行。\n"
                "建议：请先核对明细、规则命中和所需附件，再由人工确认是否提交正式流程。\n"
                f"原始问题：{payload.message}"
            )
        return UserAgentDraftPayload(
            draft_type=payload.ontology.scenario,
            title=title,
            body=body,
            confirmation_required=not is_submitted,
            claim_id=claim_id,
            claim_no=claim_no,
            status=claim_status,
            approval_stage=approval_stage,
        )


    @staticmethod
    def _should_build_draft_payload(payload: UserAgentRequest) -> bool:
        if payload.ontology.scenario == "expense" and payload.tool_payload.get("preview_only"):
            return any(
                str(payload.tool_payload.get(key) or "").strip()
                for key in ("claim_id", "claim_no")
            )
        if payload.ontology.intent == "draft":
            return True
        if payload.ontology.scenario != "expense":
            return False
        return any(
            str(payload.tool_payload.get(key) or "").strip()
            for key in ("claim_id", "claim_no", "status")
        )


    def _build_suggested_actions(
        self,
        payload: UserAgentRequest,
    ) -> list[UserAgentSuggestedAction]:
        if payload.ontology.scenario == "knowledge":
            return []

        if self._should_prompt_expense_scene_selection(payload):
            return [
                UserAgentSuggestedAction(
                    label=label,
                    action_type="select_expense_type",
                    description=description,
                    payload={
                        "expense_type": code,
                        "expense_type_label": label,
                        "original_message": payload.message,
                    },
                )
                for code, label, description in EXPENSE_SCENE_SELECTION_OPTIONS
            ]

        if self._is_generic_expense_prompt(payload):
            return [
                UserAgentSuggestedAction(
                    label="上传票据",
                    action_type="ask_clarification",
                    description="上传发票、行程单或付款截图，继续识别报销内容。",
                ),
                UserAgentSuggestedAction(
                    label="补充报销信息",
                    action_type="ask_clarification",
                    description="补充费用类型、金额、时间和事由后继续处理。",
                ),
            ]

        if payload.ontology.intent in {"query", "compare"}:
            return []

        if payload.ontology.intent == "risk_check":
            return [
                UserAgentSuggestedAction(
                    label="人工复核风险",
                    action_type="manual_review",
                    description="优先检查明细、附件和规则命中原因。",
                ),
                UserAgentSuggestedAction(
                    label="生成整改建议",
                    action_type="create_draft",
                    description="把风险说明整理成处理意见草稿。",
                ),
            ]

        if payload.ontology.intent == "draft":
            return [
                UserAgentSuggestedAction(
                    label="复制草稿",
                    action_type="copy_draft",
                    description="复制当前草稿后交由人工确认。",
                ),
                UserAgentSuggestedAction(
                    label="补充上下文",
                    action_type="ask_clarification",
                    description="补充单据编号、客户或供应商信息以完善草稿。",
                ),
            ]

        return [
            UserAgentSuggestedAction(
                label="查看规则全文",
                action_type="open_rule",
                description="继续查看引用规则或知识内容。",
            ),
            UserAgentSuggestedAction(
                label="补充问题上下文",
                action_type="ask_clarification",
                description="补充业务对象、时间或单据范围，提升回答准确度。",
            ),
        ]
