"""Сборка JSON-полезной нагрузки для системного промпта по synthesis_input и evidence_pack."""

from __future__ import annotations

import json
import re

from app.modules.agent.runtime.steps.context.contracts import AnswerSynthesisInput, EvidenceBundle

_LAYER_GUIDE = (
    "- C0_SOURCE_CHUNKS: фактический код, это основной источник деталей.\n"
    "- C1_SYMBOL_CATALOG: объявления и сигнатуры символов.\n"
    "- C2_DEPENDENCY_GRAPH: связи вызовов и зависимостей.\n"
    "- C3_ENTRYPOINTS: подтвержденные точки входа.\n"
    "- C4_SEMANTIC_ROLES: вспомогательная роль компонента, использовать осторожно."
)
_TOKEN_RE = re.compile(r"[a-zA-Zа-яА-Я0-9_/]+")


class RuntimePromptPayloadBuilder:
    def build(
        self,
        *,
        user_query: str,
        synthesis_input: AnswerSynthesisInput,
        evidence_pack: EvidenceBundle,
        answer_mode: str,
    ) -> str:
        payload = {
            "user_query": user_query,
            "resolved_scenario": synthesis_input.resolved_scenario,
            "resolved_target": synthesis_input.resolved_target,
            "answer_mode": answer_mode,
            "fast_context": synthesis_input.fast_context,
            "deep_context": synthesis_input.deep_context,
            "evidence_summary": synthesis_input.evidence_summary,
            "semantic_hints": synthesis_input.semantic_hints,
            "diagnostic_hints": synthesis_input.diagnostic_hints,
            "retrieval_summary": evidence_pack.retrieval_summary,
            "confirmed_entrypoints": self._entrypoints(user_query, evidence_pack),
            "required_entrypoints": self._required_entrypoints(user_query, evidence_pack),
            "layer_guide": _LAYER_GUIDE,
        }
        payload.update(self._scenario_payload(synthesis_input))
        return json.dumps(payload, ensure_ascii=False, indent=2)

    def _entrypoints(self, user_query: str, evidence_pack: EvidenceBundle) -> list[dict]:
        query_tokens = self._tokens(user_query)
        enriched: list[dict] = []
        for item in list(evidence_pack.entrypoints or []):
            route_path = str(item.get("route_path") or "").strip()
            http_method = str(item.get("http_method") or "").strip().upper()
            if not http_method:
                continue
            candidate = {
                "http_method": http_method,
                "route_path": route_path,
                "display": f"{http_method} {route_path}".strip(),
                "handler_symbol": str(item.get("handler_symbol") or "").strip(),
                "path": str(item.get("path") or "").strip(),
                "start_line": item.get("start_line"),
                "end_line": item.get("end_line"),
                "query_match": self._matches_query(route_path, query_tokens),
            }
            if candidate not in enriched:
                enriched.append(candidate)
        return sorted(enriched, key=lambda item: (-int(bool(item["query_match"])), -int(bool(item["route_path"])), len(item["route_path"] or "/"), item["display"]))

    def _required_entrypoints(self, user_query: str, evidence_pack: EvidenceBundle) -> list[str]:
        return [item["display"] for item in self._entrypoints(user_query, evidence_pack) if item["query_match"] and item["route_path"]]

    def _scenario_payload(self, synthesis_input: AnswerSynthesisInput) -> dict:
        scenario = (synthesis_input.resolved_scenario or "").upper()
        curated = dict(synthesis_input.curated_facts or {})
        if scenario == "EXPLAIN":
            facts = dict(curated.get("explain") or {})
            must_methods = facts.get("required_methods", [])
            must_calls = facts.get("required_calls", [])
            must_deps = facts.get("required_dependencies", [])
            must_fields = facts.get("required_fields", [])
            fact_gaps = facts.get("fact_gaps", [])
            return {
                "answer_contract": (
                    "Ответ обязан опираться на конкретные факты ниже. Если списки непусты — назови явно методы, вызовы, зависимости или поля из них. "
                    "Не заменяй их общими фразами. Учитывай fact_gaps."
                ),
                "must_mention_methods": must_methods,
                "must_mention_fields": must_fields,
                "must_mention_calls": must_calls,
                "must_mention_dependencies": must_deps,
                "must_mention_constructor_args": facts.get("required_constructor_args", []),
                "must_mention_files": facts.get("required_files", []),
                "must_not_infer_missing_details": True,
                "fact_gaps": fact_gaps,
            }
        if scenario == "ARCHITECTURE":
            facts = dict(curated.get("architecture") or {})
            components = facts.get("required_components", [])
            relations = facts.get("required_relations", [])
            verbs = facts.get("required_relation_verbs", [])
            summaries = facts.get("required_relation_summaries", [])
            return {
                "answer_contract": (
                    "Ответ обязан перечислить компоненты и связи из кода. Используй relation verbs (создаёт, вызывает, импортирует и т.д.). "
                    "Не подменяй архитектуру списком методов. Не используй retrieval labels в тексте."
                ),
                "must_mention_components": components,
                "must_mention_relations": relations,
                "must_mention_relation_summaries": summaries,
                "must_use_relation_verbs": verbs,
                "must_avoid_semantic_labels_as_primary_claims": True,
                "must_not_use_retrieval_labels": facts.get("forbidden_labels", []),
                "fact_gaps": facts.get("fact_gaps", []),
            }
        if scenario == "TRACE_FLOW":
            facts = dict(curated.get("trace_flow") or {})
            steps = facts.get("required_flow_steps", [])
            ordered_descriptions = facts.get("ordered_step_descriptions", [])
            return {
                "answer_contract": (
                    "Ответ обязан описать поток как упорядоченную последовательность шагов из payload. "
                    "Не заявляй полноту потока, если в fact_gaps указано иное. Не делай неподтверждённых утверждений."
                ),
                "must_mention_flow_steps": steps,
                "must_mention_ordered_steps": ordered_descriptions,
                "must_mention_calls": facts.get("required_calls", []),
                "must_mention_sequence_edges": facts.get("required_sequence_edges", []),
                "must_avoid_overclaiming_full_flow": True,
                "fact_gaps": facts.get("fact_gaps", []),
            }
        return {}

    def _matches_query(self, route_path: str, query_tokens: set[str]) -> bool:
        if not route_path:
            return False
        path_tokens = self._tokens(route_path)
        return bool(path_tokens & query_tokens)

    def _tokens(self, value: str) -> set[str]:
        return {token.lower().strip("/") for token in _TOKEN_RE.findall(value or "") if token.strip("/")}
