// Integration coverage for the MJPEG spool used by UVC output.
//
// Scope: write real frame and metadata files through the same spool helper
// used by direct MJPEG ingress and decoded HEVC ingress.
// Targets: server/src/video_sinks/mjpeg_spool.rs.
// Why: both profiles share the UVC helper handoff path, so metadata must keep
// them separated without changing the atomic frame publication behavior.

use std::fs;

use serial_test::serial;

mod uvc_contract {
    pub use lesavka_server::uvc_contract::*;
}

mod video_support {
    pub fn env_u32(name: &str, default: u32) -> u32 {
        std::env::var(name)
            .ok()
            .and_then(|value| value.trim().parse().ok())
            .unwrap_or(default)
    }
}

mod video_sinks {
    #[allow(clippy::items_after_test_module)]
    pub mod hevc_mjpeg_guard {
        include!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/server/src/video_sinks/hevc_mjpeg_guard.rs"
        ));
    }

    #[allow(dead_code, clippy::items_after_test_module)]
    pub mod spool {
        include!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/server/src/video_sinks/mjpeg_spool.rs"
        ));

        pub fn write_hevc(
            path: &std::path::Path,
            data: &[u8],
            source_pts_us: u64,
            decoded_pts_us: Option<u64>,
        ) -> anyhow::Result<()> {
            spool_mjpeg_frame_with_timing(
                path,
                data,
                Some(MjpegSpoolTiming::hevc_decoded_mjpeg(
                    source_pts_us,
                    decoded_pts_us,
                )),
            )
        }

        pub fn write_mjpeg(
            path: &std::path::Path,
            data: &[u8],
            source_pts_us: u64,
        ) -> anyhow::Result<()> {
            spool_mjpeg_frame_with_timing(
                path,
                data,
                Some(MjpegSpoolTiming::mjpeg_passthrough(source_pts_us)),
            )
        }
    }
}
use video_sinks::spool;

#[test]
#[serial]
fn hevc_decoded_mjpeg_spool_writes_frame_and_timing_metadata() {
    let dir = tempfile::tempdir().expect("tempdir");
    let frame_path = dir.path().join("uvc-frame.mjpg");
    let log_path = dir.path().join("uvc-frame.jsonl");
    let log_path_text = log_path.to_string_lossy().to_string();
    let frame = [0xff, 0xd8, 0x11, 0x22, 0xff, 0xd9];

    temp_env::with_vars(
        [
            ("LESAVKA_UVC_FRAME_META", Some("1")),
            (
                "LESAVKA_UVC_FRAME_META_LOG_PATH",
                Some(log_path_text.as_str()),
            ),
            ("LESAVKA_UVC_FRAME_META_PATH", None),
        ],
        || {
            spool::write_hevc(&frame_path, &frame, 42_000, Some(44_000))
                .expect("spool decoded HEVC frame");
        },
    );

    assert_eq!(fs::read(&frame_path).expect("frame bytes"), frame);

    let sidecar = frame_path.with_extension("mjpg.meta.json");
    let sidecar_text = fs::read_to_string(sidecar).expect("sidecar metadata");
    let log_text = fs::read_to_string(log_path).expect("metadata log");
    for text in [sidecar_text.as_str(), log_text.as_str()] {
        assert!(text.contains("\"profile\":\"hevc-decoded-mjpeg\""));
        assert!(text.contains("\"bytes\":6"));
        assert!(text.contains("\"source_pts_us\":42000"));
        assert!(text.contains("\"decoded_pts_us\":44000"));
    }
}

#[test]
#[serial]
fn mjpeg_passthrough_spool_keeps_decode_timing_null() {
    let dir = tempfile::tempdir().expect("tempdir");
    let frame_path = dir.path().join("uvc-frame.mjpg");
    let frame = [0xff, 0xd8, 0x33, 0x44, 0xff, 0xd9];

    temp_env::with_var("LESAVKA_UVC_FRAME_META", Some("1"), || {
        spool::write_mjpeg(&frame_path, &frame, 55_000).expect("spool MJPEG frame");
    });

    assert_eq!(fs::read(&frame_path).expect("frame bytes"), frame);
    let sidecar_text =
        fs::read_to_string(frame_path.with_extension("mjpg.meta.json")).expect("sidecar metadata");
    assert!(sidecar_text.contains("\"profile\":\"mjpeg-passthrough\""));
    assert!(sidecar_text.contains("\"source_pts_us\":55000"));
    assert!(sidecar_text.contains("\"decoded_pts_us\":null"));
}

#[test]
fn mjpeg_spool_uses_unique_temporary_paths_per_publish() {
    let source = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/server/src/video_sinks/mjpeg_spool.rs"
    ));

    assert!(source.contains("static SPOOL_TEMP_SEQUENCE"));
    assert!(
        source.contains("SPOOL_TEMP_SEQUENCE.fetch_add(1, Ordering::Relaxed)"),
        "shared MJPEG spool writers must not reuse a single process-wide temp path"
    );
}
