from __future__ import annotations

from typing import Any

from app.schemas.steward import StewardPlanRequest, StewardPlanResponse
from app.services.steward_action_contracts import StewardActionPlanBuilder
from app.services.steward_constants import BUSINESS_CANONICAL_FIELD_ORDER
from app.services.steward_intent_agent import StewardIntentAgent
from app.services.steward_model_plan_builder import StewardModelPlanBuilder
from app.services.steward_off_topic_agent import StewardOffTopicAgent
from app.services.steward_planner_extraction import StewardPlannerExtractionMixin
from app.services.steward_planner_fallback import StewardPlannerFallbackMixin


class StewardPlannerService(StewardPlannerFallbackMixin, StewardPlannerExtractionMixin):
    """小财管家第一版规划服务：只生成计划，不执行入库类动作。"""

    def __init__(
        self,
        intent_agent: StewardIntentAgent | None = None,
        off_topic_agent: StewardOffTopicAgent | None = None,
    ) -> None:
        self.intent_agent = intent_agent
        self.off_topic_agent = off_topic_agent

    def build_plan(self, request: StewardPlanRequest) -> StewardPlanResponse:
        message = self._clean_text(request.message)
        if not message:
            raise ValueError("小财管家需要一段任务描述。")

        base_date = self._resolve_base_date(request.client_now_iso, request.context_json)
        scenario = self._classify_irrelevant_input(message, request)
        model_call_traces: list[dict[str, Any]] = []
        fallback_reason = ""
        if self.intent_agent is not None and self._should_use_model_intent_recognition(message, base_date, request):
            try:
                intent_result = self.intent_agent.detect(
                    request,
                    base_date=base_date,
                    canonical_fields=list(BUSINESS_CANONICAL_FIELD_ORDER),
                )
                if intent_result is not None:
                    model_call_traces = intent_result.model_call_traces
                    llm_plan = StewardModelPlanBuilder(self).build(
                        intent_result,
                        request=request,
                        base_date=base_date,
                    )
                    if llm_plan is not None:
                        if self._looks_like_ambiguous_travel_flow(message, base_date, request):
                            return self._with_action_steps(
                                self._build_pending_flow_fallback_plan(
                                    request,
                                    base_date=base_date,
                                    model_call_traces=model_call_traces,
                                    fallback_reason=(
                                        "主模型返回了直接任务，但当前话术没有明确申请或报销动作；"
                                        "服务端已改为候选流程确认，避免误入申请流程。"
                                    ),
                                    planning_source="llm_function_call",
                                )
                            )
                        return self._with_action_steps(llm_plan)
                model_call_traces = getattr(self.intent_agent, "last_call_traces", []) or model_call_traces
                fallback_reason = "主模型未返回可用的 function calling 计划，已切换到规则兜底。"
            except Exception as exc:
                model_call_traces = getattr(self.intent_agent, "last_call_traces", []) or model_call_traces
                fallback_reason = f"主模型 function calling 调用失败，已切换到规则兜底：{exc}"

        if scenario is not None:
            return self._with_action_steps(
                self._build_off_topic_plan(
                    request,
                    scenario=scenario,
                    model_call_traces=model_call_traces,
                    fallback_reason=fallback_reason,
                )
            )

        return self._with_action_steps(
            self._build_rule_fallback_plan(
                request,
                base_date=base_date,
                model_call_traces=model_call_traces,
                fallback_reason=fallback_reason,
            )
        )

    @staticmethod
    def _with_action_steps(plan: StewardPlanResponse) -> StewardPlanResponse:
        return StewardActionPlanBuilder().attach_action_steps(plan)
