from __future__ import annotations

from app.modules.agent.intent_router_v2.analysis.ambiguity_detector import DocsAmbiguityDetector
from app.modules.agent.intent_router_v2.intent.classifier import IntentClassifierV2
from app.modules.agent.intent_router_v2.analysis.docs_query_signals import DocsQuerySignals
from app.modules.agent.intent_router_v2.intent.llm_disambiguator import DocsLlmDisambiguator
from app.modules.agent.intent_router_v2.intent.conversation_policy import ConversationPolicy
from app.modules.agent.intent_router_v2.retrieval_planning.evidence_policy_factory import EvidencePolicyFactory
from app.modules.agent.intent_router_v2.intent.graph_id_resolver import GraphIdResolver
from app.modules.agent.intent_router_v2.logger import IntentRouterLogger
from app.modules.agent.intent_router_v2.models import ConversationState, IntentRouterResult, RepoContext, SymbolResolution
from app.modules.agent.intent_router_v2.analysis.query_plan_builder import QueryPlanBuilder
from app.modules.agent.intent_router_v2.retrieval_planning.retrieval_constraints_factory import RetrievalConstraintsFactory
from app.modules.agent.intent_router_v2.retrieval_planning.retrieval_spec_factory import RetrievalSpecFactory


class IntentRouterV2:
    def __init__(
        self,
        classifier: IntentClassifierV2 | None = None,
        conversation_policy: ConversationPolicy | None = None,
        query_plan_builder: QueryPlanBuilder | None = None,
        retrieval_factory: RetrievalSpecFactory | None = None,
        constraints_factory: RetrievalConstraintsFactory | None = None,
        evidence_factory: EvidencePolicyFactory | None = None,
        graph_resolver: GraphIdResolver | None = None,
        logger: IntentRouterLogger | None = None,
        ambiguity_detector: DocsAmbiguityDetector | None = None,
        llm_disambiguator: DocsLlmDisambiguator | None = None,
        enable_llm_disambiguation: bool = False,
    ) -> None:
        self._classifier = classifier or IntentClassifierV2()
        self._conversation_policy = conversation_policy or ConversationPolicy()
        self._query_plan_builder = query_plan_builder or QueryPlanBuilder()
        self._retrieval_factory = retrieval_factory or RetrievalSpecFactory()
        self._constraints_factory = constraints_factory or RetrievalConstraintsFactory()
        self._evidence_factory = evidence_factory or EvidencePolicyFactory()
        self._graph_resolver = graph_resolver or GraphIdResolver()
        self._logger = logger or IntentRouterLogger()
        self._ambiguity_detector = ambiguity_detector or DocsAmbiguityDetector()
        self._llm_disambiguator = llm_disambiguator
        self._enable_llm_disambiguation = enable_llm_disambiguation
        self._docs_signals = DocsQuerySignals()

    def route(
        self,
        user_query: str,
        conversation_state: ConversationState | None = None,
        repo_context: RepoContext | None = None,
    ) -> IntentRouterResult:
        state = conversation_state or ConversationState()
        context = repo_context or RepoContext()
        self._logger.log_request(user_query, state, context)
        decision = self._classifier.classify(user_query, state)
        intent, conversation_mode = self._conversation_policy.resolve(decision, user_query, state)
        retrieval_profile = self._resolve_retrieval_profile(intent)
        query_plan = self._query_plan_builder.build(
            user_query,
            state,
            continue_mode=conversation_mode in {"CONTINUE", "FOLLOWUP_LIKELY"},
            conversation_mode=conversation_mode,
            intent=intent,
        )
        ambiguity = self._ambiguity_detector.detect(user_query, intent=intent, sub_intent=query_plan.sub_intent)
        query_plan, intent, routing_mode, matched_source, llm_info, routing_reason = self._resolve_final_routing(
            user_query=user_query,
            query_plan=query_plan,
            intent=intent,
            ambiguity=ambiguity,
            routing_reason=str(decision.reason or ""),
        )
        retrieval_spec = self._retrieval_factory.build(
            intent,
            query_plan.anchors,
            context,
            raw_query=query_plan.raw,
            conversation_state=state,
            conversation_mode=conversation_mode,
            sub_intent=query_plan.sub_intent,
        )
        path_scope = list(getattr(retrieval_spec.filters, "path_scope", []) or [])
        matched_anchor_type, matched_anchor_value = self._docs_signals.detect_anchor(user_query)
        result = IntentRouterResult(
            intent=intent,
            retrieval_profile=retrieval_profile,
            graph_id=self._graph_resolver.resolve(intent),
            conversation_mode=conversation_mode,
            query_plan=query_plan,
            retrieval_spec=retrieval_spec,
            retrieval_constraints=self._constraints_factory.build(
                retrieval_profile=retrieval_profile,
                sub_intent=query_plan.sub_intent,
                raw_query=query_plan.raw,
                anchors=query_plan.anchors,
                path_scope=path_scope,
            ),
            symbol_resolution=self._initial_symbol_resolution(
                retrieval_profile,
                query_plan.sub_intent,
                query_plan.symbol_candidates,
            ),
            evidence_policy=self._evidence_factory.build(
                intent,
                sub_intent=query_plan.sub_intent,
                negations=query_plan.negations,
                has_user_anchor=any(anchor.source == "user_text" for anchor in query_plan.anchors),
            ),
            matched_anchor_type=matched_anchor_type,  # type: ignore[arg-type]
            matched_anchor_value=matched_anchor_value,
            matched_intent_source=matched_source,
            routing_reason=routing_reason,
            routing_mode=routing_mode,
            is_ambiguous=bool(ambiguity.get("is_ambiguous")),
            ambiguity_reason=str(ambiguity.get("ambiguity_reason") or ""),
            deterministic_candidates=list(ambiguity.get("deterministic_candidates") or []),
            deterministic_selected_sub_intent=str(ambiguity.get("deterministic_primary_candidate") or query_plan.sub_intent),
            llm_router_used=bool(llm_info.get("used")),
            llm_router_selected_sub_intent=str(llm_info.get("sub_intent") or ""),
            llm_router_reason=str(llm_info.get("reason") or ""),
            llm_router_confidence=str(llm_info.get("confidence") or ""),
            llm_router_error=str(llm_info.get("error") or ""),
        )
        self._logger.log_result(result)
        return result

    def _initial_symbol_resolution(
        self,
        retrieval_profile: str,
        sub_intent: str,
        symbol_candidates: list[str],
    ) -> SymbolResolution:
        if retrieval_profile in {"docs", "fallback"}:
            return SymbolResolution(status="not_requested")
        if sub_intent == "OPEN_FILE":
            return SymbolResolution(status="not_requested")
        if not symbol_candidates:
            return SymbolResolution(status="not_requested")
        return SymbolResolution(
            status="pending",
            resolved_symbol=None,
            alternatives=list(symbol_candidates),
            confidence=0.0,
        )

    def _resolve_retrieval_profile(self, intent: str) -> str:
        if intent in {"DOCUMENTATION_EXPLAIN", "OPENAPI_GENERATION", "GENERAL_QA"}:
            return "docs"
        if intent == "FALLBACK":
            return "fallback"
        return "code"

    def _resolve_final_routing(
        self,
        *,
        user_query: str,
        query_plan,
        intent: str,
        ambiguity: dict[str, object],
        routing_reason: str,
    ):
        if not bool(ambiguity.get("is_ambiguous")):
            source = "llm" if not str(routing_reason).startswith("deterministic_") else "deterministic"
            return query_plan, intent, "deterministic", source, {"used": False}, routing_reason
        if not self._enable_llm_disambiguation or self._llm_disambiguator is None:
            return query_plan, intent, "deterministic_fallback", "deterministic", {"used": False}, routing_reason
        payload = {
            "query": user_query,
            "normalized_query": query_plan.normalized,
            "deterministic_primary_candidate": str(ambiguity.get("deterministic_primary_candidate") or query_plan.sub_intent),
            "deterministic_candidates": list(ambiguity.get("deterministic_candidates") or []),
            "ambiguity_reason": str(ambiguity.get("ambiguity_reason") or ""),
            "matched_anchor_type": self._docs_signals.detect_anchor(user_query)[0],
            "query_entity_candidates": self._docs_signals.query_entity_candidates(user_query),
            "query_anchor_candidates": self._docs_signals.query_anchor_candidates(user_query),
        }
        try:
            llm_choice = self._llm_disambiguator.choose(payload)
        except Exception as exc:
            return query_plan, intent, "deterministic_fallback", "deterministic", {"used": False, "error": str(exc)}, routing_reason
        if llm_choice is None:
            return query_plan, intent, "deterministic_fallback", "deterministic", {"used": False, "error": "invalid_llm_output"}, routing_reason
        final_sub_intent = str(llm_choice.get("sub_intent") or query_plan.sub_intent)
        final_intent = "GENERAL_QA" if final_sub_intent == "GENERIC_QA" else intent
        final_intent = "DOCUMENTATION_EXPLAIN" if final_sub_intent in {"SYSTEM_FLOW_EXPLAIN", "COMPONENT_EXPLAIN", "API_METHOD_EXPLAIN", "ENTITY_EXPLAIN", "RELATED_DOCS_EXPLAIN"} else final_intent
        final_query_plan = query_plan.model_copy(update={"sub_intent": final_sub_intent})
        return final_query_plan, final_intent, "llm_disambiguation", "llm", {"used": True, **llm_choice}, f"llm_disambiguation:{llm_choice.get('reason') or 'override'}"
