"""Маршрутизация запроса в домен/интент/subintent и якоря для v2."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import replace

from app.core.agent.processes.v2.intent_router.modules.anchors import V2AnchorExtractor
from app.core.agent.processes.v2.intent_router.modules.normalizer import V2QueryNormalizer
from app.core.agent.processes.v2.intent_router.modules.scope_catalog import DocsScopeCatalog, build_docs_scope_catalog
from app.core.agent.processes.v2.intent_router.modules.scope_resolver import (
    plausible_doc_endpoint_paths,
    promote_target_terms,
    resolve_docs_scope,
)
from app.core.agent.processes.v2.intent_router.modules.target_terms import V2TargetTermsExtractor
from app.core.agent.processes.v2.intent_router.models import QueryFeatures
from app.core.agent.processes.v2.intent_router.routers.confidence import V2ConfidenceAdjuster
from app.core.agent.processes.v2.intent_router.routers.fallback import V2FallbackRouter
from app.core.agent.processes.v2.intent_router.routers.llm import V2LlmRouter
from app.core.agent.processes.v2.intent_router.routers.route_catalog import V2RouteCatalog
from app.core.agent.processes.v2.intent_router.routers.validator import V2RouteValidator
from app.core.agent.utils.process_v2.models import V2RouteResult, V2ScopeType
from app.core.agent.utils.llm import AgentLlmService
from app.core.rag.persistence.query_repository import RagQueryRepository


def _scope_candidate_dict(candidate) -> dict[str, object]:
    return {
        "value": candidate.value,
        "score": candidate.score,
        "source_layer": candidate.source_layer,
        "match_type": candidate.match_type,
    }


class V2IntentRouter:
    def __init__(
        self,
        normalizer: V2QueryNormalizer | None = None,
        target_terms_extractor: V2TargetTermsExtractor | None = None,
        anchor_extractor: V2AnchorExtractor | None = None,
        llm: AgentLlmService | None = None,
        enable_llm_disambiguation: bool = True,
        route_catalog: V2RouteCatalog | None = None,
        confidence_adjuster: V2ConfidenceAdjuster | None = None,
        scope_rows_provider: Callable[[str], list[dict]] | None = None,
    ) -> None:
        self._normalizer = normalizer or V2QueryNormalizer()
        self._target_terms_extractor = target_terms_extractor or V2TargetTermsExtractor()
        self._anchor_extractor = anchor_extractor or V2AnchorExtractor()
        self._catalog = route_catalog or V2RouteCatalog()
        self._validator = V2RouteValidator(self._catalog)
        self._fallback_router = V2FallbackRouter()
        self._confidence_adjuster = confidence_adjuster or V2ConfidenceAdjuster()
        self._enable_llm_disambiguation = enable_llm_disambiguation
        self._llm_router = V2LlmRouter(llm, catalog=self._catalog) if llm is not None else None
        self._scope_rows_provider = scope_rows_provider

    def route(self, user_query: str, *, rag_session_id: str | None = None) -> V2RouteResult:
        normalized_query = self._normalizer.normalize(user_query)
        target_terms_analysis = self._target_terms_extractor.extract(normalized_query)
        sanitized_eps = plausible_doc_endpoint_paths(list(target_terms_analysis.endpoint_paths))
        if sanitized_eps != list(target_terms_analysis.endpoint_paths):
            target_terms_analysis = replace(target_terms_analysis, endpoint_paths=sanitized_eps)
        allowed_paths = set(sanitized_eps)
        target_terms_analysis = replace(
            target_terms_analysis,
            target_terms=[
                t
                for t in target_terms_analysis.target_terms
                if not str(t).startswith("/") or str(t).lower() in allowed_paths
            ],
        )
        raw_target_terms = list(target_terms_analysis.target_terms)
        scope_rows = self._load_scope_rows(rag_session_id)
        scope_catalog: DocsScopeCatalog | None
        if not scope_rows:
            scope_catalog = None
        else:
            scope_catalog = build_docs_scope_catalog(scope_rows)
        resolution = resolve_docs_scope(normalized_query, target_terms_analysis, scope_catalog)
        promoted_terms = promote_target_terms(raw_target_terms, target_terms_analysis, resolution)
        refined_terms = replace(target_terms_analysis, target_terms=promoted_terms)
        anchor_analysis = self._anchor_extractor.extract(normalized_query, refined_terms)
        self._apply_scope_to_anchors(anchor_analysis.anchors, resolution)
        features = QueryFeatures(
            normalized_query=normalized_query,
            target_terms=list(refined_terms.target_terms),
            endpoint_paths=list(refined_terms.endpoint_paths),
            file_names=list(anchor_analysis.anchors.file_names),
            matched_aliases=list(refined_terms.matched_aliases),
            target_doc_hints=list(anchor_analysis.anchors.target_doc_hints),
            file_markers=list(anchor_analysis.file_markers),
            architecture_markers=list(anchor_analysis.architecture_markers),
            logic_markers=list(anchor_analysis.logic_markers),
            domain_markers=list(anchor_analysis.domain_markers),
            endpoint_markers=list(anchor_analysis.endpoint_markers),
            scope_type=resolution.scope_type,
        )
        llm_attempted = self._enable_llm_disambiguation and self._llm_router is not None
        llm_candidate = self._route_with_llm(
            features=features,
            anchors=anchor_analysis.anchors,
        )
        llm_result = self._validator.validate(llm_candidate)
        if llm_result is not None:
            confidence = self._confidence_adjuster.adjust(float(llm_result["confidence"]), features)
            return V2RouteResult(
                routing_domain=llm_result["routing_domain"],
                intent=llm_result["intent"],
                subintent=llm_result["subintent"],
                user_query=user_query,
                normalized_query=features.normalized_query,
                target_terms=features.target_terms,
                anchors=anchor_analysis.anchors,
                confidence=confidence,
                routing_mode="llm_default",
                llm_router_used=True,
                reason_short=str(llm_result["reason_short"]),
                scope_type=resolution.scope_type,
            )
        if llm_attempted:
            return self._fallback_router.route_without_deterministic_signals(
                user_query=user_query,
                features=features,
                anchors=anchor_analysis.anchors,
                scope_type=resolution.scope_type,
            )
        return self._fallback_router.route(
            user_query=user_query,
            features=features,
            anchors=anchor_analysis.anchors,
            llm_attempted=llm_attempted,
            scope_type=resolution.scope_type,
        )

    def _load_scope_rows(self, rag_session_id: str | None) -> list[dict]:
        sid = str(rag_session_id or "").strip()
        if not sid:
            return []
        if self._scope_rows_provider is not None:
            return self._scope_rows_provider(sid)
        try:
            return RagQueryRepository().list_docs_scope_index_rows(sid)
        except Exception:
            return []

    def _apply_scope_to_anchors(self, anchors, resolution) -> None:
        anchors.candidate_domains = list(resolution.candidate_domains)
        anchors.candidate_subdomains = list(resolution.candidate_subdomains)
        anchors.candidate_entities = list(resolution.candidate_entities)
        anchors.candidate_apis = list(resolution.candidate_apis)
        if not resolution.catalog_loaded:
            return
        merged_endpoints = list(dict.fromkeys([*resolution.strong_endpoint_paths, *anchors.endpoint_paths]))
        anchors.endpoint_paths = merged_endpoints
        merged_entities = list(dict.fromkeys([*resolution.strong_entity_names, *anchors.entity_names]))
        anchors.entity_names = merged_entities
        if resolution.strong_domain:
            anchors.process_domain = resolution.strong_domain
        if resolution.strong_subdomain:
            anchors.process_subdomain = resolution.strong_subdomain
        if resolution.scope_type == V2ScopeType.SUBDOMAIN and resolution.strong_domain and resolution.strong_subdomain:
            anchors.process_domain = resolution.strong_domain
            anchors.process_subdomain = resolution.strong_subdomain

    def _route_with_llm(self, *, features: QueryFeatures, anchors) -> dict | None:
        if not self._enable_llm_disambiguation or self._llm_router is None:
            return None
        try:
            return self._llm_router.classify(
                normalized_query=features.normalized_query,
                target_terms=features.target_terms,
                scope_type=features.scope_type,
                anchors={
                    "entity_names": anchors.entity_names,
                    "file_names": anchors.file_names,
                    "endpoint_paths": anchors.endpoint_paths,
                    "target_doc_hints": anchors.target_doc_hints,
                    "matched_aliases": anchors.matched_aliases,
                    "process_domain": anchors.process_domain,
                    "process_subdomain": anchors.process_subdomain,
                    "candidate_domains": [_scope_candidate_dict(c) for c in anchors.candidate_domains],
                    "candidate_subdomains": [_scope_candidate_dict(c) for c in anchors.candidate_subdomains],
                    "candidate_entities": [_scope_candidate_dict(c) for c in anchors.candidate_entities],
                    "candidate_apis": [_scope_candidate_dict(c) for c in anchors.candidate_apis],
                },
            )
        except Exception:
            return None
