from __future__ import annotations

import json
import re

from app.modules.agent.intent_router_v2.analysis.docs_query_signals import DocsQuerySignals
from app.modules.agent.intent_router_v2.models import ConversationState, IntentDecision
from app.modules.agent.intent_router_v2.protocols import TextGenerator
from app.modules.agent.intent_router_v2.analysis.test_signals import has_test_focus

_CODE_FILE_PATH_RE = re.compile(
    r"\b(?:[\w.-]+/)*[\w.-]+\.(?:py|js|jsx|ts|tsx|java|kt|go|rb|php|c|cc|cpp|h|hpp|cs|swift|rs)(?!\w)\b",
    re.IGNORECASE,
)


class IntentClassifierV2:
    _GENERATE_DOCS_MARKERS = (
        "сгенерируй документац",
        "подготовь документац",
        "создай документац",
        "генерац",
        "generate documentation",
        "write documentation",
    )
    _DOCS_MARKERS = ("документац", "readme", "docs/", ".md", "spec", "runbook", "markdown")
    _OPENAPI_MARKERS = ("openapi", "swagger", "yaml", "spec", "schema")
    _OPENAPI_EXTENDED_MARKERS = ("request body", "response schema", "request schema", "response body")
    _DOCS_RELATED_MARKERS = (
        "что связано",
        "связанные документы",
        "что дальше читать",
        "по каким документам идти",
        "родительский документ",
        "дочерние документы",
        "какие документы",
        "что еще посмотреть",
        "где еще описано",
        "где еще используется",
        "с чем связан",
        "какие страницы связаны",
        "документация по",
        "найди",
        "где описан",
        "где в документации",
        "покажи документы",
        "после этого",
    )
    _DOCS_EXPLAIN_MARKERS = ("объясни", "как работает", "что делает", "что такое")
    _DOCS_FLOW_MARKERS = (
        "цикл",
        "сценар",
        "процесс",
        "происходит",
        "последовательность",
        "шаги",
        "как работает процесс",
        "как происходит",
        "как устроен процесс",
        "workflow",
        "flow",
        "lifecycle",
        "жизненный цикл",
    )
    _DOCS_COMPONENT_MARKERS = (
        "компонент",
        "модул",
        "подсистем",
        "часть системы",
        "блок",
        "роль",
        "какую роль",
        "что делает",
        "как работает компонент",
        "как устроен",
    )
    _DOCS_ENTITY_MARKERS = ("сущност", "entity", "бизнес-объект", "объект", "как используется", "что такое")
    _DOCS_API_MARKERS = ("api", "endpoint", "эндпоинт", "ручк", "request", "response")
    _GENERAL_QA_MARKERS = (
        "помоги понять",
        "с чего начать",
        "как начать",
        "куда смотреть",
        "что тут важно",
        "что вообще",
        "в целом",
        "обзор",
        "какая структура документации",
        "структура документации",
        "какая документация есть",
        "что описано в документации",
        "что есть по сервису",
    )
    _CODE_MARKERS = (
        "по коду",
        "код",
        "класс",
        "метод",
        "функц",
        "модул",
        "пакет",
        "файл",
        "block",
        "блок",
        "handler",
        "endpoint",
        "архитектур",
        "сервис",
        "обработчик",
        "pipeline",
        "бизнес-логика",
    )

    def __init__(self, llm: TextGenerator | None = None) -> None:
        self._llm = llm
        self._docs_signals = DocsQuerySignals()

    def classify(self, user_query: str, conversation_state: ConversationState) -> IntentDecision:
        deterministic = self._deterministic(user_query)
        if deterministic:
            return deterministic
        llm_decision = self._classify_with_llm(user_query, conversation_state)
        if llm_decision:
            return llm_decision
        return IntentDecision(intent="FALLBACK", confidence=0.55, reason="fallback")

    def _deterministic(self, user_query: str) -> IntentDecision | None:
        text = " ".join((user_query or "").lower().split())
        if any(marker in text for marker in self._GENERATE_DOCS_MARKERS):
            return IntentDecision(intent="GENERATE_DOCS_FROM_CODE", confidence=0.97, reason="deterministic_generate_docs")
        if any(marker in text for marker in (*self._OPENAPI_MARKERS, *self._OPENAPI_EXTENDED_MARKERS)):
            return IntentDecision(intent="OPENAPI_GENERATION", confidence=0.98, reason="deterministic_openapi")
        if self._is_general_docs_qa(text):
            return IntentDecision(intent="GENERAL_QA", confidence=0.76, reason="deterministic_general_docs")
        if self._is_docs_explain(text, user_query):
            return IntentDecision(intent="DOCUMENTATION_EXPLAIN", confidence=0.91, reason="deterministic_docs_explain")
        if self._is_related_docs(text):
            return IntentDecision(intent="DOCUMENTATION_EXPLAIN", confidence=0.9, reason="deterministic_docs_related")
        if not has_test_focus(text) and self._docs_signals.has_component_like_token(user_query) and not self._is_general_docs_qa(text):
            return IntentDecision(intent="DOCUMENTATION_EXPLAIN", confidence=0.82, reason="deterministic_docs_component_anchor")
        if self._looks_like_docs_question(text):
            return IntentDecision(intent="DOCUMENTATION_EXPLAIN", confidence=0.9, reason="deterministic_docs")
        if self._looks_like_code_question(user_query, text):
            return IntentDecision(intent="CODE_QA", confidence=0.9, reason="deterministic_code")
        return IntentDecision(intent="GENERAL_QA", confidence=0.62, reason="deterministic_general")

    def _classify_with_llm(self, user_query: str, conversation_state: ConversationState) -> IntentDecision | None:
        if self._llm is None:
            return None
        payload = json.dumps(
            {
                "message": user_query,
                "active_intent": conversation_state.active_intent,
                "last_query": conversation_state.last_query,
                "allowed_intents": [
                    "CODE_QA",
                    "DOCUMENTATION_EXPLAIN",
                    "OPENAPI_GENERATION",
                    "GENERAL_QA",
                    "GENERATE_DOCS_FROM_CODE",
                ],
            },
            ensure_ascii=False,
        )
        try:
            raw = self._llm.generate("rag_intent_router_v2", payload, log_context="rag.intent_router_v2.classify").strip()
        except Exception:
            return None
        parsed = self._parse(raw)
        if parsed is None:
            return None
        return parsed

    def _parse(self, raw: str) -> IntentDecision | None:
        candidate = self._strip_code_fence(raw)
        try:
            payload = json.loads(candidate)
        except json.JSONDecodeError:
            return None
        intent = str(payload.get("intent") or "").strip().upper()
        if intent not in {
            "CODE_QA",
            "DOCUMENTATION_EXPLAIN",
            "OPENAPI_GENERATION",
            "GENERAL_QA",
            "GENERATE_DOCS_FROM_CODE",
            "FALLBACK",
        }:
            return None
        sub_intent = str(payload.get("sub_intent") or "").strip() or None
        return IntentDecision(
            intent=intent,
            sub_intent=sub_intent,
            confidence=float(payload.get("confidence") or 0.7),
            reason=str(payload.get("reason") or "llm").strip() or "llm",
        )

    def _strip_code_fence(self, text: str) -> str:
        if not text.startswith("```"):
            return text
        lines = text.splitlines()
        if len(lines) < 3 or lines[-1].strip() != "```":
            return text
        return "\n".join(lines[1:-1]).strip()

    def _looks_like_docs_question(self, text: str) -> bool:
        if self._has_code_file_path(text):
            return False
        if self._is_general_docs_qa(text):
            return False
        return self._has_docs_context(text) or self._has_docs_subject(text)

    def _has_docs_context(self, text: str) -> bool:
        return any(marker in text for marker in self._DOCS_MARKERS)

    def _is_docs_discovery(self, text: str) -> bool:
        return self._is_related_docs(text)

    def _is_related_docs(self, text: str) -> bool:
        return any(marker in text for marker in self._DOCS_RELATED_MARKERS)

    def _is_docs_explain(self, text: str, user_query: str) -> bool:
        if not any(marker in text for marker in self._DOCS_EXPLAIN_MARKERS):
            return False
        return self._docs_signals.has_docs_anchor(user_query) or self._has_docs_subject(text)

    def _has_docs_subject(self, text: str) -> bool:
        return any(
            marker in text
            for marker in (*self._DOCS_FLOW_MARKERS, *self._DOCS_COMPONENT_MARKERS, *self._DOCS_ENTITY_MARKERS, *self._DOCS_API_MARKERS)
        )

    def _is_general_docs_qa(self, text: str) -> bool:
        return any(marker in text for marker in self._GENERAL_QA_MARKERS)

    def _looks_like_code_question(self, raw_text: str, lowered: str) -> bool:
        if self._has_code_file_path(raw_text):
            return True
        if has_test_focus(lowered):
            return True
        if any(marker in lowered for marker in self._DOCS_MARKERS) and not any(marker in lowered for marker in self._CODE_MARKERS):
            return False
        if any(marker in lowered for marker in self._CODE_MARKERS):
            return True
        return bool(re.search(r"\b[a-z_][A-Za-z0-9_]{2,}\(", raw_text or ""))

    def _has_code_file_path(self, text: str) -> bool:
        return bool(_CODE_FILE_PATH_RE.search(text or ""))
