from __future__ import annotations

import re
from dataclasses import dataclass

from app.core.agent.processes.v2.intent_router.modules.target_terms import TargetTermsAnalysis
from app.core.agent.utils.process_v2.models import V2RouteAnchors


@dataclass(slots=True)
class AnchorAnalysis:
    anchors: V2RouteAnchors
    file_markers: list[str]
    architecture_markers: list[str]
    logic_markers: list[str]
    domain_markers: list[str]
    endpoint_markers: list[str]


class _MarkerScanner:
    _FILE_MARKERS = (
        "в каком файле",
        "в каком документе",
        "в каких файлах",
        "где находится",
        "где описан",
        "где описана",
        "где описаны",
        "покажи файл",
        "какие файлы",
        "найди файл",
        "найди файлы",
        "покажи документ",
        "где описано",
        "документ с описанием",
    )
    _ARCHITECTURE_MARKERS = (
        "архитектура",
        "архитектур",
        "architecture",
        "arch overview",
        "как устроено приложение",
        "как устроен сервис",
        "основные части системы",
        "из чего состоит",
    )
    _LOGIC_MARKERS = (
        "цикл",
        "loop",
        "flow",
        "workflow",
        "process",
        "worker",
        "как работает отправка уведомлений",
        "логика отправки",
        "background job",
        "runtime loop",
    )
    _DOMAIN_MARKERS = ("runtime health", "health model", "статусы здоровья", "сущность", "entity", "здоровье runtime")
    _ENDPOINT_MARKERS = (
        "endpoint",
        "api",
        "route",
        "method",
        "метод api",
        "метод",
        "метода",
        "ручка",
        "эндпоинт",
        "маршрут",
        "роут",
    )

    def scan(self, lowered_query: str) -> dict[str, list[str]]:
        return {
            "file_markers": self._matching(lowered_query, self._FILE_MARKERS),
            "architecture_markers": self._matching(lowered_query, self._ARCHITECTURE_MARKERS),
            "logic_markers": self._matching(lowered_query, self._LOGIC_MARKERS),
            "domain_markers": self._matching(lowered_query, self._DOMAIN_MARKERS),
            "endpoint_markers": self._matching(lowered_query, self._ENDPOINT_MARKERS),
        }

    def _matching(self, query: str, markers: tuple[str, ...]) -> list[str]:
        return [marker for marker in markers if marker in query]


class _EntityNameExtractor:
    _ENTITY_RE = re.compile(r"\b[A-Z][A-Za-z0-9_]+\b")
    _IGNORE = {"arch"}

    def extract(self, query: str) -> list[str]:
        items: list[str] = []
        for match in self._ENTITY_RE.finditer(query):
            candidate = match.group(0).strip()
            if candidate and candidate.lower() not in self._IGNORE and candidate not in items:
                items.append(candidate)
        return items


class _FileNameExtractor:
    _TOKEN_RE = re.compile(r"`([^`]+)`|([A-Za-z0-9_./-]+)")
    _WITH_EXTENSION_RE = re.compile(r".+\.(md|yaml|yml|json)$", re.IGNORECASE)
    _DOC_PATH_RE = re.compile(r"^(docs|doc|documentation)/.+")

    def extract(self, query: str) -> list[str]:
        items: list[str] = []
        for match in self._TOKEN_RE.finditer(query):
            candidate = next((item for item in match.groups() if item), "")
            normalized = str(candidate or "").strip().strip("`'\"")
            if self._is_file_name(normalized):
                self._append_unique(items, normalized.lower())
        return items

    def _is_file_name(self, token: str) -> bool:
        if not token:
            return False
        if token.startswith("/") and "." not in token:
            return False
        if self._WITH_EXTENSION_RE.fullmatch(token):
            return True
        return self._DOC_PATH_RE.fullmatch(token) is not None

    def _append_unique(self, items: list[str], value: str) -> None:
        if value and value not in items:
            items.append(value)


class _ProcessAnchorExtractor:
    _DOMAIN_KEYWORDS = {
        "billing": "billing",
        "notifications": "notifications",
    }
    _SUBDOMAIN_KEYWORDS = {
        "invoice": ("billing", "invoice"),
        "invoices": ("billing", "invoice"),
        "delivery_loop": ("notifications", "delivery_loop"),
        "delivery": ("notifications", "delivery_loop"),
    }

    def extract(self, lowered_query: str) -> tuple[str | None, str | None]:
        domain = next((value for token, value in self._DOMAIN_KEYWORDS.items() if token in lowered_query), None)
        subdomain: str | None = None
        for token, mapping in self._SUBDOMAIN_KEYWORDS.items():
            if token in lowered_query:
                domain = domain or mapping[0]
                subdomain = mapping[1]
                break
        return domain, subdomain


