"""
Test that read_sampled_frames_batch falls back to CPU when GPU decode fails.
Uses monkeypatching — no real GPU needed.
"""
import subprocess
import pytest
from unittest.mock import patch, MagicMock


def make_test_video(path):
    """Create a tiny valid H.264 video for testing."""
    subprocess.run(
        ["ffmpeg", "-y", "-f", "lavfi", "-i", "testsrc=duration=3:size=64x64:rate=8",
         "-c:v", "libx264", "-pix_fmt", "yuv420p", str(path)],
        capture_output=True, check=True
    )


def test_falls_back_to_cpu_on_gpu_exception(tmp_path):
    """If GPU VideoReader.get_batch raises, CPU decode is used and succeeds."""
    try:
        from decord import VideoReader as RealVideoReader
    except ImportError:
        pytest.skip("decord not available")

    video = tmp_path / "test.mp4"
    make_test_video(video)

    from badas.inference.frame_sampling import read_sampled_frames_batch

    gpu_attempted = []
    cpu_attempted = []

    class FakeGPUReader:
        """Simulates GPU VideoReader that fails on get_batch."""
        def __init__(self, path, ctx=None):
            gpu_attempted.append(True)
            self._real = RealVideoReader(str(path))
        def __len__(self):
            return len(self._real)
        def get_avg_fps(self):
            return self._real.get_avg_fps()
        def get_batch(self, indices):
            raise RuntimeError("Simulated GPU EOF failure")

    class FakeCPUReader:
        """Delegates all calls to real VideoReader."""
        def __init__(self, path, ctx=None):
            cpu_attempted.append(True)
            self._real = RealVideoReader(str(path))
        def __len__(self):
            return len(self._real)
        def get_avg_fps(self):
            return self._real.get_avg_fps()
        def get_batch(self, indices):
            return self._real.get_batch(indices)

    gpu_ctx = MagicMock()
    cpu_ctx = MagicMock()

    call_count = [0]

    def fake_video_reader(path, ctx=None):
        if ctx is gpu_ctx:
            return FakeGPUReader(path, ctx)
        return FakeCPUReader(path, ctx)

    with patch("badas.inference.frame_sampling.VideoReader", side_effect=fake_video_reader), \
         patch("badas.inference.frame_sampling.gpu", return_value=gpu_ctx), \
         patch("badas.inference.frame_sampling.cpu", return_value=cpu_ctx):
        frames, indices, info = read_sampled_frames_batch(str(video), target_fps=8.0, use_gpu=True)

    assert len(gpu_attempted) == 1, f"GPU should be tried once, got {len(gpu_attempted)}"
    assert len(cpu_attempted) == 1, f"CPU fallback should run once, got {len(cpu_attempted)}"
    assert len(frames) > 0, "Should have decoded frames via CPU fallback"
    assert frames[0].ndim == 3, "Frames should be HWC arrays"


def test_cpu_only_skips_gpu(tmp_path):
    """When use_gpu=False, GPU is never attempted."""
    try:
        from decord import VideoReader as RealVideoReader
    except ImportError:
        pytest.skip("decord not available")

    video = tmp_path / "test.mp4"
    make_test_video(video)

    from badas.inference.frame_sampling import read_sampled_frames_batch

    gpu_attempted = []

    def fake_gpu(*args, **kwargs):
        gpu_attempted.append(True)
        return MagicMock()

    with patch("badas.inference.frame_sampling.gpu", side_effect=fake_gpu):
        frames, indices, info = read_sampled_frames_batch(str(video), target_fps=8.0, use_gpu=False)

    assert len(gpu_attempted) == 0, "GPU should not be attempted when use_gpu=False"
    assert len(frames) > 0
