from __future__ import annotations

import json
from pathlib import Path

import pytest

from recoil.pipeline.cli import generate
from recoil.pipeline.core.persistence import scene_path


PROJECT = "fixture"


@pytest.fixture(autouse=True)
def _projects_root(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    root = tmp_path / "projects"
    root.mkdir()
    (root / ".recoil-data-root").touch()
    project_root = root / PROJECT
    project_root.mkdir()
    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(root))
    return project_root


def _shot(batch_index: int, shot_index: int) -> dict:
    ordinal = (batch_index - 1) * 3 + shot_index
    return {
        "shot_id": f"EP001_SH{ordinal:02d}",
        "scene_index": batch_index,
        "pipeline": "video",
        "video_model": "seeddance-2.0",
        "asset_data": {
            "characters": [],
            "location_id": f"LOC_{batch_index}",
        },
        "prompt_data": {"shot_type": "MS"},
        "routing_data": {
            "target_editorial_duration_s": 2.0,
            "is_env_only": False,
            "has_dialogue": False,
        },
        "aspect_ratio": "9:16",
    }


def _plan_payload() -> dict:
    return {
        "episode_id": "ep_001",
        "project": PROJECT,
        "shots": [
            _shot(batch_index, shot_index)
            for batch_index in range(1, 5)
            for shot_index in range(1, 4)
        ],
    }


def _set_rederive_argv(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(
        generate.sys,
        "argv",
        [
            "generate.py",
            "rederive",
            "--project",
            PROJECT,
            "--episode",
            "1",
        ],
    )


def _stub_front_stages(
    monkeypatch: pytest.MonkeyPatch,
    events: list[str],
) -> None:
    def fake_run_camera_test(self, episode_num: int):  # noqa: ANN001
        events.append(f"camera:{episode_num}")
        return None

    def fake_load_bible(self):  # noqa: ANN001
        return {"stub": "bible"}

    def fake_run_storyboard_pass(self, episode: int, bible):  # noqa: ANN001
        events.append(f"plan:{episode}")
        assert bible == {"stub": "bible"}
        plan_path = self.plans_dir / "ep_001_plan.json"
        plan_path.write_text(
            json.dumps(_plan_payload(), indent=2),
            encoding="utf-8",
        )
        return {"sentinel": "plan"}

    monkeypatch.setattr(
        generate.IngestPipeline,
        "run_camera_test",
        fake_run_camera_test,
    )
    monkeypatch.setattr(generate.IngestPipeline, "_load_bible", fake_load_bible)
    monkeypatch.setattr(
        generate.IngestPipeline,
        "run_storyboard_pass",
        fake_run_storyboard_pass,
    )


def test_rederive_gate_clean_proceeds_to_stage_c(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    events: list[str] = []
    _stub_front_stages(monkeypatch, events)

    def fake_run_gate(project: str, episode: int) -> int:
        events.append(f"gate:{project}:{episode}")
        print("coverage_report=/tmp/coverage.json")
        print("proposal=None")
        print("BLOCK=0 WARN=0")
        return 0

    monkeypatch.setattr(generate, "_run_gate", fake_run_gate)
    _set_rederive_argv(monkeypatch)

    code = generate.main()

    assert code == 0
    assert events == ["camera:1", "plan:1", "gate:fixture:1"]
    assert scene_path(PROJECT, "ep_001", "BATCH_001").exists()


def test_rederive_gate_blocked_exits_three_before_stage_c(
    monkeypatch: pytest.MonkeyPatch,
    capsys: pytest.CaptureFixture[str],
) -> None:
    events: list[str] = []
    _stub_front_stages(monkeypatch, events)
    proposal_path = "/tmp/fixture/ep_001_bible_proposal.json"

    def fake_run_gate(project: str, episode: int) -> int:
        events.append(f"gate:{project}:{episode}")
        print("coverage_report=/tmp/coverage.json")
        print(f"proposal={proposal_path}")
        print("BLOCK=1 WARN=0")
        return 3

    monkeypatch.setattr(generate, "_run_gate", fake_run_gate)
    _set_rederive_argv(monkeypatch)

    code = generate.main()
    captured = capsys.readouterr()

    assert code == 3
    assert events == ["camera:1", "plan:1", "gate:fixture:1"]
    assert f"proposal={proposal_path}" in captured.out
    assert "rederive: coverage gate BLOCKED" in captured.out
    assert "--skip-camera-test --skip-plan --skip-extract" in captured.out
    assert not scene_path(PROJECT, "ep_001", "BATCH_001").exists()
