from __future__ import annotations

from collections import Counter

from app.modules.agent.engine.orchestrator.actions.common import ActionSupport
from app.modules.agent.engine.orchestrator.execution_context import ExecutionContext
from app.modules.agent.engine.orchestrator.models import ArtifactType


class ExplainActions(ActionSupport):
    def collect_sources(self, ctx: ExecutionContext) -> list[str]:
        rag_items = list(ctx.task.metadata.get("rag_items", []) or [])
        rag_context = str(ctx.task.metadata.get("rag_context", ""))
        confluence_context = str(ctx.task.metadata.get("confluence_context", ""))
        files_map = dict(ctx.task.metadata.get("files_map", {}) or {})
        payload = {
            "rag_items": rag_items,
            "rag_context": rag_context,
            "confluence_context": confluence_context,
            "files_count": len(files_map),
            "source_profile": self._source_profile(rag_items),
        }
        evidence_ids: list[str] = []
        for item in rag_items[:5]:
            snippet = str(item.get("content", "") or "").strip()
            if not snippet:
                continue
            evidence_ids.append(
                self.add_evidence(
                    ctx,
                    source_type="rag_chunk",
                    source_ref=str(item.get("source", ctx.task.rag_session_id)),
                    snippet=snippet,
                    score=0.9,
                )
            )
        artifact_id = self.put(
            ctx,
            "sources",
            ArtifactType.STRUCTURED_JSON,
            payload,
            meta={"evidence_ids": evidence_ids},
        )
        return [artifact_id]

    def extract_logic(self, ctx: ExecutionContext) -> list[str]:
        sources = self.get(ctx, "sources", {}) or {}
        message = ctx.task.user_message
        profile = str(sources.get("source_profile", "docs"))
        ru = self._is_russian(message)
        notes = (
            "Используй код как основной источник и ссылайся на конкретные файлы и слои."
            if profile == "code" and ru
            else "Use code as the primary source and cite concrete files/layers."
            if profile == "code"
            else "Используй требования и документацию как основной источник."
            if ru
            else "Use requirements/docs as primary source over code."
        )
        logic = {
            "request": message,
            "assumptions": [f"{profile}-first"],
            "notes": notes,
            "source_summary": sources,
        }
        return [self.put(ctx, "logic_model", ArtifactType.STRUCTURED_JSON, logic)]

    def summarize(self, ctx: ExecutionContext) -> list[str]:
        sources = self.get(ctx, "sources", {}) or {}
        profile = str(sources.get("source_profile", "docs"))
        items = list(sources.get("rag_items", []) or [])
        message = ctx.task.user_message
        ru = self._is_russian(message)
        answer = self._code_answer(items, russian=ru) if profile == "code" else self._docs_answer(items, russian=ru)
        return [self.put(ctx, "final_answer", ArtifactType.TEXT, answer)]

    def _source_profile(self, items: list[dict]) -> str:
        layers = [str(item.get("layer", "") or "") for item in items]
        if any(layer.startswith("C") for layer in layers):
            return "code"
        return "docs"

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

    def _code_answer(self, items: list[dict], *, russian: bool) -> str:
        if not items:
            return (
                "Не удалось найти релевантный кодовый контекст по этому запросу."
                if russian
                else "No relevant code context was found for this request."
            )
        details = self._code_details(items, russian=russian)
        refs = self._code_references(items, russian=russian)
        parts = [
            "## Кратко" if russian else "## Summary",
            details,
        ]
        if refs:
            parts.append(refs)
        return "\n\n".join(part for part in parts if part.strip())

    def _docs_answer(self, items: list[dict], *, russian: bool) -> str:
        return (
            "Запрошенная часть проекта объяснена на основе требований и документации."
            if russian
            else "The requested project part is explained from requirements/docs context."
        )

    def _code_details(self, items: list[dict], *, russian: bool) -> str:
        if not items:
            return ""
        symbol_items = [item for item in items if str(item.get("layer", "")) == "C1_SYMBOL_CATALOG"]
        edge_items = [item for item in items if str(item.get("layer", "")) == "C2_DEPENDENCY_GRAPH"]
        source_items = [item for item in items if str(item.get("layer", "")) == "C0_SOURCE_CHUNKS"]

        lines = ["### Что видно по коду" if russian else "### What the code shows"]
        alias = self._find_alias_symbol(symbol_items)
        if alias:
            imported_from = str(alias.get("metadata", {}).get("lang_payload", {}).get("imported_from", "")).strip()
            if russian:
                lines.append(f"- `ConfigManager` в проекте доступен как alias в `{alias.get('source', '')}` и указывает на `{imported_from}`.")
            else:
                lines.append(f"- `ConfigManager` is exposed as an alias in `{alias.get('source', '')}` and points to `{imported_from}`.")

        management_hint = self._management_summary(symbol_items, edge_items, source_items, russian=russian)
        if management_hint:
            lines.extend(management_hint)

        symbol_lines = 0
        for item in symbol_items[:4]:
            title = str(item.get("title", "") or "")
            source = str(item.get("source", "") or "")
            content = str(item.get("content", "") or "").strip()
            summary = content.splitlines()[-1].strip() if content else ""
            if not title:
                continue
            if self._is_test_path(source):
                continue
            if self._is_control_symbol(title):
                continue
            if russian:
                lines.append(f"- Символ `{title}` из `{source}`: {summary}")
            else:
                lines.append(f"- Symbol `{title}` from `{source}`: {summary}")
            symbol_lines += 1
            if symbol_lines >= 2:
                break

        edge_map: dict[str, list[str]] = {}
        for item in edge_items:
            meta = item.get("metadata", {}) or {}
            src_qname = str(meta.get("src_qname", "") or "").strip()
            dst_ref = str(meta.get("dst_ref", "") or "").strip()
            if not src_qname or not dst_ref:
                continue
            if self._is_test_path(str(item.get("source", "") or "")):
                continue
            edge_map.setdefault(src_qname, [])
            if dst_ref not in edge_map[src_qname]:
                edge_map[src_qname].append(dst_ref)
        for src_qname, targets in list(edge_map.items())[:3]:
            joined = ", ".join(targets[:4])
            if russian:
                lines.append(f"- `{src_qname}` вызывает или использует: {joined}.")
            else:
                lines.append(f"- `{src_qname}` calls or uses: {joined}.")

        for item in source_items[:2]:
            source = str(item.get("source", "") or "")
            content = str(item.get("content", "") or "")
            if self._is_test_path(source):
                continue
            if "management" in content.lower() or "control" in content.lower():
                snippet = " ".join(content.splitlines()[:4]).strip()
                if russian:
                    lines.append(f"- В `{source}` есть прямое указание на управление через конфиг/API: `{snippet[:220]}`")
                else:
                    lines.append(f"- `{source}` directly mentions config/API control: `{snippet[:220]}`")

        return "\n".join(lines)

    def _code_references(self, items: list[dict], *, russian: bool) -> str:
        paths = [str(item.get("source", "") or "") for item in items if item.get("source") and not self._is_test_path(str(item.get("source", "") or ""))]
        if not paths:
            return ""
        lines = ["### Где смотреть в проекте" if russian else "### Where to look in the project"]
        for path, _count in Counter(paths).most_common(3):
            lines.append(f"- `{path}`")
        return "\n".join(lines)

    def _find_alias_symbol(self, items: list[dict]) -> dict | None:
        for item in items:
            meta = item.get("metadata", {}) or {}
            payload = meta.get("lang_payload", {}) or {}
            qname = str(meta.get("qname", "") or "")
            if qname == "ConfigManager" and payload.get("import_alias"):
                return item
        return None

    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_control_symbol(self, title: str) -> bool:
        lowered = title.lower()
        return any(token in lowered for token in ("controlchannel", "controlchannelbridge", "on_start", "on_stop", "on_status"))

    def _management_summary(
        self,
        symbol_items: list[dict],
        edge_items: list[dict],
        source_items: list[dict],
        *,
        russian: bool,
    ) -> list[str]:
        qnames = {str((item.get("metadata", {}) or {}).get("qname", "") or ""): item for item in symbol_items if not self._is_test_path(str(item.get("source", "") or ""))}
        source_texts = [str(item.get("content", "") or "") for item in source_items if not self._is_test_path(str(item.get("source", "") or ""))]
        result: list[str] = []

        if any("управление через api" in text.lower() or "section management" in text.lower() or "секция management" in text.lower() for text in source_texts):
            result.append(
                "- Для `ConfigManager` в коде предусмотрен отдельный интерфейс управления через API/конфиг: это прямо указано в публичной точке входа модуля."
                if russian
                else "- `ConfigManager` has a dedicated API/config-based management interface; this is stated in the module's public entrypoint."
            )

        has_control_channel = "ControlChannel" in qnames
        has_bridge = "ControlChannelBridge" in qnames
        if has_control_channel:
            result.append(
                "- Базовый контракт управления задает `ControlChannel`: он определяет команды `start` и `stop` для внешнего канала управления."
                if russian
                else "- The base management contract is `ControlChannel`, which defines external `start` and `stop` commands."
            )
        if has_bridge:
            result.append(
                "- `ControlChannelBridge` связывает внешний канал управления с lifecycle-методами менеджера: `on_start`, `on_stop`, `on_status`."
                if russian
                else "- `ControlChannelBridge` maps the external control channel to manager lifecycle methods: `on_start`, `on_stop`, `on_status`."
            )

        edge_refs = []
        for item in edge_items:
            if self._is_test_path(str(item.get("source", "") or "")):
                continue
            meta = item.get("metadata", {}) or {}
            src = str(meta.get("src_qname", "") or "")
            dst = str(meta.get("dst_ref", "") or "")
            if src.startswith("ControlChannelBridge.") and dst in {"self._start_runtime", "self._stop_runtime", "self._get_status"}:
                edge_refs.append((src, dst))
        if edge_refs:
            mappings = ", ".join(f"{src} -> {dst}" for src, dst in edge_refs[:3])
            result.append(
                f"- По связям в коде видно, что команды управления маршрутизируются так: {mappings}."
                if russian
                else f"- The code relationships show the management command routing: {mappings}."
            )
        return result
