from __future__ import annotations

import re

from app.modules.rag.retrieval.query_terms import extract_query_terms


class ProjectQaSupport:
    def resolve_request(self, message: str) -> dict:
        profile = self.build_profile(message)
        subject = profile["entities"][0] if profile.get("entities") else ""
        return {
            "original_message": message,
            "normalized_message": " ".join((message or "").split()),
            "subject_hint": subject,
            "source_hint": profile["domain"],
            "russian": profile["russian"],
        }

    def build_profile(self, message: str) -> dict:
        lowered = message.lower()
        return {
            "domain": "code" if self.looks_like_code_question(lowered) else "docs",
            "intent": self.detect_intent(lowered),
            "terms": extract_query_terms(message),
            "entities": self.extract_entities(message),
            "russian": self.is_russian(message),
        }

    def build_retrieval_query(self, resolved_request: dict, profile: dict) -> str:
        normalized = str(resolved_request.get("normalized_message") or resolved_request.get("original_message") or "").strip()
        if profile.get("domain") == "code" and "по коду" not in normalized.lower():
            return f"по коду {normalized}".strip()
        return normalized

    def build_source_bundle(self, profile: dict, rag_items: list[dict], files_map: dict[str, dict]) -> dict:
        terms = list(profile.get("terms", []) or [])
        entities = list(profile.get("entities", []) or [])
        explicit_test = any(term in {"test", "tests", "тест", "тесты"} for term in terms)

        ranked_rag: list[tuple[int, dict]] = []
        for item in rag_items:
            score = self.rag_score(item, terms, entities)
            source = str(item.get("source", "") or "")
            if not explicit_test and self.is_test_path(source):
                score -= 3
            if score > 0:
                ranked_rag.append((score, item))
        ranked_rag.sort(key=lambda pair: pair[0], reverse=True)

        ranked_files: list[tuple[int, dict]] = []
        for path, payload in files_map.items():
            score = self.file_score(path, payload, terms, entities)
            if not explicit_test and self.is_test_path(path):
                score -= 3
            if score > 0:
                ranked_files.append(
                    (
                        score,
                        {
                            "path": path,
                            "content": str(payload.get("content", "")),
                            "content_hash": str(payload.get("content_hash", "")),
                        },
                    )
                )
        ranked_files.sort(key=lambda pair: pair[0], reverse=True)

        return {
            "profile": profile,
            "rag_items": [item for _, item in ranked_rag[:12]],
            "file_candidates": [item for _, item in ranked_files[:10]],
            "rag_total": len(ranked_rag),
            "files_total": len(ranked_files),
        }

    def build_answer_brief(self, profile: dict, analysis: dict) -> dict:
        return {
            "question_profile": profile,
            "resolved_subject": analysis.get("subject"),
            "key_findings": analysis.get("findings", []),
            "supporting_evidence": analysis.get("evidence", []),
            "missing_evidence": analysis.get("gaps", []),
            "answer_mode": analysis.get("answer_mode", "summary"),
        }

    def compose_answer(self, brief: dict) -> str:
        profile = brief.get("question_profile", {}) or {}
        russian = bool(profile.get("russian"))
        answer_mode = str(brief.get("answer_mode") or "summary")
        findings = list(brief.get("key_findings", []) or [])
        evidence = list(brief.get("supporting_evidence", []) or [])
        gaps = list(brief.get("missing_evidence", []) or [])

        title = "## Кратко" if russian else "## Summary"
        lines = [title]
        lines.append("### Что реализовано" if answer_mode == "inventory" and russian else "### Implemented items" if answer_mode == "inventory" else "### Что видно по проекту" if russian else "### What the project shows")
        if findings:
            lines.extend(f"- {item}" for item in findings)
        else:
            lines.append("Не удалось собрать подтвержденные выводы по доступным данным." if russian else "No supported findings could be assembled from the available data.")
        if evidence:
            lines.append("")
            lines.append("### Где смотреть в проекте" if russian else "### Where to look in the project")
            lines.extend(f"- `{item}`" for item in evidence[:5])
        if gaps:
            lines.append("")
            lines.append("### Что пока не подтверждено кодом" if russian else "### What is not yet confirmed in code")
            lines.extend(f"- {item}" for item in gaps[:3])
        return "\n".join(lines)

    def detect_intent(self, lowered: str) -> str:
        if any(token in lowered for token in ("какие", "что уже реализ", "список", "перечень", "какие есть")):
            return "inventory"
        if any(token in lowered for token in ("где", "find", "where")):
            return "lookup"
        if any(token in lowered for token in ("сравни", "compare")):
            return "compare"
        return "explain"

    def looks_like_code_question(self, lowered: str) -> bool:
        code_markers = ("по коду", "код", "реализ", "имплементац", "класс", "метод", "модул", "файл", "канал", "handler", "endpoint")
        return any(marker in lowered for marker in code_markers) or bool(re.search(r"\b[A-Z][A-Za-z0-9_]{2,}\b", lowered))

    def extract_entities(self, message: str) -> list[str]:
        return re.findall(r"\b[A-Z][A-Za-z0-9_]{2,}\b", message)[:5]

    def rag_score(self, item: dict, terms: list[str], entities: list[str]) -> int:
        haystacks = [
            str(item.get("source", "") or "").lower(),
            str(item.get("title", "") or "").lower(),
            str(item.get("content", "") or "").lower(),
            str((item.get("metadata", {}) or {}).get("qname", "") or "").lower(),
        ]
        score = 0
        for term in terms:
            if any(term in hay for hay in haystacks):
                score += 3
        for entity in entities:
            if any(entity.lower() in hay for hay in haystacks):
                score += 5
        return score

    def file_score(self, path: str, payload: dict, terms: list[str], entities: list[str]) -> int:
        content = str(payload.get("content", "") or "").lower()
        path_lower = path.lower()
        score = 0
        for term in terms:
            if term in path_lower:
                score += 4
            elif term in content:
                score += 2
        for entity in entities:
            entity_lower = entity.lower()
            if entity_lower in path_lower:
                score += 5
            elif entity_lower in content:
                score += 3
        return score

    def is_test_path(self, path: str) -> bool:
        lowered = path.lower()
        return lowered.startswith("tests/") or "/tests/" in lowered or lowered.startswith("test_") or "/test_" in lowered

    def is_russian(self, text: str) -> bool:
        return any("а" <= ch.lower() <= "я" or ch.lower() == "ё" for ch in text)
