import subprocess
import tempfile
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch

from recoil.execution.step_runner import make_video_identity_gate


def test_video_identity_gate_delegates_frame_to_gate_2_and_keeps_retriable_failure(tmp_path):
    video_path = tmp_path / "take.mp4"
    video_path.write_bytes(b"video")
    ref_path = tmp_path / "ref.png"
    ref_path.write_bytes(b"ref")
    frame_path = Path(tempfile.gettempdir()) / "take_idgate.jpg"

    validator_result = SimpleNamespace(
        passed=False,
        details={"total_score": 3},
        cost=0.039,
    )

    with (
        patch("subprocess.run") as run_ffmpeg,
        patch("recoil.pipeline._lib.validation.Validator") as validator_cls,
    ):
        validator_cls.return_value.run_gate_2.return_value = validator_result
        gate = make_video_identity_gate(ref_paths=[ref_path], extract_at_s=0.5)
        verdict = gate(video_path, {"shot_id": "SH01"})

    run_ffmpeg.assert_called_once_with(
        [
            "ffmpeg",
            "-y",
            "-ss",
            "0.500",
            "-i",
            str(video_path),
            "-frames:v",
            "1",
            "-q:v",
            "2",
            str(frame_path),
        ],
        capture_output=True,
        timeout=30,
        check=True,
    )
    validator_cls.return_value.run_gate_2.assert_called_once()
    assert validator_cls.return_value.run_gate_2.call_args.kwargs["keyframe_path"] == frame_path
    assert verdict.passed is False
    assert verdict.gate_name == "gate_2_video"
    assert verdict.details == {"total_score": 3}
    assert verdict.retriable is True


def test_video_identity_gate_scores_image_input_directly_without_ffmpeg(tmp_path):
    """REC-63 gap fix: execute_pass passes an already-extracted boundary FRAME
    (jpg), not a video. The gate must score it directly via run_gate_2 and NOT
    run ffmpeg (which would fail on a still and fail-open → toothless gate on
    the r2v_multi path)."""
    frame_path = tmp_path / "boundary_seg00.jpg"
    frame_path.write_bytes(b"jpg")
    ref_path = tmp_path / "ref.png"
    ref_path.write_bytes(b"ref")

    validator_result = SimpleNamespace(
        passed=False,
        details={"total_score": 3},
        cost=0.039,
    )

    with (
        patch("subprocess.run") as run_ffmpeg,
        patch("recoil.pipeline._lib.validation.Validator") as validator_cls,
    ):
        validator_cls.return_value.run_gate_2.return_value = validator_result
        gate = make_video_identity_gate(ref_paths=[ref_path])
        verdict = gate(frame_path, {"shot_id": "SH01"})

    run_ffmpeg.assert_not_called()
    validator_cls.return_value.run_gate_2.assert_called_once()
    assert validator_cls.return_value.run_gate_2.call_args.kwargs["keyframe_path"] == frame_path
    assert verdict.passed is False
    assert verdict.gate_name == "gate_2_video"
    assert verdict.retriable is True


def test_build_identity_gates_from_payload_spec_precedence_and_none(tmp_path):
    """build_identity_gates_from_payload builds the gate callable from the
    serializable ref-path spec (runner-side); honors an in-memory gates list;
    returns None when neither is present."""
    from recoil.execution.step_runner import build_identity_gates_from_payload

    ref = tmp_path / "ref.png"
    ref.write_bytes(b"ref")

    gates = build_identity_gates_from_payload({"identity_gate_ref_paths": [str(ref)]})
    assert gates and callable(gates[0])

    sentinel = [object()]
    assert build_identity_gates_from_payload({"gates": sentinel}) is sentinel

    assert build_identity_gates_from_payload({}) is None


def test_video_identity_gate_fails_closed_when_frame_extract_fails(tmp_path):
    video_path = tmp_path / "take.mp4"
    video_path.write_bytes(b"video")

    with patch(
        "subprocess.run",
        side_effect=subprocess.TimeoutExpired(cmd="ffmpeg", timeout=30),
    ):
        gate = make_video_identity_gate(ref_paths=[tmp_path / "ref.png"])
        verdict = gate(video_path, {"shot_id": "SH01"})

    assert verdict.passed is False
    assert verdict.gate_name == "gate_2_video"
    assert verdict.reason.startswith("frame_extract_failed:infra")
    assert verdict.details["gate_closed_by"] == "infra"
    assert verdict.cost == 0.0
    assert verdict.retriable is True
