// Include-based coverage for server camera sink internals.
//
// Scope: include `server/src/video_sinks.rs` and directly exercise private sink
// selection/dispatch helpers through stable constructor paths.
// Targets: `server/src/video_sinks.rs`.
// Why: sink internals carry substantial branch logic beyond public smoke tests.

use gstreamer::{self as gst, prelude::ObjectExt};

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

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

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

#[path = "../../../../server/src/media_timing.rs"]
#[allow(warnings)]
mod media_timing;

#[allow(warnings)]
mod video_sinks {
    include!(env!("LESAVKA_SERVER_VIDEO_SINKS_SRC"));

    use crate::camera::{CameraOutput, HdmiConnector, HdmiMode};
    use serial_test::serial;
    use temp_env::with_var;

    fn cfg(codec: CameraCodec) -> CameraConfig {
        CameraConfig {
            output: CameraOutput::Hdmi,
            codec,
            width: 640,
            height: 360,
            fps: 24,
            hdmi: None,
        }
    }

    fn hdmi_cfg_with_ugreen_like_modes(codec: CameraCodec) -> CameraConfig {
        CameraConfig {
            output: CameraOutput::Hdmi,
            codec,
            width: 1280,
            height: 720,
            fps: 30,
            hdmi: Some(HdmiConnector {
                name: String::from("card1-HDMI-A-2"),
                id: Some(43),
                modes: vec![
                    HdmiMode {
                        width: 1920,
                        height: 1080,
                    },
                    HdmiMode {
                        width: 1024,
                        height: 768,
                    },
                ],
            }),
        }
    }

    fn init_gst() {
        let _ = gst::init();
    }

    #[test]
    #[serial]
    fn build_hdmi_sink_respects_env_override_success_path() {
        init_gst();
        with_var("LESAVKA_HDMI_SINK", Some("fakesink"), || {
            let sink = build_hdmi_sink(&cfg(CameraCodec::H264));
            assert!(sink.is_ok(), "known override sink should build");
        });
    }

    #[test]
    #[serial]
    #[cfg(not(coverage))]
    fn build_hdmi_sink_configures_fbdev_override_for_capture_adapters() {
        init_gst();
        if gst::ElementFactory::find("fbdevsink").is_none() {
            return;
        }

        with_var("LESAVKA_HDMI_SINK", Some("fbdevsink"), || {
            with_var("LESAVKA_HDMI_FBDEV", Some("/dev/fb42"), || {
                let sink = build_hdmi_sink(&cfg(CameraCodec::H264))
                    .expect("fbdevsink override should build");

                if sink.has_property("device", None) {
                    assert_eq!(sink.property::<String>("device"), "/dev/fb42");
                }
                if sink.has_property("sync", None) {
                    assert!(
                        sink.property::<bool>("sync"),
                        "fbdev HDMI output should honor the shared session clock"
                    );
                }
            });
        });
    }

    #[test]
    #[serial]
    fn build_hdmi_sink_invalid_override_surfaces_error() {
        init_gst();
        with_var(
            "LESAVKA_HDMI_SINK",
            Some("definitely-not-a-real-gst-element"),
            || {
                let sink = build_hdmi_sink(&cfg(CameraCodec::H264));
                assert!(sink.is_err(), "invalid override must fail");
            },
        );
    }

    #[test]
    #[serial]
    fn build_hdmi_sink_falls_back_when_override_is_unset() {
        init_gst();
        with_var("LESAVKA_HDMI_SINK", None::<&str>, || {
            let sink = build_hdmi_sink(&cfg(CameraCodec::H264));
            assert!(sink.is_ok(), "fallback sink should build");
        });
    }

    #[test]
    fn hdmi_display_size_scales_uplink_to_capture_adapter_mode() {
        let cfg = hdmi_cfg_with_ugreen_like_modes(CameraCodec::H264);
        assert_eq!((cfg.width, cfg.height), (1280, 720));
        assert_eq!(cfg.hdmi_display_size(), (1920, 1080));
    }

    #[test]
    #[serial]
    #[cfg(not(coverage))]
    fn build_hdmi_sink_pins_kms_connector_and_modesetting_when_available() {
        init_gst();
        if gst::ElementFactory::find("kmssink").is_none() {
            return;
        }

        with_var("LESAVKA_HDMI_SINK", None::<&str>, || {
            with_var("LESAVKA_HDMI_DRIVER", Some("vc4"), || {
                with_var("LESAVKA_HDMI_RESTORE_CRTC", None::<&str>, || {
                    let sink = build_hdmi_sink(&hdmi_cfg_with_ugreen_like_modes(CameraCodec::H264))
                        .expect("kmssink should build");

                    if sink.has_property("force-modesetting", None) {
                        assert!(
                            sink.property::<bool>("force-modesetting"),
                            "kmssink must drive the HDMI mode instead of relying on desktop state"
                        );
                    }
                    if sink.has_property("restore-crtc", None) {
                        assert!(
                            !sink.property::<bool>("restore-crtc"),
                            "dedicated HDMI capture output should not restore the console CRTC"
                        );
                    }
                    if sink.has_property("connector-id", None) {
                        assert_eq!(sink.property::<i32>("connector-id"), 43);
                    }
                    if sink.has_property("driver-name", None) {
                        assert_eq!(sink.property::<String>("driver-name"), "vc4");
                    }
                });
            });
        });
    }

