"""小财管家业务无关输入的引导生成 agent。

当用户的输入被识别为与财务任务无关时（问候、纯数字、闲聊等），
由该 agent 用 function calling 让主模型生成一句管家对主人语气的引导回复。
LLM 不可用或调用失败时，由调用方 fallback 到规则模板。
"""

from __future__ import annotations

import json
from dataclasses import dataclass
from typing import Any

from app.schemas.steward import StewardPlanRequest
from app.services.runtime_chat import RuntimeChatService


STEWARD_OFF_TOPIC_FUNCTION_NAME = "submit_steward_off_topic_response"


STEWARD_OFF_TOPIC_SCENARIO_PROMPTS: dict[str, str] = {
    "greeting": (
        "用户发起了礼貌问候，例如「你好」「您好」「早上好」。"
        "请像管家一样礼貌地回应主人的问候，温和询问主人今天要办理什么业务，"
        "并顺势说明小财管家能帮主人整理费用申请和费用报销两类事项。"
        "可以再提示主人试试用具体的话术来表达需求。"
    ),
    "off_business": (
        "用户说了有意义的话但与财务无关，例如问天气、聊生活、聊工作日常。"
        "请温和地告诉主人：这句话里没有识别到财务事项，"
        "小财管家目前只能帮主人整理费用申请和费用报销。"
        "再请主人用具体的话术告诉小财管家他/她想办什么业务。"
    ),
    "meaningless": (
        "用户输入了与财务无关且难以理解的内容，例如纯数字、纯标点、重复字符。"
        "请温和地告诉主人：这句话里好像没有出现费用申请、报销、出差、交通、招待这些关键词，"
        "请主人换种说法再告诉小财管家一次。"
    ),
}


@dataclass(frozen=True, slots=True)
class StewardOffTopicAgentResult:
    response_text: str
    model_call_traces: list[dict[str, Any]]


class StewardOffTopicAgent:
    """使用大模型 function calling 生成小财管家对业务无关输入的多样化引导。"""

    def __init__(self, runtime_chat_service: RuntimeChatService) -> None:
        self.runtime_chat_service = runtime_chat_service
        self.last_call_traces: list[dict[str, Any]] = []

    def generate(
        self,
        request: StewardPlanRequest,
        *,
        scenario: str,
    ) -> StewardOffTopicAgentResult | None:
        messages = self._build_messages(request, scenario=scenario)
        try:
            result = self.runtime_chat_service.complete_with_tool_call(
                messages,
                tools=[self._build_tool_schema()],
                tool_choice={
                    "type": "function",
                    "function": {"name": STEWARD_OFF_TOPIC_FUNCTION_NAME},
                },
                max_tokens=400,
                temperature=0.7,
                timeout_seconds=15,
                max_attempts=1,
            )
        except Exception:
            return None

        self.last_call_traces = result.calls_as_dicts()
        if result.tool_call is None or result.tool_call.name != STEWARD_OFF_TOPIC_FUNCTION_NAME:
            return None

        arguments = result.tool_call.arguments or {}
        response_text = str(arguments.get("response_text") or "").strip()
        if not response_text:
            return None
        return StewardOffTopicAgentResult(
            response_text=response_text,
            model_call_traces=self.last_call_traces,
        )

    @staticmethod
    def _build_messages(
        request: StewardPlanRequest,
        *,
        scenario: str,
    ) -> list[dict[str, Any]]:
        scenario_hint = STEWARD_OFF_TOPIC_SCENARIO_PROMPTS.get(
            scenario,
            STEWARD_OFF_TOPIC_SCENARIO_PROMPTS["off_business"],
        )
        return [
            {
                "role": "system",
                "content": (
                    "你是 X-Financial 的小财管家，要像一位贴身的管家那样为主人服务。"
                    "用户输入的内容与财务任务无关，你需要生成一段温和、尊敬的引导回复。"
                    "要求：\n"
                    "1. 始终称呼用户为「主人」「您」，不要用「你」。\n"
                    "2. 语气尊敬、温和、主动，体现管家的服务意识。\n"
                    "3. 不要每次都用相同的句式，要根据用户的输入和当前场景变化表达。\n"
                    "4. 控制在 2-4 句，不要过长。\n"
                    "5. 必须使用 function calling 输出 response_text，不要返回普通文本。\n"
                    "6. response_text 使用 Markdown，第一行用 ### 标题，正文与引导句之间留空行。\n"
                    "7. 如果主人是在问候，先礼貌回应再引导；"
                    "如果主人说的是非财务话题，温和说明小财管家能做什么；"
                    "如果主人的内容没有意义，请他/她换种说法。\n"
                    f"当前场景：{scenario_hint}"
                ),
            },
            {
                "role": "user",
                "content": json.dumps(
                    {
                        "message": request.message,
                        "scenario": scenario,
                    },
                    ensure_ascii=False,
                ),
            },
        ]

    @staticmethod
    def _build_tool_schema() -> dict[str, Any]:
        return {
            "type": "function",
            "function": {
                "name": STEWARD_OFF_TOPIC_FUNCTION_NAME,
                "description": (
                    "提交小财管家对业务无关输入的引导回复。"
                    "response_text 是完整 Markdown 文本："
                    "第一行 ### 标题（基于场景），空行，正文（歉意或回应 + 能力范围 + 引导句）。"
                ),
                "parameters": {
                    "type": "object",
                    "properties": {
                        "response_text": {
                            "type": "string",
                            "description": (
                                "面向用户的引导回复 Markdown 文本。"
                                "称呼用户为「主人」「您」，不要用「你」。"
                            ),
                        },
                    },
                    "required": ["response_text"],
                },
            },
        }
