use super::mjpeg_spool::{mjpeg_spool_enabled, mjpeg_spool_path};

#[test]
fn mjpeg_spool_byte_guard_accepts_jpeg_and_identifies_hevc_annex_b() {
    assert!(super::looks_like_mjpeg_frame(&[
        0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10
    ]));
    assert!(!super::looks_like_mjpeg_frame(&[
        0x00, 0x00, 0x00, 0x01, 0x46, 0x01
    ]));
    assert!(super::looks_like_annex_b_hevc(&[
        0x00, 0x00, 0x00, 0x01, 0x46, 0x01
    ]));
    assert!(super::looks_like_annex_b_hevc(&[0x00, 0x00, 0x01, 0x26]));
    assert!(!super::looks_like_annex_b_hevc(&[0xff, 0xd8, 0xff, 0xdb]));
}

#[test]
fn uvc_session_clock_alignment_defaults_on_and_accepts_disable_overrides() {
    temp_env::with_var_unset("LESAVKA_UVC_SESSION_CLOCK_ALIGN", || {
        assert!(super::uvc_sink_session_clock_align_enabled());
    });

    for disabled in ["0", "false", "no", "off"] {
        temp_env::with_var("LESAVKA_UVC_SESSION_CLOCK_ALIGN", Some(disabled), || {
            assert!(!super::uvc_sink_session_clock_align_enabled());
        });
    }

    temp_env::with_var("LESAVKA_UVC_SESSION_CLOCK_ALIGN", Some("1"), || {
        assert!(super::uvc_sink_session_clock_align_enabled());
    });
}

#[test]
fn mjpeg_uvc_sink_defaults_to_mmap_io_mode_with_safe_override() {
    temp_env::with_var_unset("LESAVKA_UVC_MJPEG_IO_MODE", || {
        assert_eq!(super::uvc_mjpeg_v4l2sink_io_mode(), "mmap");
    });

    temp_env::with_var("LESAVKA_UVC_MJPEG_IO_MODE", Some("mmap"), || {
        assert_eq!(super::uvc_mjpeg_v4l2sink_io_mode(), "mmap");
    });

    temp_env::with_var("LESAVKA_UVC_MJPEG_IO_MODE", Some("not-real"), || {
        assert_eq!(super::uvc_mjpeg_v4l2sink_io_mode(), "mmap");
    });
}

#[test]
fn mjpeg_spool_defaults_on_with_path_override() {
    temp_env::with_var_unset("LESAVKA_UVC_MJPEG_SPOOL", || {
        assert!(mjpeg_spool_enabled());
    });

    temp_env::with_var("LESAVKA_UVC_MJPEG_SPOOL", Some("0"), || {
        assert!(!mjpeg_spool_enabled());
    });
    for disabled in ["false", "no", "off"] {
        temp_env::with_var("LESAVKA_UVC_MJPEG_SPOOL", Some(disabled), || {
            assert!(!mjpeg_spool_enabled());
        });
    }

    temp_env::with_var("LESAVKA_UVC_FRAME_PATH", Some("/tmp/frame.mjpg"), || {
        assert_eq!(
            mjpeg_spool_path(),
            std::path::PathBuf::from("/tmp/frame.mjpg")
        );
    });
}

#[test]
fn uvc_appsrc_limits_default_to_freshness_first_bounds() {
    temp_env::with_var_unset("LESAVKA_UVC_APP_MAX_BUFFERS", || {
        temp_env::with_var_unset("LESAVKA_UVC_APP_MAX_BYTES", || {
            temp_env::with_var_unset("LESAVKA_UVC_APP_MAX_TIME_NS", || {
                temp_env::with_var_unset("LESAVKA_UVC_APP_LEAKY_TYPE", || {
                    assert_eq!(super::uvc_appsrc_max_buffers(), 4);
                    assert_eq!(super::uvc_appsrc_max_bytes(), 4 * 1024 * 1024);
                    assert_eq!(super::uvc_appsrc_max_time_ns(), 200_000_000);
                    assert_eq!(super::uvc_appsrc_leaky_type(), "downstream");
                });
            });
        });
    });
}

#[test]
fn uvc_appsrc_limits_accept_positive_safe_overrides_only() {
    temp_env::with_var("LESAVKA_UVC_APP_MAX_BUFFERS", Some("6"), || {
        temp_env::with_var("LESAVKA_UVC_APP_MAX_BYTES", Some("1048576"), || {
            temp_env::with_var("LESAVKA_UVC_APP_MAX_TIME_NS", Some("100000000"), || {
                temp_env::with_var("LESAVKA_UVC_APP_LEAKY_TYPE", Some("upstream"), || {
                    assert_eq!(super::uvc_appsrc_max_buffers(), 6);
                    assert_eq!(super::uvc_appsrc_max_bytes(), 1_048_576);
                    assert_eq!(super::uvc_appsrc_max_time_ns(), 100_000_000);
                    assert_eq!(super::uvc_appsrc_leaky_type(), "upstream");
                });
            });
        });
    });

    temp_env::with_var("LESAVKA_UVC_APP_MAX_BUFFERS", Some("0"), || {
        temp_env::with_var("LESAVKA_UVC_APP_MAX_BYTES", Some("nope"), || {
            temp_env::with_var("LESAVKA_UVC_APP_MAX_TIME_NS", Some("0"), || {
                temp_env::with_var("LESAVKA_UVC_APP_LEAKY_TYPE", Some("sideways"), || {
                    assert_eq!(super::uvc_appsrc_max_buffers(), 4);
                    assert_eq!(super::uvc_appsrc_max_bytes(), 4 * 1024 * 1024);
                    assert_eq!(super::uvc_appsrc_max_time_ns(), 200_000_000);
                    assert_eq!(super::uvc_appsrc_leaky_type(), "downstream");
                });
            });
        });
    });
}

