// Regression coverage for the direct-MJPEG normalizer RSS leak.
//
// Scope: lock down the safe production defaults and lifecycle cleanup for the
// server UVC MJPEG path.
// Targets: server/src/video_sinks/hevc_mjpeg_guard.rs,
// server/src/video_sinks/webcam_sink.rs, server/src/camera_runtime.rs, and
// the camera/upstream media RPC lifecycles.
// Why: the native GStreamer jpegdec/jpegenc normalizer can retain process RSS
// during long calls; future changes must keep it opt-in, guarded, and cleaned
// up when the client stream closes.

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)
    }
}

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

    pub fn normalizer_enabled() -> bool {
        direct_mjpeg_normalize_enabled()
    }

    pub fn normalizer_rss_limit_kb() -> Option<u64> {
        direct_mjpeg_normalize_rss_limit_kb()
    }
}

const SERVER_INSTALL: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/install/server.sh"
));
const WEBCAM_SINK: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/server/src/video_sinks/webcam_sink.rs"
));
const CAMERA_RUNTIME: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/server/src/camera_runtime.rs"
));
const CAMERA_RPC: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/server/src/main/relay_service/camera_stream_rpc.rs"
));
const UPSTREAM_MEDIA_RPC: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/server/src/main/relay_service/upstream_media_rpc.rs"
));

fn assert_ordered(haystack: &str, earlier: &str, later: &str) {
    let earlier_pos = haystack
        .find(earlier)
        .unwrap_or_else(|| panic!("missing earlier marker {earlier:?}"));
    let later_pos = haystack
        .find(later)
        .unwrap_or_else(|| panic!("missing later marker {later:?}"));
    assert!(
        earlier_pos < later_pos,
        "expected marker {earlier:?} before {later:?}"
    );
}

#[test]
fn direct_mjpeg_normalizer_is_opt_in_after_native_rss_leak() {
    temp_env::with_vars(
        [
            ("LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE", None::<&str>),
            (
                "LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB",
                None::<&str>,
            ),
        ],
        || {
            assert!(
                !guard::normalizer_enabled(),
                "direct MJPEG normalization must stay opt-in after the native RSS leak"
            );
            assert_eq!(guard::normalizer_rss_limit_kb(), Some(768 * 1024));
        },
    );

    temp_env::with_vars(
        [
            ("LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE", Some("on")),
            ("LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB", Some("0")),
        ],
        || {
            assert!(guard::normalizer_enabled());
            assert_eq!(guard::normalizer_rss_limit_kb(), None);
        },
    );
}

#[test]
fn installer_keeps_the_leaking_native_normalizer_disabled_by_default() {
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-0}"));
    assert!(!SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-768}"));
}

#[test]
fn opt_in_normalizer_has_rss_fuse_before_per_frame_gstreamer_allocation() {
    assert!(WEBCAM_SINK.contains("current_process_rss_kb"));
    assert!(WEBCAM_SINK.contains("direct_mjpeg_normalize_rss_limit_kb"));
    assert!(WEBCAM_SINK.contains(
        "direct MJPEG normalization disabled because server RSS exceeded its safety limit"
    ));
    assert_ordered(
        WEBCAM_SINK,
        "direct_mjpeg_normalize_rss_limit_kb()",
        "gst::Buffer::from_slice(pkt.data.clone())",
    );
}

#[test]
fn closed_camera_streams_release_native_uvc_pipeline_state() {
    assert!(CAMERA_RUNTIME.contains("pub async fn release_if_active"));
    assert!(CAMERA_RUNTIME.contains("slot.take()"));
    assert!(WEBCAM_SINK.contains("impl Drop for WebcamSink"));
    assert!(WEBCAM_SINK.contains("set_state(gst::State::Null)"));
    assert!(CAMERA_RPC.contains("camera_rt.release_if_active(camera_session_id).await"));
    assert!(UPSTREAM_MEDIA_RPC.contains("camera_rt.release_if_active(camera_session_id).await"));
}
