"""Tests for simulation mode — full end-to-end without LLM calls."""

import json
from pathlib import Path

import pytest

from dpe.engine.orchestrator import Orchestrator
from dpe.models.config import DPEConfig


@pytest.fixture
def sim_config(tmp_path: Path) -> DPEConfig:
    return DPEConfig(simulation_mode=True, output_dir=str(tmp_path))


class TestSimulationRun:
    def test_completes_successfully(self, sim_config: DPEConfig) -> None:
        orch = Orchestrator(config=sim_config)
        manifest = orch.run("A task management app for remote teams")
        assert manifest.stop_reason == "completed"

    def test_produces_artifacts(self, sim_config: DPEConfig) -> None:
        orch = Orchestrator(config=sim_config)
        manifest = orch.run("Test idea")
        # Should produce artifacts for non-skipped stages
        assert len(manifest.artifacts) > 0

    def test_produces_manifest_file(self, sim_config: DPEConfig) -> None:
        orch = Orchestrator(config=sim_config)
        manifest = orch.run("Test idea")
        manifest_path = orch._registry.run_dir / "run_manifest.json"
        assert manifest_path.exists()
        loaded = json.loads(manifest_path.read_text(encoding="utf-8"))
        assert loaded["run_id"] == manifest.run_id
        assert loaded["stop_reason"] == "completed"

    def test_produces_artifact_index(self, sim_config: DPEConfig) -> None:
        orch = Orchestrator(config=sim_config)
        orch.run("Test idea")
        index_path = orch._registry.run_dir / "artifact_index.json"
        assert index_path.exists()

    def test_no_real_model_calls(self, sim_config: DPEConfig) -> None:
        orch = Orchestrator(config=sim_config)
        manifest = orch.run("Test idea")
        for stage in manifest.stages:
            if not stage.skipped:
                assert stage.adapter_used == "simulation"

    def test_budget_tracked(self, sim_config: DPEConfig) -> None:
        orch = Orchestrator(config=sim_config)
        manifest = orch.run("Test idea")
        assert manifest.budget_actuals.total_calls_used > 0

    def test_config_hash_in_manifest(self, sim_config: DPEConfig) -> None:
        orch = Orchestrator(config=sim_config)
        manifest = orch.run("Test idea")
        assert manifest.config_hash == sim_config.config_hash()

    def test_all_artifact_files_exist(self, sim_config: DPEConfig) -> None:
        orch = Orchestrator(config=sim_config)
        manifest = orch.run("Test idea")
        for artifact in manifest.artifacts:
            path = orch._registry.run_dir / artifact.filename
            assert path.exists(), f"Missing artifact: {artifact.filename}"


class TestSimulationWithoutCrossCritique:
    def test_skips_cross_critique(self, tmp_path: Path) -> None:
        cfg = DPEConfig(
            simulation_mode=True,
            output_dir=str(tmp_path),
            enable_cross_critique=False,
        )
        orch = Orchestrator(config=cfg)
        manifest = orch.run("Test idea")
        skipped = [s.stage for s in manifest.stages if s.skipped]
        assert "CROSS_CRITIQUE_A" in skipped
        assert "CROSS_CRITIQUE_B" in skipped


class TestSimulationBudgetEnforcement:
    def test_stops_on_budget_exceeded(self, tmp_path: Path) -> None:
        cfg = DPEConfig(
            simulation_mode=True,
            output_dir=str(tmp_path),
            budget={"max_total_calls": 3},
        )
        orch = Orchestrator(config=cfg)
        manifest = orch.run("Test idea")
        assert manifest.stop_reason == "budget_exceeded"
        assert manifest.budget_actuals.total_calls_used <= 3