class V2AnchorExtractor:
    def __init__(
        self,
        marker_scanner: _MarkerScanner | None = None,
        entity_extractor: _EntityNameExtractor | None = None,
        file_name_extractor: _FileNameExtractor | None = None,
        process_anchor_extractor: _ProcessAnchorExtractor | None = None,
    ) -> None:
        self._marker_scanner = marker_scanner or _MarkerScanner()
        self._entity_extractor = entity_extractor or _EntityNameExtractor()
        self._file_name_extractor = file_name_extractor or _FileNameExtractor()
        self._process_anchor_extractor = process_anchor_extractor or _ProcessAnchorExtractor()

    def extract(self, normalized_query: str, terms: TargetTermsAnalysis) -> AnchorAnalysis:
        lowered_query = normalized_query.lower()
        markers = self._marker_scanner.scan(lowered_query)
        process_domain, process_subdomain = self._process_anchor_extractor.extract(lowered_query)
        anchors = V2RouteAnchors(
            entity_names=self._entity_extractor.extract(normalized_query),
            file_names=self._file_name_extractor.extract(normalized_query),
            endpoint_paths=list(terms.endpoint_paths),
            target_doc_hints=self._target_doc_hints(
                endpoint_paths=terms.endpoint_paths,
                api_like_terms=terms.api_like_terms,
                alias_docs=terms.alias_docs,
                architecture_markers=markers["architecture_markers"],
                logic_markers=markers["logic_markers"],
                domain_markers=markers["domain_markers"],
            ),
            matched_aliases=list(terms.matched_aliases),
            process_domain=process_domain,
            process_subdomain=process_subdomain,
        )
        return AnchorAnalysis(
            anchors=anchors,
            file_markers=markers["file_markers"],
            architecture_markers=markers["architecture_markers"],
            logic_markers=markers["logic_markers"],
            domain_markers=markers["domain_markers"],
            endpoint_markers=markers["endpoint_markers"],
        )

    def _target_doc_hints(
        self,
        *,
        endpoint_paths: list[str],
        api_like_terms: list[str],
        alias_docs: list[str],
        architecture_markers: list[str],
        logic_markers: list[str],
        domain_markers: list[str],
    ) -> list[str]:
        hints = list(alias_docs)
        endpoint_map = {
            "/health": "docs/api/health-endpoint.md",
            "/send": "docs/api/send-message-endpoint.md",
            "/actions/{action}": "docs/api/control-actions-endpoint.md",
        }
        for endpoint in endpoint_paths:
            for hint in self._endpoint_hint_variants(endpoint):
                self._append_unique(hints, hint)
            hint = endpoint_map.get(endpoint)
            self._append_unique(hints, hint)
        for term in api_like_terms:
            for hint in self._api_like_hint_variants(term):
                self._append_unique(hints, hint)
        if architecture_markers:
            self._append_unique(hints, "docs/architecture/telegram-notify-app-overview.md")
        if logic_markers:
            self._append_unique(hints, "docs/logic/telegram-notification-loop.md")
        if domain_markers:
            self._append_unique(hints, "docs/domains/runtime-health-entity.md")
        return hints

    def _endpoint_hint_variants(self, endpoint: str) -> list[str]:
        normalized = str(endpoint or "").strip().lower()
        if not normalized:
            return []
        slug = normalized.strip("/").replace("/", "-").replace("{", "").replace("}", "")
        leaf = next((part for part in reversed(slug.split("-")) if part and part != "id"), "")
        hints: list[str] = [normalized]
        for value in (slug, leaf):
            if not value:
                continue
            hints.extend([value, f"{value}-endpoint", f"{value} endpoint"])
        return list(dict.fromkeys(hints))

    def _api_like_hint_variants(self, term: str) -> list[str]:
        normalized = str(term or "").strip().lower().lstrip("/")
        if not normalized:
            return []
        return [normalized, f"/{normalized}", f"{normalized}-endpoint", f"{normalized} endpoint"]

    def _append_unique(self, items: list[str], value: str | None) -> None:
        normalized = str(value or "").strip()
        if normalized and normalized not in items:
            items.append(normalized)
