from __future__ import annotations

import unittest

from app.plugins.base import PluginRegistry
from app.plugins.mock import MockPluginExecutor
from app.schemas.chat import ChatRequest
from app.schemas.debug import IntentCandidate, MatcherStageDebug, RoutingDebug
from app.schemas.workflow import Workflow, WorkflowStep
from app.services.agent_service import AgentService
from app.services.intent_registry import IntentRegistry
from app.services.planner import HeuristicWorkflowPlanner
from app.services.response_policy import ResponsePolicy
from app.services.rewrite_engine import ContextRewriteEngine
from app.services.router import RouteMatchResult
from app.services.session_store import InMemorySessionStore


class _FailIfCalledPlanner:
    def plan(self, text, intents, context=None):
        _ = (intents, context)
        raise AssertionError(f"planner should not be called for single intent request: {text}")


class _ScriptedRouter:
    def __init__(self, registry: IntentRegistry) -> None:
        self._registry = registry
        self._route_map = {
            "来点music": self._route_result("cabin_play_music", ["cabin_play_music"]),
            "打开车窗和空调": self._route_result("cabin_window_open", ["cabin_window_open", "cabin_ac_on"]),
            "关闭车窗": self._route_result("cabin_window_close", ["cabin_window_close", "cabin_window_open"]),
        }
        self._slot_map = {
            ("播放黄昏", "cabin_play_music"): {"song": "黄昏"},
            ("来一首黄昏", "cabin_play_music"): {"song": "黄昏"},
            ("来点黄昏", "cabin_play_music"): {"song": "黄昏"},
        }

    def route(self, text: str) -> RouteMatchResult:
        if text not in self._route_map:
            raise AssertionError(f"unexpected route request: {text}")
        return self._route_map[text]

    def extract_slots(self, text: str, intent) -> dict[str, object]:
        return dict(self._slot_map.get((text, intent.intent_id), {}))

    def _route_result(self, selected_intent: str, candidates: list[str]) -> RouteMatchResult:
        intent = self._registry.get(selected_intent)
        stage = MatcherStageDebug(
            stage="fusion",
            accepted=True,
            selected_intent=selected_intent,
            score=1.0,
            candidates=[
                IntentCandidate(intent_id=intent_id, score=max(0.5, 1.0 - index * 0.1))
                for index, intent_id in enumerate(candidates)
            ],
        )
        return RouteMatchResult(
            intent=intent,
            debug=RoutingDebug(
                selected_intent=selected_intent,
                matched_stage="fusion",
                decision="execute",
                stages=[stage],
            ),
        )


class DialogContinuationAndMultiIntentTests(unittest.TestCase):
    def setUp(self) -> None:
        self.registry = IntentRegistry.from_json("app/data/intents.json")
        self.plugins = PluginRegistry()
        MockPluginExecutor().register(self.plugins)
        self.service = AgentService(
            intent_registry=self.registry,
            router=_ScriptedRouter(self.registry),
            plugins=self.plugins,
            session_store=InMemorySessionStore(),
            rewrite_engine=ContextRewriteEngine(),
            response_policy=ResponsePolicy(),
            planner=HeuristicWorkflowPlanner(),
        )

    def test_music_followup_in_chat_continues_waiting_slot(self) -> None:
        first = self.service.handle_chat(
            ChatRequest(
                session_id="sess_music_followup",
                user_id="user_1",
                input_text="来点music",
            )
        )

        self.assertEqual(first.reply_type, "ask_slot")
        self.assertEqual(first.pending_slots, ["media_query"])

        second = self.service.handle_chat(
            ChatRequest(
                session_id="sess_music_followup",
                user_id="user_1",
                input_text="黄昏",
            )
        )

        self.assertEqual(second.reply_type, "workflow_result")
        self.assertEqual(second.intent, "cabin_play_music")
        self.assertEqual(second.filled_slots.get("song"), "黄昏")
        self.assertIn("黄昏", second.reply_text)

    def test_parallel_compound_request_enters_planner(self) -> None:
        response = self.service.handle_chat(
            ChatRequest(
                session_id="sess_parallel_compound",
                user_id="user_1",
                input_text="打开车窗和空调",
            )
        )

        self.assertEqual(response.reply_type, "workflow_result")
        self.assertEqual(response.workflow.workflow_type, "sequence")
        step_intents = [step.intent_id for step in response.workflow.steps]
        self.assertEqual(step_intents, ["cabin_window_open", "cabin_ac_on"])
        self.assertIn("车窗", response.reply_text)
        self.assertIn("空调", response.reply_text)

    def test_single_cabin_intent_does_not_enter_planner_from_top2_domain_candidates(self) -> None:
        service = AgentService(
            intent_registry=self.registry,
            router=_ScriptedRouter(self.registry),
            plugins=self.plugins,
            session_store=InMemorySessionStore(),
            rewrite_engine=ContextRewriteEngine(),
            response_policy=ResponsePolicy(),
            planner=_FailIfCalledPlanner(),
        )

        response = service.handle_chat(
            ChatRequest(
                session_id="sess_single_cabin_no_planner",
                user_id="user_1",
                input_text="关闭车窗",
            )
        )

        self.assertEqual(response.reply_type, "workflow_result")
        self.assertEqual(response.intent, "cabin_window_close")
        self.assertEqual(response.routing_debug.decision, "execute")
        self.assertFalse(any(stage.stage == "planner" for stage in response.routing_debug.stages))

    def test_waiting_confirmation_can_continue_via_chat(self) -> None:
        session = self.service.session_store.get_or_create("sess_confirm_chat", "user_1")
        session.current_intent = "cs_cancel_order"
        session.status = "waiting_confirmation"
        session.pending_slots = ["confirmation"]
        session.slots = {"order_id": "A123456"}
        session.workflow = Workflow(
            workflow_id="wf_confirm_chat",
            workflow_type="conditional",
            domain="customer_service",
            intent_id="cs_cancel_order",
            status="waiting_confirmation",
            risk_level="high",
            slots={"order_id": "A123456"},
            steps=[
                WorkflowStep(
                    step=1,
                    step_id="step_cancel",
                    intent_id="cs_cancel_order",
                    plugin_id="plugin.order.cancel",
                    action="cancel_order",
                    status="waiting_confirmation",
                    slots={"order_id": "A123456"},
                    requires_confirmation=True,
                )
            ],
            meta={
                "pending_confirmation": {
                    "step_id": "step_cancel",
                    "intent_id": "cs_cancel_order",
                    "detail": "确认取消订单 A123456",
                },
                "step_results": {},
                "confirmed_steps": [],
            },
        ).model_dump()
        self.service.session_store.save(session)

        response = self.service.handle_chat(
            ChatRequest(
                session_id="sess_confirm_chat",
                user_id="user_1",
                input_text="确认",
            )
        )

        self.assertEqual(response.reply_type, "workflow_result")
        self.assertEqual(response.status, "completed")
        self.assertIn("A123456", response.reply_text)


if __name__ == "__main__":
    unittest.main()
