from __future__ import annotations

import unittest

from app.schemas.debug import IntentCandidate, MatcherStageDebug
from app.schemas.intent import IntentDefinition
from app.services.intent_registry import IntentRegistry
from app.services.router import IntentMatchResult, MultiStageIntentMatcher


class _FakeMatcher:
    def __init__(self, stage_debug: MatcherStageDebug) -> None:
        self._stage_debug = stage_debug

    def match(self, text: str) -> IntentMatchResult:
        _ = text
        return IntentMatchResult(intent=None, stage_debug=self._stage_debug)


def _intent(intent_id: str) -> IntentDefinition:
    return IntentDefinition(
        intent_id=intent_id,
        plugin_id=f"mock.{intent_id}",
        domain="test",
        keywords=[],
        examples=[],
    )


class RouterDecisionTests(unittest.TestCase):
    def setUp(self) -> None:
        self.registry = IntentRegistry([_intent("alpha"), _intent("beta"), _intent("gamma")])

    def test_execute_when_bert_classifier_is_clear(self) -> None:
        matcher = MultiStageIntentMatcher(
            registry=self.registry,
            matchers=[
                _FakeMatcher(
                    MatcherStageDebug(
                        stage="classifier",
                        accepted=True,
                        selected_intent="alpha",
                        score=0.92,
                        reason="classifier selected best candidate",
                        backend="joint-bert-local",
                        candidates=[
                            IntentCandidate(intent_id="alpha", score=0.92, reason="classifier", model_name="joint-bert-local"),
                            IntentCandidate(intent_id="beta", score=0.21, reason="classifier", model_name="joint-bert-local"),
                        ],
                    )
                ),
            ],
        )

        result = matcher.match("alpha")

        self.assertEqual(result.debug.decision, "execute")
        self.assertEqual(result.intent.intent_id if result.intent else None, "alpha")

    def test_clarify_when_bert_top_candidates_are_too_close(self) -> None:
        matcher = MultiStageIntentMatcher(
            registry=self.registry,
            matchers=[
                _FakeMatcher(
                    MatcherStageDebug(
                        stage="classifier",
                        accepted=True,
                        selected_intent="alpha",
                        score=0.22,
                        reason="classifier selected best candidate",
                        backend="bert-local",
                        metadata={"threshold": 0.2},
                        candidates=[
                            IntentCandidate(intent_id="alpha", score=0.31, reason="classifier", model_name="bert-local"),
                            IntentCandidate(intent_id="beta", score=0.28, reason="classifier", model_name="bert-local"),
                        ],
                    )
                ),
            ],
            route_to_cloud_threshold=0.2,
        )

        result = matcher.match("ambiguous request")

        self.assertEqual(result.debug.decision, "clarify")
        self.assertIsNone(result.intent)
        self.assertEqual(result.debug.confidence_grade, "medium")

    def test_route_to_cloud_when_bert_signal_is_weak_but_known(self) -> None:
        matcher = MultiStageIntentMatcher(
            registry=self.registry,
            matchers=[
                _FakeMatcher(
                    MatcherStageDebug(
                        stage="classifier",
                        accepted=False,
                        selected_intent="alpha",
                        score=0.29,
                        reason="classifier below execute threshold",
                        backend="joint-bert-local",
                        candidates=[
                            IntentCandidate(intent_id="alpha", score=0.29, reason="classifier", model_name="joint-bert-local"),
                            IntentCandidate(intent_id="beta", score=0.14, reason="classifier", model_name="joint-bert-local"),
                        ],
                    )
                ),
            ],
        )

        result = matcher.match("weak symbolic request")

        self.assertEqual(result.debug.decision, "route_to_cloud")
        self.assertIsNone(result.intent)

    def test_reject_when_no_branch_has_usable_signal(self) -> None:
        matcher = MultiStageIntentMatcher(
            registry=self.registry,
            matchers=[
                _FakeMatcher(
                    MatcherStageDebug(
                        stage="classifier",
                        accepted=False,
                        score=0.12,
                        reason="classifier below threshold",
                        backend="bert-local",
                        metadata={"threshold": 0.2},
                        candidates=[],
                    )
                ),
            ],
        )

        result = matcher.match("unknown request")

        self.assertEqual(result.debug.decision, "reject")
        self.assertTrue(result.debug.unknown_detected)
        self.assertIsNone(result.intent)

    def test_route_to_cloud_for_low_confidence_classifier_only_bert_signal(self) -> None:
        matcher = MultiStageIntentMatcher(
            registry=self.registry,
            matchers=[
                _FakeMatcher(
                    MatcherStageDebug(
                        stage="classifier",
                        accepted=True,
                        selected_intent="alpha",
                        score=0.31,
                        reason="classifier selected best candidate",
                        backend="bert-local",
                        metadata={"threshold": 0.0, "top_margin": 0.04},
                        candidates=[
                            IntentCandidate(intent_id="alpha", score=0.31, reason="classifier", model_name="bert-local"),
                            IntentCandidate(intent_id="beta", score=0.27, reason="classifier", model_name="bert-local"),
                        ],
                    )
                ),
            ],
        )

        result = matcher.match("bert only weak request")

        self.assertEqual(result.debug.decision, "route_to_cloud")
        self.assertIsNone(result.intent)

    def test_execute_for_high_confidence_classifier_only_bert_signal(self) -> None:
        matcher = MultiStageIntentMatcher(
            registry=self.registry,
            matchers=[
                _FakeMatcher(
                    MatcherStageDebug(
                        stage="classifier",
                        accepted=True,
                        selected_intent="alpha",
                        score=0.92,
                        reason="classifier selected best candidate",
                        backend="bert-local",
                        metadata={"threshold": 0.0, "top_margin": 0.63},
                        candidates=[
                            IntentCandidate(intent_id="alpha", score=0.92, reason="classifier", model_name="bert-local"),
                            IntentCandidate(intent_id="beta", score=0.29, reason="classifier", model_name="bert-local"),
                        ],
                    )
                ),
            ],
        )

        result = matcher.match("bert only strong request")

        self.assertEqual(result.debug.decision, "execute")
        self.assertEqual(result.intent.intent_id if result.intent else None, "alpha")


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