    #[test]
    #[serial]
    fn bool_env_parser_accepts_operator_friendly_values() {
        with_var("LESAVKA_BOOL_TEST", None::<&str>, || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), None);
        });
        with_var("LESAVKA_BOOL_TEST", Some("yes"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), Some(true));
        });
        with_var("LESAVKA_BOOL_TEST", Some("TRUE"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), Some(true));
        });
        with_var("LESAVKA_BOOL_TEST", Some("1"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), Some(true));
        });
        with_var("LESAVKA_BOOL_TEST", Some("on"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), Some(true));
        });
        with_var("LESAVKA_BOOL_TEST", Some("off"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), Some(false));
        });
        with_var("LESAVKA_BOOL_TEST", Some("0"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), Some(false));
        });
        with_var("LESAVKA_BOOL_TEST", Some("false"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), Some(false));
        });
        with_var("LESAVKA_BOOL_TEST", Some("no"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), Some(false));
        });
        with_var("LESAVKA_BOOL_TEST", Some("shrug"), || {
            assert_eq!(read_bool_env("LESAVKA_BOOL_TEST"), None);
        });
    }

    #[test]
    #[serial]
    fn camera_sink_dispatch_is_stable_for_hdmi_variant() {
        with_var("LESAVKA_HDMI_SINK", Some("fakesink"), || {
            if let Ok(sink) = HdmiSink::new(&cfg(CameraCodec::Mjpeg)) {
                let cam_sink = CameraSink::Hdmi(sink);
                cam_sink.push(VideoPacket {
                    id: 8,
                    pts: 1,
                    data: vec![0xFF, 0xD8, 0xFF, 0xD9],
                    ..Default::default()
                });
            }
        });
    }

    #[test]
    #[serial]
    fn camera_sink_dispatch_is_stable_for_uvc_variant() {
        if let Ok(sink) = WebcamSink::new("/dev/video-definitely-missing", &cfg(CameraCodec::Mjpeg))
        {
            let cam_sink = CameraSink::Uvc(sink);
            cam_sink.push(VideoPacket {
                id: 9,
                pts: 2,
                data: vec![0xFF, 0xD8, 0xFF, 0xD9],
                ..Default::default()
            });
        }
    }

    #[test]
    fn camera_sink_dispatch_mirrors_packets_to_uvc_and_hdmi() {
        let source = include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/server/src/video_sinks/camera_relay.rs"
        ));
        assert!(source.contains("UvcWithHdmiMirror"));
        assert!(source.contains("uvc.push(pkt.clone())"));
        assert!(source.contains("hdmi.push(pkt)"));
        assert!(source.contains("new_uvc_with_hdmi_mirror"));
    }

    #[test]
    #[serial]
    fn camera_relay_feed_covers_dev_mode_dump_branch_without_panicking() {
        with_var("LESAVKA_DEV_MODE", Some("1"), || {
            with_var("LESAVKA_HDMI_SINK", Some("fakesink"), || {
                if let Ok(relay) = CameraRelay::new_hdmi(3, &cfg(CameraCodec::H264)) {
                    relay.feed(VideoPacket {
                        id: 3,
                        pts: 3,
                        data: vec![0, 0, 0, 1, 0x65, 0x88, 0x84],
                        ..Default::default()
                    });
                }
            });
        });
    }

    #[test]
    #[cfg(coverage)]
    fn camera_relay_noop_feed_covers_freshness_first_test_path() {
        let relay = CameraRelay::new_noop(7);
        relay.feed(VideoPacket {
            id: 7,
            pts: 99,
            data: vec![0, 0, 0, 1, 0x65, 0x88],
            ..Default::default()
        });
    }
}

#[test]
fn media_timing_helpers_configure_sink_clock_properties() {
    let _ = gst::init();
    let pipeline = gst::Pipeline::new();
    crate::media_timing::prepare_pipeline_clock_sync(&pipeline);
    crate::media_timing::align_pipeline_to_session_clock(&pipeline, 3_000);

    let sink = gst::ElementFactory::make("fakesink")
        .build()
        .expect("build fakesink");
    crate::media_timing::enable_sink_clock_sync(&sink);
    assert!(sink.property::<bool>("sync"));

    let identity = gst::ElementFactory::make("identity")
        .build()
        .expect("build identity");
    crate::media_timing::enable_sink_clock_sync(&identity);
}
