import time

import pytest

from conftest import load_bin_module

capture_supervisor = load_bin_module("capture_supervisor")

CFG = {
    "camera": {
        "mode": "sbs_mjpeg",
        "segment_seconds": 3600,
        "device_primary": "/dev/video0",
        "device_left": "/dev/video0",
        "device_right": "/dev/video2",
        "growth_check_delay_seconds": 5,
        "resolution": "3200x1200",
        "framerate": 15,
        "capture_buffers": 3,
    }
}


def test_device_for_instance_primary():
    assert capture_supervisor.device_for_instance(CFG, "primary") == "/dev/video0"


def test_device_for_instance_left_right():
    assert capture_supervisor.device_for_instance(CFG, "left") == "/dev/video0"
    assert capture_supervisor.device_for_instance(CFG, "right") == "/dev/video2"


def test_device_for_instance_unknown_raises():
    with pytest.raises(ValueError):
        capture_supervisor.device_for_instance(CFG, "bogus")


def test_latest_segment_none_when_empty(tmp_path):
    assert capture_supervisor.latest_segment(tmp_path, "primary") is None


def test_latest_segment_picks_most_recently_modified(tmp_path):
    older = tmp_path / "cam_primary_20260101T000000.mjpeg"
    newer = tmp_path / "cam_primary_20260101T010000.mjpeg"
    older.write_bytes(b"x" * 10)
    time.sleep(0.05)
    newer.write_bytes(b"x" * 10)
    assert capture_supervisor.latest_segment(tmp_path, "primary").name == newer.name


def test_latest_segment_ignores_other_instances(tmp_path):
    (tmp_path / "cam_left_20260101T000000.mjpeg").write_bytes(b"x")
    assert capture_supervisor.latest_segment(tmp_path, "right") is None


def test_latest_segment_respects_extension(tmp_path):
    (tmp_path / "cam_primary_20260101T000000.mkv").write_bytes(b"x")
    assert capture_supervisor.latest_segment(tmp_path, "primary", "mjpeg") is None
    assert capture_supervisor.latest_segment(tmp_path, "primary", "mkv") is not None


def test_build_ffmpeg_command_uses_stream_copy_and_raw_mjpeg():
    cmd = capture_supervisor.build_ffmpeg_command(CFG, "/dev/video0", "/data/video/out_%Y%m%dT%H%M%S.mjpeg")
    assert "ffmpeg" in cmd
    assert "-c" in cmd and cmd[cmd.index("-c") + 1] == "copy"
    assert "-segment_format" in cmd and cmd[cmd.index("-segment_format") + 1] == "mjpeg"
    assert "/dev/video0" in cmd


def test_mjpeg_modes_use_the_low_memory_v4l2ctl_source():
    assert capture_supervisor.uses_v4l2ctl_source(CFG) is True
    assert capture_supervisor.uses_v4l2ctl_source({"camera": {**CFG["camera"], "mode": "sbs_h264"}}) is False


def test_v4l2_ctl_command_caps_buffers_and_sets_format():
    """Regression guard: ffmpeg's own v4l2 input allocates 32 buffers
    (~246MB at 3200x1200) and gets OOM-killed on a 512MB board. The
    capture path must go through v4l2-ctl with a small --stream-mmap."""
    cmd = capture_supervisor.build_v4l2_ctl_command(CFG, "/dev/video0")
    assert cmd[0] == "v4l2-ctl"
    assert "--stream-mmap=3" in cmd
    assert "--stream-to=-" in cmd
    assert "--set-fmt-video=width=3200,height=1200,pixelformat=MJPG" in cmd
    assert "--set-parm=15" in cmd


def test_stdin_ffmpeg_command_reads_framed_mjpeg_not_v4l2():
    cmd = capture_supervisor.build_ffmpeg_command(CFG, "/dev/video0", "out.mjpeg", from_stdin=True)
    assert cmd[cmd.index("-i") + 1] == "-"
    assert cmd[cmd.index("-f") + 1] == "mjpeg"
    # stdin is the video source -- -nostdin would break it.
    assert "-nostdin" not in cmd
    assert "v4l2" not in cmd
    # Raw MJPEG has no timestamps; without this the segment muxer would
    # produce segments of the wrong wall-clock duration.
    assert cmd[cmd.index("-framerate") + 1] == "15"


def test_stdin_ffmpeg_command_still_segments_and_stream_copies():
    cmd = capture_supervisor.build_ffmpeg_command(CFG, "/dev/video0", "out.mjpeg", from_stdin=True)
    assert cmd[cmd.index("-c") + 1] == "copy"
    assert cmd[cmd.index("-segment_time") + 1] == "3600"
    assert cmd[cmd.index("-segment_format") + 1] == "mjpeg"


def test_build_ffmpeg_command_h264_modes_use_matroska():
    # A raw H.264 elementary stream would lose framing/timestamps, so the
    # h264 modes keep the crash-safe Matroska container.
    cfg = {"camera": {**CFG["camera"], "mode": "sbs_h264"}}
    cmd = capture_supervisor.build_ffmpeg_command(cfg, "/dev/video0", "out.mkv")
    assert cmd[cmd.index("-segment_format") + 1] == "matroska"


def test_segment_extension_by_mode():
    assert capture_supervisor.segment_extension({"camera": {"mode": "sbs_mjpeg"}}) == "mjpeg"
    assert capture_supervisor.segment_extension({"camera": {"mode": "sbs_h264"}}) == "mkv"


def test_build_ffmpeg_command_requests_resolution_and_framerate():
    cmd = capture_supervisor.build_ffmpeg_command(CFG, "/dev/video0", "out.mjpeg")
    assert cmd[cmd.index("-video_size") + 1] == "3200x1200"
    assert cmd[cmd.index("-framerate") + 1] == "15"
    # Both must come before -i (they're v4l2 input options, not output options).
    assert cmd.index("-video_size") < cmd.index("-i")
    assert cmd.index("-framerate") < cmd.index("-i")


def test_build_ffmpeg_command_input_format_by_mode():
    for mode, expected_format in [
        ("sbs_mjpeg", "mjpeg"),
        ("sbs_h264", "h264"),
        ("dual_node_mjpeg", "mjpeg"),
        ("dual_node_h264", "h264"),
    ]:
        cfg = {"camera": {**CFG["camera"], "mode": mode}}
        cmd = capture_supervisor.build_ffmpeg_command(cfg, "/dev/video0", "out.mkv")
        assert cmd[cmd.index("-input_format") + 1] == expected_format
