from __future__ import annotations

from tests.pipeline_setup.suite_02_pipeline.pipeline_intent_rag.helpers.diagnostics import build_prompt_diagnostics


class PromptPayloadBuilder:
    def build(
        self,
        query: str,
        rag_rows: list[dict],
        *,
        prompt_template: dict | None = None,
        sub_intent: str | None = None,
        default_system_prompt: str,
        default_task_prompt: str,
        default_template_id: str,
        system_prompt_version: str,
    ) -> dict:
        template = dict(prompt_template or {})
        system_prompt = str(template.get("system_prompt") or default_system_prompt)
        task_prompt = str(template.get("task_prompt") or default_task_prompt)
        template_id = str(template.get("template_id") or default_template_id)
        context = RagContextFormatter().format(rag_rows, sub_intent=sub_intent)
        user_prompt = (
            f"Вопрос пользователя:\n{query}\n\n"
            f"Правила ответа:\n{task_prompt}\n\n"
            f"Как интерпретировать слои RAG:\n{_LAYER_GUIDE}\n\n"
            f"Контекст RAG:\n{context}\n\n"
            "Ответь пользователю естественным инженерным языком."
        )
        return {
            "system_prompt": system_prompt,
            "user_prompt": user_prompt,
            "sub_intent": str(sub_intent or "").strip() or None,
            "diagnostics": build_prompt_diagnostics(
                system_prompt=system_prompt,
                user_prompt=user_prompt,
                rag_rows=rag_rows,
                prompt_template_id=template_id,
                system_prompt_version=system_prompt_version,
            ),
        }


class RagContextFormatter:
    _DEFAULT_LIMIT = 6
    _QUOTAS = {
        "OPEN_FILE": {"C0_SOURCE_CHUNKS": 4, "C1_SYMBOL_CATALOG": 1, "C4_SEMANTIC_ROLES": 1},
        "EXPLAIN": {
            "C1_SYMBOL_CATALOG": 1,
            "C0_SOURCE_CHUNKS": 2,
            "C2_DEPENDENCY_GRAPH": 1,
            "C4_SEMANTIC_ROLES": 1,
            "C3_ENTRYPOINTS": 1,
        },
        "EXPLAIN_LOCAL": {"C0_SOURCE_CHUNKS": 3, "C1_SYMBOL_CATALOG": 1, "C2_DEPENDENCY_GRAPH": 1, "C4_SEMANTIC_ROLES": 1},
        "FIND_TESTS": {"C1_SYMBOL_CATALOG": 1, "C2_DEPENDENCY_GRAPH": 2, "C0_SOURCE_CHUNKS": 2, "C4_SEMANTIC_ROLES": 1},
        "FIND_ENTRYPOINTS": {"C3_ENTRYPOINTS": 3, "C0_SOURCE_CHUNKS": 2, "C1_SYMBOL_CATALOG": 1},
        "TRACE_FLOW": {"C2_DEPENDENCY_GRAPH": 2, "C3_ENTRYPOINTS": 1, "C0_SOURCE_CHUNKS": 2, "C1_SYMBOL_CATALOG": 1},
        "ARCHITECTURE": {"C4_SEMANTIC_ROLES": 2, "C2_DEPENDENCY_GRAPH": 2, "C3_ENTRYPOINTS": 1, "C1_SYMBOL_CATALOG": 1},
    }

    def format(self, rag_rows: list[dict], *, sub_intent: str | None = None) -> str:
        if not rag_rows:
            return "(пусто)"
        selected = self._select_rows(rag_rows, sub_intent=sub_intent)
        return "\n\n".join(_RagRowFormatter().format(row) for row in selected)

    def _select_rows(self, rag_rows: list[dict], *, sub_intent: str | None) -> list[dict]:
        rows = self._prioritize_rows(rag_rows, sub_intent=sub_intent)
        quotas = dict(self._QUOTAS.get(str(sub_intent or "").strip().upper(), {}))
        if not quotas:
            return rows[: self._DEFAULT_LIMIT]
        selected: list[dict] = []
        counts = {layer: 0 for layer in quotas}
        for row in rows:
            layer = str(row.get("layer") or "")
            if layer not in quotas or counts[layer] >= quotas[layer]:
                continue
            selected.append(row)
            counts[layer] += 1
        if len(selected) >= self._DEFAULT_LIMIT:
            return selected[: self._DEFAULT_LIMIT]
        for row in rows:
            if row in selected:
                continue
            selected.append(row)
            if len(selected) >= self._DEFAULT_LIMIT:
                break
        return selected

    def _prioritize_rows(self, rag_rows: list[dict], *, sub_intent: str | None) -> list[dict]:
        if str(sub_intent or "").strip().upper() != "FIND_ENTRYPOINTS":
            return list(rag_rows)
        ranked = sorted(
            enumerate(rag_rows),
            key=lambda item: (_entrypoint_priority(item[1]), item[0]),
        )
        return [row for _, row in ranked]


class _RagRowFormatter:
    def format(self, row: dict) -> str:
        layer = str(row.get("layer") or "")
        path = str(row.get("path") or "")
        title = str(row.get("title") or "")
        metadata = dict(row.get("metadata") or {})
        content = self._content(row, metadata)
        return f"- [{layer}] {path} | {title}\n{content}"

    def _content(self, row: dict, metadata: dict) -> str:
        if str(row.get("layer") or "") != "C3_ENTRYPOINTS":
            return str(row.get("content") or "")[:1200]
        http_method = str(metadata.get("http_method") or "").strip()
        route_path = str(metadata.get("route_path") or "").strip()
        if not http_method or not route_path:
            return str(row.get("content") or "")[:1200]
        lines = [str(metadata.get("summary_text") or f"{http_method} {route_path}").strip()]
        declaring = str(metadata.get("declaring_symbol") or "").strip()
        handler = str(metadata.get("handler_symbol") or "").strip()
        decorator_text = str(metadata.get("decorator_text") or "").strip()
        if declaring:
            lines.append(f"Declared in: {declaring}")
        if handler:
            lines.append(f"Handler: {handler}")
        if decorator_text:
            lines.append(f"Decorator: {decorator_text}")
        return "\n".join(lines)


def _entrypoint_priority(row: dict) -> tuple[int, int]:
    if str(row.get("layer") or "") != "C3_ENTRYPOINTS":
        return (2, 0)
    metadata = dict(row.get("metadata") or {})
    if str(metadata.get("http_method") or "").strip() and str(metadata.get("route_path") or "").strip():
        return (0, 0)
    if str(metadata.get("entry_type") or "").strip() == "http":
        return (1, 0)
    return (1, 1)


_LAYER_GUIDE = (
    "- C0_SOURCE_CHUNKS: фактический код, это самый надёжный источник деталей реализации.\n"
    "- C1_SYMBOL_CATALOG: объявления символов, сигнатуры и базовая идентификация сущностей.\n"
    "- C2_DEPENDENCY_GRAPH: связи вызовов, зависимостей и потоков между сущностями.\n"
    "- C3_ENTRYPOINTS: подтверждённые точки входа. Если есть http_method и route_path, считай это сильным сигналом реального route.\n"
    "- C4_SEMANTIC_ROLES: эвристическое описание роли компонента; используй как вспомогательный сигнал и не ставь выше фактического кода."
)
