"""Integration tests for EpisodeRunner.run_episode_batches end-to-end (dry-run)."""
from __future__ import annotations

import asyncio
import json
import sys
from pathlib import Path

_REPO_ROOT = Path(__file__).resolve().parents[4]
if str(_REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(_REPO_ROOT))


def _flat_plan() -> dict:
    """Construct a tiny flat plan: 6 shots, 2 locations, 1 episode."""
    return {
        "episode_id": "ep_001",
        "project": "fixture",
        "shots": [
            {"shot_id": "EP001_SH01", "scene_index": 1, "pipeline": "still",
             "model": "gemini-3-pro-image-preview",
             "asset_data": {"location_id": "L1", "characters": []},
             "prompt_data": {"shot_type": "WS"},
             "routing_data": {"target_editorial_duration_s": 3}},
            {"shot_id": "EP001_SH02", "scene_index": 2, "pipeline": "still",
             "model": "gemini-3-pro-image-preview",
             "asset_data": {"location_id": "L1", "characters": []},
             "prompt_data": {"shot_type": "MS"},
             "routing_data": {"target_editorial_duration_s": 4}},
            {"shot_id": "EP001_SH03", "scene_index": 3, "pipeline": "still",
             "model": "gemini-3-pro-image-preview",
             "asset_data": {"location_id": "L1", "characters": []},
             "prompt_data": {"shot_type": "CU"},
             "routing_data": {"target_editorial_duration_s": 3}},
            {"shot_id": "EP001_SH04", "scene_index": 4, "pipeline": "still",
             "model": "gemini-3-pro-image-preview",
             "asset_data": {"location_id": "L2", "characters": []},
             "prompt_data": {"shot_type": "WS"},
             "routing_data": {"target_editorial_duration_s": 5}},
            {"shot_id": "EP001_SH05", "scene_index": 5, "pipeline": "still",
             "model": "gemini-3-pro-image-preview",
             "asset_data": {"location_id": "L2", "characters": []},
             "prompt_data": {"shot_type": "MS"},
             "routing_data": {"target_editorial_duration_s": 4}},
            {"shot_id": "EP001_SH06", "scene_index": 6, "pipeline": "still",
             "model": "gemini-3-pro-image-preview",
             "asset_data": {"location_id": "L2", "characters": []},
             "prompt_data": {"shot_type": "CU"},
             "routing_data": {"target_editorial_duration_s": 3}},
        ],
    }


def test_run_episode_batches_dry_run_emits_two_batches(tmp_path, monkeypatch):
    from recoil.pipeline.orchestrator.episode_runner import EpisodeRunner
    from recoil.pipeline._lib.plan_loader import load_plan

    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(tmp_path / "projects"))
    (tmp_path / "projects" / "fixture").mkdir(parents=True)
    (tmp_path / "projects" / ".recoil-data-root").touch()  # paths-refactor-v2 sentinel (harness pre-flight infra fix)

    plan_path = tmp_path / "plan.json"
    plan_path.write_text(json.dumps(_flat_plan()))
    canonical = load_plan(plan_path)

    runner = EpisodeRunner(
        project="fixture", plan=canonical.raw, casting={},
        max_takes=1, budget_usd=1.0, concurrency=1, episode="ep_001",
    )
    scenes = asyncio.run(runner.run_episode_batches(canonical, dry_run=True))
    assert len(scenes) == 2
    assert scenes[0].scene_id == "BATCH_001"
    assert scenes[1].scene_id == "BATCH_002"


def test_run_episode_batches_below_threshold_falls_back(tmp_path, monkeypatch):
    """A single-shot location should produce a Beat-per-shot fallback."""
    from recoil.pipeline.orchestrator.episode_runner import EpisodeRunner
    from recoil.pipeline._lib.plan_loader import load_plan

    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(tmp_path / "projects"))
    (tmp_path / "projects" / "fixture").mkdir(parents=True)
    (tmp_path / "projects" / ".recoil-data-root").touch()  # paths-refactor-v2 sentinel (harness pre-flight infra fix)

    raw = _flat_plan()
    raw["shots"] = raw["shots"][:4]   # Truncate L2 to 1 shot
    plan_path = tmp_path / "plan.json"
    plan_path.write_text(json.dumps(raw))
    canonical = load_plan(plan_path)

    runner = EpisodeRunner(
        project="fixture", plan=canonical.raw, casting={},
        max_takes=1, budget_usd=1.0, concurrency=1, episode="ep_001",
    )
    scenes = asyncio.run(runner.run_episode_batches(canonical, dry_run=True))
    assert any(b.beat_id == "EP001_SH04" for s in scenes for b in s.beats)


def test_derive_only_dedup_candidate_reuses_same_structure_version(
    tmp_path, monkeypatch
):
    from recoil.pipeline.core.persistence import (
        load_manifest,
        save_scene,
        scene_path,
        scene_version_path,
    )
    from recoil.pipeline.core.take import Beat, Scene
    from recoil.pipeline.orchestrator.episode_runner import EpisodeRunner
    from recoil.pipeline._lib.plan_loader import load_plan

    monkeypatch.setenv("RECOIL_PROJECTS_ROOT", str(tmp_path / "projects"))
    (tmp_path / "projects" / "fixture").mkdir(parents=True)
    (tmp_path / "projects" / ".recoil-data-root").touch()

    plan_path = tmp_path / "plan.json"
    plan_path.write_text(json.dumps(_flat_plan()))
    canonical = load_plan(plan_path)
    save_scene(
        Scene(scene_id="BATCH_001", beats=[Beat(beat_id="OLD")]),
        scene_path("fixture", "ep_001", "BATCH_001"),
    )

    runner = EpisodeRunner(
        project="fixture", plan=canonical.raw, casting={},
        max_takes=1, budget_usd=1.0, concurrency=1, episode="ep_001",
    )
    first = asyncio.run(
        runner.run_episode_batches(
            canonical,
            derive_only=True,
            dedup_candidate=True,
            only_scene_ids={"BATCH_001"},
        )
    )
    second = asyncio.run(
        runner.run_episode_batches(
            canonical,
            derive_only=True,
            dedup_candidate=True,
            only_scene_ids={"BATCH_001"},
        )
    )

    assert first["written"] == ["BATCH_001"]
    assert second["written"] == ["BATCH_001"]
    manifest = load_manifest("fixture", "ep_001", "BATCH_001")
    assert [entry["version"] for entry in manifest["versions"]] == [1, 2]
    assert manifest["active_version"] == 1
    assert scene_version_path("fixture", "ep_001", "BATCH_001", 2).exists()
    assert not scene_version_path("fixture", "ep_001", "BATCH_001", 3).exists()
