from __future__ import annotations

from datetime import datetime
from pathlib import Path
from types import SimpleNamespace

from tests.pipeline_setup.suite_02_pipeline.pipeline_intent_rag.helpers.models import PhraseCase, PipelineResult
from tests.pipeline_setup.suite_02_pipeline.pipeline_intent_rag.helpers.runtime import PipelineRuntime


class _FakeAnswerer:
    def build_prompt_payload(
        self,
        query: str,
        rag_rows: list[dict],
        *,
        prompt_template: dict | None = None,
        sub_intent: str | None = None,
    ) -> dict:
        return {
            "system_prompt": "sys",
            "user_prompt": f"q={query} rows={len(rag_rows)} sub={sub_intent}",
            "diagnostics": {
                "prompt_stats": {"tokens_in_estimate": 10, "evidence_rows": len(rag_rows), "evidence_chars": 100},
                "evidence_summary": [],
                "prompt_template_id": str((prompt_template or {}).get("template_id") or "t"),
                "system_prompt_version": "v1",
            },
        }

    def answer_from_payload(self, payload: dict) -> str:
        return "ok"


class _ShouldNotBeCalledAnswerer:
    def __init__(self) -> None:
        raise AssertionError("LLM should not be instantiated for missing OPEN_FILE")


class _FakeModel:
    def __init__(self, **payload) -> None:
        self.__dict__.update(payload)

    def model_dump(self) -> dict:
        return dict(self.__dict__)


def _fake_executor_result(
    *,
    query: str,
    sub_intent: str,
    rag_rows: list[dict],
    final_answer: str,
    answer_mode: str,
    llm_used: bool,
    validation_reasons: list[str],
    path_scope: list[str] | None = None,
    symbol_candidates: list[str] | None = None,
    symbol_resolution: dict | None = None,
    layers: list[str] | None = None,
) -> SimpleNamespace:
    return SimpleNamespace(
        final_answer=final_answer,
        answer_mode=answer_mode,
        llm_used=llm_used,
        validation=SimpleNamespace(reasons=validation_reasons),
        retrieval_result=SimpleNamespace(raw_rows=rag_rows),
        router_result=SimpleNamespace(
            query_plan=SimpleNamespace(symbol_candidates=symbol_candidates or []),
            symbol_resolution=_FakeModel(**(symbol_resolution or {"status": "not_requested", "resolved_symbol": None, "alternatives": [], "confidence": 0.0})),
        ),
        diagnostics=SimpleNamespace(
            router_result={
                "intent": "CODE_QA",
                "graph_id": "CodeQAGraph",
                "conversation_mode": "START",
                "sub_intent": sub_intent,
            },
            retrieval_request={
                "query": query,
                "path_scope": path_scope or [],
                "requested_layers": layers or [],
            },
            timings_ms={},
        ),
    )


def test_full_chain_writes_single_record_with_steps(monkeypatch) -> None:
    test_root = Path(__file__).resolve().parent
    runtime = PipelineRuntime(mode="full_chain", test_name="test_runtime_full_chain_output", test_root=test_root)
    case = PhraseCase(case_id="c1", text="Открой файл src/mail_order_bot/context.py", expected_intent="CODE_QA")
    rag_rows = [
        {
            "path": "src/mail_order_bot/context.py",
            "layer": "C0_SOURCE_CHUNKS",
            "title": "src/mail_order_bot/context.py:1-10",
            "content": "class Context: ...",
            "metadata": {},
        }
    ]
    writes: list[dict] = []

    monkeypatch.setattr(runtime._writer, "write_record", lambda payload: writes.append(payload))
    monkeypatch.setattr(
        runtime,
        "_executor_instance",
        lambda: SimpleNamespace(
            execute=lambda **_: _fake_executor_result(
                query=case.text,
                sub_intent="OPEN_FILE",
                rag_rows=rag_rows,
                final_answer="ok",
                answer_mode="normal",
                llm_used=True,
                validation_reasons=[],
                path_scope=["src/mail_order_bot/context.py"],
                layers=["C0_SOURCE_CHUNKS"],
            )
        ),
    )

    runtime.run_full_chain(case)

    assert len(writes) == 1
    payload = writes[0]
    assert payload["mode"] == "full_chain"
    assert payload["summary"]["llm"]["answer_status"] == "answered"
    assert payload["run_info"]["case_id"] == case.case_id
    assert payload["input_request"]["text"] == case.text
    assert [item["step"] for item in payload["steps"]] == ["intent_router", "retrieval", "llm_answer"]


