from __future__ import annotations

import json
from pathlib import Path

import pytest

from recoil.execution.step_types import ProjectPaths
from recoil.pipeline.cli import generate
from recoil.pipeline.core.persistence import (
    load_manifest,
    load_scene,
    load_scene_active,
    save_scene,
    scene_path,
)
from recoil.pipeline.core.take import Beat, Scene


PROJECT = "fixture"
BATCH_ID = "BATCH_004"


@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) * 4 + shot_index
    return {
        "shot_id": f"EP001_SH{ordinal:02d}",
        "scene_index": ordinal,
        "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 _write_plan() -> Path:
    paths = ProjectPaths.for_episode(PROJECT, 1)
    paths.plans_dir.mkdir(parents=True, exist_ok=True)
    plan_path = paths.plans_dir / "ep_001_plan.json"
    shots = [
        _shot(batch_index, shot_index)
        for batch_index in range(1, 5)
        for shot_index in range(1, 5)
    ]
    plan_path.write_text(
        json.dumps(
            {
                "episode_id": "ep_001",
                "project": PROJECT,
                "shots": shots,
            },
            indent=2,
        ),
        encoding="utf-8",
    )
    return plan_path


def _save_locked_scene() -> Path:
    scene = Scene(
        scene_id=BATCH_ID,
        beats=[
            Beat(
                beat_id=f"OLD_{BATCH_ID}",
                beat_metadata={"scene_id": BATCH_ID, "marker": "locked"},
            )
        ],
        locked=True,
        lock_reason="staged storyboard",
        locked_by="JT",
        locked_at="2026-06-13T00:00:00Z",
    )
    path = scene_path(PROJECT, "ep_001", BATCH_ID)
    save_scene(scene, path)
    return path


def _set_rederive_argv(monkeypatch: pytest.MonkeyPatch, *extra: str) -> None:
    monkeypatch.setattr(
        generate.sys,
        "argv",
        [
            "generate.py",
            "rederive",
            "--project",
            PROJECT,
            "--episode",
            "1",
            "--skip-camera-test",
            "--skip-plan",
            "--skip-extract",
            *extra,
        ],
    )


def test_rederive_locked_scene_appends_candidate(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """REC-231 Phase 7: a re-derive of a LOCKED scene APPENDS an immutable candidate
    version and leaves the active (locked) body byte-untouched. ``scene.locked`` no
    longer skips or gates the write (the force/clobber overwrite path is DELETED), and
    the pointer never moves on an append, so ``load_scene_active`` still returns the
    preserved locked body."""
    _write_plan()
    locked_path = _save_locked_scene()
    locked_before = locked_path.read_bytes()
    _set_rederive_argv(monkeypatch)

    code = generate.main()

    assert code == 0
    # The active (locked v1) body is byte-untouched.
    assert locked_path.read_bytes() == locked_before
    active = load_scene_active(PROJECT, "ep_001", BATCH_ID)
    assert active.locked is True
    assert active.beats[0].beat_id == f"OLD_{BATCH_ID}"
    # A candidate (v2) was appended; the pointer never moved off the locked v1.
    manifest = load_manifest(PROJECT, "ep_001", BATCH_ID)
    assert manifest is not None
    assert manifest["active_version"] == 1
    versions = {v["version"]: v for v in manifest["versions"]}
    assert versions[1]["source"] == "legacy_flat"
    assert versions[2]["state"] == "candidate"


def test_rederive_writes_unlocked_scenes(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    _write_plan()
    _set_rederive_argv(monkeypatch)

    code = generate.main()

    assert code == 0
    path = scene_path(PROJECT, "ep_001", "BATCH_001")
    assert path.exists()
    scene = load_scene(path)
    assert scene.scene_id == "BATCH_001"
    assert scene.beats[0].beat_metadata["modality"] == "r2v_multi"
