import asyncio

from app.modules.agent.engine.orchestrator.models import (
    OutputContract,
    RoutingMeta,
    Scenario,
    TaskConstraints,
    TaskSpec,
)
from app.modules.agent.engine.orchestrator.service import OrchestratorService


class DummyGraph:
    pass


def _task(scenario: Scenario, *, domain_id: str = "project", process_id: str = "qa") -> TaskSpec:
    allow_writes = scenario in {Scenario.DOCS_FROM_ANALYTICS, Scenario.TARGETED_EDIT, Scenario.GHERKIN_MODEL}
    return TaskSpec(
        task_id="task-1",
        dialog_session_id="dialog-1",
        rag_session_id="rag-1",
        mode="auto",
        user_message="Explain this module",
        scenario=scenario,
        routing=RoutingMeta(domain_id=domain_id, process_id=process_id, confidence=0.95, reason="unit-test"),
        constraints=TaskConstraints(allow_writes=allow_writes, max_steps=16, max_retries_per_step=2, step_timeout_sec=90),
        output_contract=OutputContract(result_type="answer"),
        metadata={
            "rag_context": "RAG",
            "confluence_context": "",
            "files_map": {},
        },
    )


def test_orchestrator_service_returns_answer() -> None:
    service = OrchestratorService()

    def graph_resolver(domain_id: str, process_id: str):
        assert domain_id == "default"
        assert process_id == "general"
        return DummyGraph()

    def graph_invoker(_graph, state: dict, dialog_session_id: str):
        assert state["message"] == "Explain this module"
        assert dialog_session_id == "dialog-1"
        return {"answer": "It works.", "changeset": []}

    result = asyncio.run(
        service.run(
            task=_task(Scenario.GENERAL_QA, domain_id="default", process_id="general"),
            graph_resolver=graph_resolver,
            graph_invoker=graph_invoker,
        )
    )
    assert result.answer == "It works."
    assert result.meta["plan"]["status"] == "completed"


def test_orchestrator_service_generates_changeset_for_docs_scenario() -> None:
    service = OrchestratorService()

    def graph_resolver(_domain_id: str, _process_id: str):
        return DummyGraph()

    def graph_invoker(_graph, _state: dict, _dialog_session_id: str):
        return {"answer": "unused", "changeset": []}

    result = asyncio.run(
        service.run(
            task=_task(Scenario.DOCS_FROM_ANALYTICS),
            graph_resolver=graph_resolver,
            graph_invoker=graph_invoker,
        )
    )
    assert result.meta["plan"]["status"] == "completed"
    assert len(result.changeset) > 0


def test_orchestrator_service_uses_project_qa_reasoning_without_graph() -> None:
    service = OrchestratorService()
    requested_graphs: list[tuple[str, str]] = []

    def graph_resolver(domain_id: str, process_id: str):
        requested_graphs.append((domain_id, process_id))
        return DummyGraph()

    def graph_invoker(_graph, state: dict, _dialog_session_id: str):
        if "resolved_request" not in state:
            return {
                "resolved_request": {
                    "original_message": state["message"],
                    "normalized_message": state["message"],
                    "subject_hint": "",
                    "source_hint": "code",
                    "russian": True,
                }
            }
        if "question_profile" not in state:
            return {
                "question_profile": {
                    "domain": "code",
                    "intent": "inventory",
                    "terms": ["control", "channel"],
                    "entities": [],
                    "russian": True,
                }
            }
        if "source_bundle" not in state:
            return {
                "source_bundle": {
                    "profile": state["question_profile"],
                    "rag_items": [],
                    "file_candidates": [
                        {"path": "src/config_manager/v2/control/base.py", "content": "class ControlChannel: pass"},
                        {"path": "src/config_manager/v2/control/http_channel.py", "content": "class HttpControlChannel(ControlChannel): pass # http api"},
                    ],
                    "rag_total": 0,
                    "files_total": 2,
                }
            }
        if "analysis_brief" not in state:
            return {
                "analysis_brief": {
                    "subject": "management channels",
                    "findings": ["В коде найдены конкретные реализации каналов управления: http channel (`src/config_manager/v2/control/http_channel.py`)."],
                    "evidence": ["src/config_manager/v2/control/http_channel.py"],
                    "gaps": [],
                    "answer_mode": "inventory",
                }
            }
        return {
            "answer_brief": {
                "question_profile": state["question_profile"],
                "resolved_subject": "management channels",
                "key_findings": ["В коде найдены конкретные реализации каналов управления: http channel (`src/config_manager/v2/control/http_channel.py`)."],
                "supporting_evidence": ["src/config_manager/v2/control/http_channel.py"],
                "missing_evidence": [],
                "answer_mode": "inventory",
            },
            "final_answer": "## Кратко\n### Что реализовано\n- В коде найдены конкретные реализации каналов управления: http channel (`src/config_manager/v2/control/http_channel.py`).",
        }

    task = _task(Scenario.GENERAL_QA).model_copy(
        update={
            "user_message": "Какие каналы управления уже реализованы?",
            "metadata": {
                "rag_context": "",
                "confluence_context": "",
                "files_map": {
                    "src/config_manager/v2/control/base.py": {
                        "content": "class ControlChannel:\n    async def start(self):\n        ..."
                    },
                    "src/config_manager/v2/control/http_channel.py": {
                        "content": "class HttpControlChannel(ControlChannel):\n    async def start(self):\n        ...\n# http api"
                    },
                },
                "rag_items": [],
            },
        }
    )

    result = asyncio.run(service.run(task=task, graph_resolver=graph_resolver, graph_invoker=graph_invoker))

    assert "Что реализовано" in result.answer
    assert "http channel" in result.answer.lower()
    assert result.meta["plan"]["status"] == "completed"
    assert requested_graphs == [
        ("project_qa", "conversation_understanding"),
        ("project_qa", "question_classification"),
        ("project_qa", "context_retrieval"),
        ("project_qa", "context_analysis"),
        ("project_qa", "answer_composition"),
    ]