#[test]
fn hevc_spool_freshness_bounds_default_to_tiny_live_handoff() {
    temp_env::with_var_unset("LESAVKA_UVC_HEVC_FRESHNESS_QUEUE_BUFFERS", || {
        temp_env::with_var_unset("LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT", || {
            assert_eq!(super::uvc_hevc_freshness_queue_buffers(), 2);
            assert_eq!(super::uvc_hevc_decode_miss_limit(), 15);
        });
    });
}

#[test]
fn hevc_spool_freshness_bounds_accept_only_small_positive_queue_depths() {
    temp_env::with_var(
        "LESAVKA_UVC_HEVC_FRESHNESS_QUEUE_BUFFERS",
        Some("3"),
        || {
            temp_env::with_var("LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT", Some("4"), || {
                assert_eq!(super::uvc_hevc_freshness_queue_buffers(), 3);
                assert_eq!(super::uvc_hevc_decode_miss_limit(), 4);
            });
        },
    );

    temp_env::with_var(
        "LESAVKA_UVC_HEVC_FRESHNESS_QUEUE_BUFFERS",
        Some("99"),
        || {
            assert_eq!(super::uvc_hevc_freshness_queue_buffers(), 4);
        },
    );

    temp_env::with_var(
        "LESAVKA_UVC_HEVC_FRESHNESS_QUEUE_BUFFERS",
        Some("0"),
        || {
            temp_env::with_var("LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT", Some("0"), || {
                assert_eq!(super::uvc_hevc_freshness_queue_buffers(), 2);
                assert_eq!(super::uvc_hevc_decode_miss_limit(), 15);
            });
        },
    );
}

#[cfg(not(coverage))]
#[test]
fn webcam_bus_watch_stops_promptly_on_drop() {
    use gstreamer as gst;
    use gstreamer::prelude::ElementExt;

    gst::init().expect("gstreamer init");
    let pipeline = gst::Pipeline::new();
    let bus = pipeline.bus().expect("pipeline bus");
    let started = std::time::Instant::now();

    drop(super::WebcamBusWatchHandle::spawn(bus, "test-webcam"));

    assert!(
        started.elapsed() < std::time::Duration::from_secs(1),
        "webcam bus watcher should not outlive dropped webcam sinks"
    );
}

/// Verifies the optional direct-MJPEG normalizer preserves JPEG framing before UVC handoff.
#[cfg(not(coverage))]
#[test]
fn direct_mjpeg_normalizer_branch_reencodes_a_valid_frame() {
    use gstreamer as gst;
    use gstreamer::prelude::ElementExt;

    fn jpeg_dimensions(bytes: &[u8]) -> Option<(u16, u16)> {
        let mut idx = 2;
        while idx + 4 <= bytes.len() {
            if bytes[idx] != 0xff {
                idx += 1;
                continue;
            }
            while idx < bytes.len() && bytes[idx] == 0xff {
                idx += 1;
            }
            let marker = *bytes.get(idx)?;
            idx += 1;
            if marker == 0xda || marker == 0xd9 {
                return None;
            }
            if matches!(marker, 0x01 | 0xd0..=0xd9) {
                continue;
            }
            let segment_len = bytes
                .get(idx..idx + 2)
                .map(|value| u16::from_be_bytes([value[0], value[1]]))?
                as usize;
            if segment_len < 7 || idx + segment_len > bytes.len() {
                return None;
            }
            if matches!(
                marker,
                0xc0 | 0xc1
                    | 0xc2
                    | 0xc3
                    | 0xc5
                    | 0xc6
                    | 0xc7
                    | 0xc9
                    | 0xca
                    | 0xcb
                    | 0xcd
                    | 0xce
                    | 0xcf
            ) {
                let height = u16::from_be_bytes([bytes[idx + 3], bytes[idx + 4]]);
                let width = u16::from_be_bytes([bytes[idx + 5], bytes[idx + 6]]);
                return Some((width, height));
            }
            idx += segment_len;
        }
        None
    }

    const FIXTURE: &[u8] = include_bytes!("../../bin/lesavka_uvc/idle_1280x720_black.jpg");

    gst::init().expect("gstreamer init");
    let pipeline = gst::Pipeline::new();
    let (src, sink) = super::build_direct_mjpeg_normalize_branch(&pipeline, 640, 360, 30)
        .expect("normalizer branch");
    pipeline
        .set_state(gst::State::Playing)
        .expect("normalizer pipeline playing");

    let sample = (0..30)
        .find_map(|_| {
            src.push_buffer(gst::Buffer::from_slice(FIXTURE))
                .expect("push fixture");
            std::thread::sleep(std::time::Duration::from_millis(1));
            super::freshest_direct_mjpeg_sample(&sink)
        })
        .expect("normalized sample without a frame-sized pull wait");
    let buffer = sample.buffer().expect("sample buffer");
    let map = buffer.map_readable().expect("readable normalized buffer");

    assert!(map.as_slice().starts_with(&[0xff, 0xd8, 0xff]));
    assert!(map.as_slice().ends_with(&[0xff, 0xd9]));
    assert_eq!(jpeg_dimensions(map.as_slice()), Some((640, 360)));
    pipeline.set_state(gst::State::Null).ok();
}
