"""Tests for canonical shot-label and atomic next-take-number.

Note: `_make_shot_label_for_video` was removed in the Bug E fix
(2026-05-19). Video filenames now use the canonical video_naming
grammar — see `recoil/tests/test_video_naming.py` for that surface.
"""
import pytest
from pathlib import Path
from recoil.execution.step_runner import _make_shot_label_for_keyframe, _make_shot_label_canonical


def test_canonical_shot_label_keyframe_format():
    """Keyframe filename: {PROJECT}_{EP}_S{NN}_take{N}.png"""
    label = _make_shot_label_for_keyframe("EP001_SH03", project="TARTARUS")
    assert label == "TARTARUS_EP001_S03"


def test_canonical_shot_label_canonical_video_is_none():
    """After Bug E fix, the canonical dict returns None for video."""
    d = _make_shot_label_canonical("EP001_SH03", project="TARTARUS")
    assert d["keyframe"] == "TARTARUS_EP001_S03"
    assert d["video"] is None


def test_next_take_number_finds_existing_keyframes(tmp_path):
    """The keyframe filesystem scan must actually find existing keyframes."""
    from recoil.execution.step_runner import StepRunner
    from recoil.execution.step_types import ProjectPaths
    from unittest.mock import MagicMock

    paths = ProjectPaths(
        project="tartarus", project_root=tmp_path,
        frames_dir=tmp_path / "frames", video_dir=tmp_path / "video",
        plans_dir=tmp_path / "plans", previs_dir=tmp_path / "previs",
    )
    for d in [paths.frames_dir, paths.video_dir, paths.plans_dir, paths.previs_dir]:
        d.mkdir()

    # Pre-populate with existing takes (the names _save_keyframe actually writes)
    (paths.frames_dir / "TARTARUS_EP001_S03_take1.png").write_bytes(b"x")
    (paths.frames_dir / "TARTARUS_EP001_S03_take2.png").write_bytes(b"x")
    (paths.frames_dir / "TARTARUS_EP001_S03_take3.png").write_bytes(b"x")

    store = MagicMock()
    store.get_shot.return_value = None  # store is empty — must rely on filesystem
    # ExecutionStore.acquire_next_take_number doesn't exist on the mock by default,
    # so the fallback path runs (filesystem scan)
    if hasattr(store, "acquire_next_take_number"):
        del store.acquire_next_take_number
    runner = StepRunner(store=store, paths=paths)

    next_take = runner._next_take_number("EP001_SH03")
    # Should be 4 — find the existing 3 takes via filesystem
    assert next_take == 4


def test_next_take_number_atomic_under_concurrency(tmp_path):
    """Two parallel _next_take_number calls must not collide."""
    import threading
    from recoil.execution.step_runner import StepRunner
    from recoil.execution.step_types import ProjectPaths
    from unittest.mock import MagicMock

    paths = ProjectPaths(
        project="tartarus", project_root=tmp_path,
        frames_dir=tmp_path / "frames", video_dir=tmp_path / "video",
        plans_dir=tmp_path / "plans", previs_dir=tmp_path / "previs",
    )
    for d in [paths.frames_dir, paths.video_dir, paths.plans_dir, paths.previs_dir]:
        d.mkdir()

    store = MagicMock()
    store.get_shot.return_value = None
    # Mock acquire_next_take_number to use a thread-safe counter
    counter = {"n": 0}
    lock = threading.Lock()
    def fake_acquire(shot_id):
        with lock:
            counter["n"] += 1
            return counter["n"]
    store.acquire_next_take_number = fake_acquire

    runner = StepRunner(store=store, paths=paths)

    results = []
    def worker():
        results.append(runner._next_take_number("EP001_SH03"))

    threads = [threading.Thread(target=worker) for _ in range(10)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    # All 10 results must be unique — no collisions
    assert len(set(results)) == 10
