from __future__ import annotations

import json
from pathlib import Path

import pytest

from recoil.core.paths import ProjectPaths as CoreProjectPaths
from recoil.execution.step_types import ProjectPaths
from recoil.pipeline.cli import generate
from recoil.pipeline.core.persistence import save_scene, scene_path
from recoil.pipeline.core.take import Beat, Scene
from recoil.pipeline.orchestrator.learning_engine import LearningEngine


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 _write_script() -> Path:
    project_root = CoreProjectPaths.for_project(PROJECT).project_root
    script_dir = project_root / "episodes"
    script_dir.mkdir(parents=True, exist_ok=True)
    script_path = script_dir / "ep_001.md"
    script_path.write_text("# Episode 1\n\nA locked script.\n", encoding="utf-8")
    return script_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 _retry_records() -> list[dict]:
    path = CoreProjectPaths.for_project(PROJECT).learning_dir / "retries.jsonl"
    if not path.exists():
        return []
    return [
        json.loads(line)
        for line in path.read_text(encoding="utf-8").splitlines()
        if line.strip()
    ]


def test_rederive_persists_operator_replan_retry_event(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    _write_script()
    _write_plan()
    _save_locked_scene()
    _set_rederive_argv(monkeypatch, "--reason", "preserve staged board")

    code = generate.main()

    assert code == 0
    records = _retry_records()
    assert len(records) == 1
    record = records[0]
    assert record["event_kind"] == "operator_replan"
    assert record["source"] == "operator"
    assert record["stage_to"] == "scenes"
    assert record["notes"] == "preserve staged board"
    # REC-231 Phase 7: the force_scene_overwrite telemetry key is DELETED.
    assert "force_scene_overwrite" not in record
    # REC-231 Phase 7: a re-derive no longer SKIPS a locked scene — it appends a
    # candidate non-destructively (lock is inert), so nothing is reported skipped.
    assert record["skipped_locked"] == []


def test_rederive_dry_run_does_not_write_retry_event(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    _write_script()
    _write_plan()
    _save_locked_scene()
    _set_rederive_argv(monkeypatch, "--dry-run", "--reason", "probe only")

    code = generate.main()

    assert code == 0
    assert _retry_records() == []


def test_query_strategy_stats_ignores_operator_replan_events() -> None:
    learning = LearningEngine(PROJECT)
    learning.ingest_retry(
        event_kind="strategy_retry",
        failure_mode="identity_drift",
        strategy_applied="add_turnaround_angles",
        succeeded=True,
        cost_usd=1.0,
        latency_s=5.0,
    )
    learning.ingest_retry(
        event_kind="operator_replan",
        failure_mode="identity_drift",
        strategy_applied="operator_noise",
        succeeded=False,
        source="operator",
        stage_to="scenes",
    )
    learning.flush()

    stats = LearningEngine(PROJECT).query_strategy_stats(
        "identity_drift",
        min_samples=1,
    )

    assert set(stats) == {"add_turnaround_angles"}
    assert stats["add_turnaround_angles"].n_samples == 1
    assert stats["add_turnaround_angles"].success_rate == 1.0