def test_full_chain_short_circuits_missing_open_file(monkeypatch) -> None:
    test_root = Path(__file__).resolve().parent
    runtime = PipelineRuntime(mode="full_chain", test_name="test_runtime_full_chain_output", test_root=test_root)
    case = PhraseCase(case_id="missing", text="Открой файл src/app_runtime/core/missing_runtime.py", expected_intent="CODE_QA")
    writes: list[dict] = []

    monkeypatch.setattr(runtime._writer, "write_record", lambda payload: writes.append(payload))
    monkeypatch.setattr(
        runtime,
        "_executor_instance",
        lambda: SimpleNamespace(
            execute=lambda **_: _fake_executor_result(
                query=case.text,
                sub_intent="OPEN_FILE",
                rag_rows=[],
                final_answer="Файл src/app_runtime/core/missing_runtime.py не найден.",
                answer_mode="degraded",
                llm_used=False,
                validation_reasons=["file_not_found"],
                path_scope=["src/app_runtime/core/missing_runtime.py"],
                layers=["C0_SOURCE_CHUNKS"],
            )
        ),
    )

    result = runtime.run_full_chain(case)

    assert result.llm_answer == "Файл src/app_runtime/core/missing_runtime.py не найден."
    assert len(writes) == 1
    payload = writes[0]
    assert payload["summary"]["llm"]["answer_status"] == "degraded"
    assert payload["diagnostics"]["answer_policy"]["failure_reason"] == "file_not_found"


def test_full_chain_short_circuits_missing_explain_symbol(monkeypatch) -> None:
    test_root = Path(__file__).resolve().parent
    runtime = PipelineRuntime(mode="full_chain", test_name="test_runtime_full_chain_output", test_root=test_root)
    case = PhraseCase(case_id="missing-symbol", text="Объясни класс RuntimeFactoryManager", expected_intent="CODE_QA")
    writes: list[dict] = []

    monkeypatch.setattr(runtime._writer, "write_record", lambda payload: writes.append(payload))
    monkeypatch.setattr(
        runtime,
        "_executor_instance",
        lambda: SimpleNamespace(
            execute=lambda **_: _fake_executor_result(
                query=case.text,
                sub_intent="EXPLAIN",
                rag_rows=[],
                final_answer="Сущность RuntimeFactoryManager не найдена. Ближайшие варианты: RuntimeManager, WorkflowRuntimeFactory.",
                answer_mode="degraded",
                llm_used=False,
                validation_reasons=["symbol_not_found"],
                symbol_candidates=["RuntimeFactoryManager"],
                symbol_resolution={
                    "status": "not_found",
                    "resolved_symbol": None,
                    "alternatives": ["RuntimeManager", "WorkflowRuntimeFactory"],
                    "confidence": 0.0,
                },
            )
        ),
    )

    result = runtime.run_full_chain(case)

    assert result.llm_answer.startswith("Сущность RuntimeFactoryManager не найдена")
    assert "исходя из названия" not in result.llm_answer
    assert len(writes) == 1
    payload = writes[0]
    assert payload["summary"]["llm"]["answer_status"] == "degraded"
    assert payload["diagnostics"]["answer_policy"]["failure_reason"] == "symbol_not_found"


def test_full_chain_synthesizes_confirmed_http_entrypoints(monkeypatch) -> None:
    test_root = Path(__file__).resolve().parent
    runtime = PipelineRuntime(mode="full_chain", test_name="test_runtime_full_chain_output", test_root=test_root)
    case = PhraseCase(case_id="entrypoints", text="Где health endpoint?", expected_intent="CODE_QA")
    rag_rows = [
        {
            "layer": "C3_ENTRYPOINTS",
            "path": "src/app_runtime/control/http_app.py",
            "title": "GET /health declared in HttpControlAppFactory",
            "content": "",
            "metadata": {
                "http_method": "GET",
                "route_path": "/health",
                "declaring_symbol": "HttpControlAppFactory",
                "handler_symbol": "HttpControlAppFactory.create.health",
            },
        }
    ]
    writes: list[dict] = []

    monkeypatch.setattr(runtime._writer, "write_record", lambda payload: writes.append(payload))
    monkeypatch.setattr(
        runtime,
        "_executor_instance",
        lambda: SimpleNamespace(
            execute=lambda **_: _fake_executor_result(
                query=case.text,
                sub_intent="FIND_ENTRYPOINTS",
                rag_rows=rag_rows,
                final_answer="GET /health — объявлен в HttpControlAppFactory, обрабатывается health",
                answer_mode="normal",
                llm_used=True,
                validation_reasons=[],
                layers=["C3_ENTRYPOINTS"],
            )
        ),
    )

    result = runtime.run_full_chain(case)

    assert result.llm_answer == "GET /health — объявлен в HttpControlAppFactory, обрабатывается health"
    assert len(writes) == 1
    assert writes[0]["summary"]["llm"]["answer_status"] == "answered"
