import sys
import types

sqlalchemy = types.ModuleType("sqlalchemy")
sqlalchemy.text = lambda value: value
sqlalchemy.create_engine = lambda *args, **kwargs: object()
sys.modules.setdefault("sqlalchemy", sqlalchemy)

sqlalchemy_engine = types.ModuleType("sqlalchemy.engine")
sqlalchemy_engine.Engine = object
sys.modules.setdefault("sqlalchemy.engine", sqlalchemy_engine)

sqlalchemy_orm = types.ModuleType("sqlalchemy.orm")
sqlalchemy_orm.sessionmaker = lambda *args, **kwargs: object()
sys.modules.setdefault("sqlalchemy.orm", sqlalchemy_orm)

sqlalchemy_pool = types.ModuleType("sqlalchemy.pool")
sqlalchemy_pool.NullPool = object
sys.modules.setdefault("sqlalchemy.pool", sqlalchemy_pool)

from app.modules.agent.engine.router.router_service import RouterService
from app.modules.agent.engine.router.schemas import RouteDecision, RouterContext


class _FakeRegistry:
    def is_valid(self, domain_id: str, process_id: str) -> bool:
        return (domain_id, process_id) in {
            ("default", "general"),
            ("project", "qa"),
            ("project", "edits"),
            ("docs", "generation"),
        }

    def get_factory(self, domain_id: str, process_id: str):
        return object()


class _FakeClassifier:
    def __init__(self, decision: RouteDecision | None = None, forced: RouteDecision | None = None) -> None:
        self._decision = decision or RouteDecision(domain_id="project", process_id="qa", confidence=0.95, reason="new_intent")
        self._forced = forced
        self.calls = 0

    def from_mode(self, mode: str) -> RouteDecision | None:
        return self._forced if mode != "auto" else None

    def classify_new_intent(self, user_message: str, context: RouterContext) -> RouteDecision:
        self.calls += 1
        return self._decision


class _FakeContextStore:
    def __init__(self, context: RouterContext) -> None:
        self._context = context
        self.updated: list[dict] = []

    def get(self, conversation_key: str) -> RouterContext:
        return self._context

    def update(self, conversation_key: str, **kwargs) -> None:
        self.updated.append({"conversation_key": conversation_key, **kwargs})


class _FakeSwitchDetector:
    def __init__(self, should_switch: bool) -> None:
        self._should_switch = should_switch

    def should_switch(self, user_message: str, context: RouterContext) -> bool:
        return self._should_switch


def test_router_service_classifies_first_message() -> None:
    service = RouterService(
        registry=_FakeRegistry(),
        classifier=_FakeClassifier(),
        context_store=_FakeContextStore(RouterContext()),
        switch_detector=_FakeSwitchDetector(False),
    )

    route = service.resolve("Объясни как работает endpoint", "dialog-1")

    assert route.domain_id == "project"
    assert route.process_id == "qa"
    assert route.decision_type == "start"


def test_router_service_keeps_current_intent_for_follow_up() -> None:
    context = RouterContext(
        active_intent={"domain_id": "project", "process_id": "qa"},
        last_routing={"domain_id": "project", "process_id": "qa"},
        dialog_started=True,
        turn_index=1,
    )
    classifier = _FakeClassifier(
        decision=RouteDecision(domain_id="docs", process_id="generation", confidence=0.99, reason="should_not_run")
    )
    service = RouterService(
        registry=_FakeRegistry(),
        classifier=classifier,
        context_store=_FakeContextStore(context),
        switch_detector=_FakeSwitchDetector(False),
    )

    route = service.resolve("Покажи подробнее", "dialog-1")

    assert route.domain_id == "project"
    assert route.process_id == "qa"
    assert route.decision_type == "continue"
    assert classifier.calls == 0


def test_router_service_switches_only_on_explicit_new_intent() -> None:
    context = RouterContext(
        active_intent={"domain_id": "project", "process_id": "qa"},
        last_routing={"domain_id": "project", "process_id": "qa"},
        dialog_started=True,
        turn_index=2,
    )
    classifier = _FakeClassifier(
        decision=RouteDecision(domain_id="project", process_id="edits", confidence=0.96, reason="explicit_edit")
    )
    service = RouterService(
        registry=_FakeRegistry(),
        classifier=classifier,
        context_store=_FakeContextStore(context),
        switch_detector=_FakeSwitchDetector(True),
    )

    route = service.resolve("Теперь измени файл README.md", "dialog-1")

    assert route.domain_id == "project"
    assert route.process_id == "edits"
    assert route.decision_type == "switch"
    assert route.explicit_switch is True
    assert classifier.calls == 1


def test_router_service_keeps_current_when_explicit_switch_is_unresolved() -> None:
    context = RouterContext(
        active_intent={"domain_id": "project", "process_id": "qa"},
        last_routing={"domain_id": "project", "process_id": "qa"},
        dialog_started=True,
        turn_index=2,
    )
    classifier = _FakeClassifier(
        decision=RouteDecision(domain_id="docs", process_id="generation", confidence=0.2, reason="low_confidence")
    )
    service = RouterService(
        registry=_FakeRegistry(),
        classifier=classifier,
        context_store=_FakeContextStore(context),
        switch_detector=_FakeSwitchDetector(True),
    )

    route = service.resolve("Теперь сделай что-то другое", "dialog-1")

    assert route.domain_id == "project"
    assert route.process_id == "qa"
    assert route.decision_type == "continue"
    assert route.reason == "explicit_switch_unresolved_keep_current"


def test_router_service_persists_decision_type() -> None:
    store = _FakeContextStore(RouterContext())
    service = RouterService(
        registry=_FakeRegistry(),
        classifier=_FakeClassifier(),
        context_store=store,
        switch_detector=_FakeSwitchDetector(False),
    )

    service.persist_context(
        "dialog-1",
        domain_id="project",
        process_id="qa",
        user_message="Объясни",
        assistant_message="Ответ",
        decision_type="continue",
    )

    assert store.updated[0]["decision_type"] == "continue"
