// Include-based coverage for camera capture configuration branches.
//
// Scope: include `client/src/input/camera.rs` and exercise encoder/source
// selection helpers plus non-device fallbacks.
// Targets: `client/src/input/camera.rs`.
// Why: camera startup should remain robust across codec/env permutations.

#[allow(warnings)]
mod live_capture_clock {
    include!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/tests/helpers/support/live_capture_clock_shim.rs"
    ));
}

#[allow(warnings)]
mod camera_include_contract {
    include!(env!("LESAVKA_CLIENT_CAMERA_SRC"));

    use serial_test::serial;
    use std::os::unix::fs::symlink;
    use temp_env::with_var;
    use tempfile::tempdir;

    fn init_gst() {
        gst::init().ok();
    }

    #[test]
    fn env_u32_parses_values_and_falls_back() {
        with_var("LESAVKA_TEST_CAM_U32", Some("77"), || {
            assert_eq!(env_u32("LESAVKA_TEST_CAM_U32", 11), 77);
        });
        with_var("LESAVKA_TEST_CAM_U32", Some("not-a-number"), || {
            assert_eq!(env_u32("LESAVKA_TEST_CAM_U32", 11), 11);
        });
    }

    #[test]
    #[serial]
    fn camera_source_profile_defaults_to_auto_decode_for_v4l2_sources() {
        with_var("LESAVKA_CAM_MJPG", None::<&str>, || {
            with_var("LESAVKA_CAM_FORMAT", None::<&str>, || {
                assert_eq!(camera_source_profile(true), CameraSourceProfile::AutoDecode);
                assert_eq!(camera_source_profile(false), CameraSourceProfile::Raw);
            });
        });

        with_var("LESAVKA_CAM_FORMAT", Some("raw"), || {
            assert_eq!(camera_source_profile(true), CameraSourceProfile::Raw);
        });
        with_var("LESAVKA_CAM_FORMAT", Some("mjpeg"), || {
            assert_eq!(camera_source_profile(true), CameraSourceProfile::Mjpeg);
        });
        with_var("LESAVKA_CAM_MJPG", Some("1"), || {
            assert_eq!(camera_source_profile(true), CameraSourceProfile::Mjpeg);
        });
    }

    #[test]
    fn camera_auto_decode_caps_accept_raw_and_mjpeg_at_requested_profile() {
        let caps = camera_auto_decode_caps(1280, 720, 30);
        assert!(caps.contains("video/x-raw,width=(int)1280,height=(int)720"));
        assert!(caps.contains("image/jpeg,width=(int)1280,height=(int)720"));
        assert!(caps.contains("framerate=(fraction)30/1"));

        let chain = camera_raw_source_chain(
            "v4l2src device=/dev/video0 do-timestamp=true",
            "video/x-raw,width=1280,height=720,framerate=30/1",
            1280,
            720,
            30,
            CameraSourceProfile::AutoDecode,
        );
        assert!(chain.contains("decodebin ! videoconvert ! videoscale ! videorate"));
        assert!(chain.contains("capsfilter caps=\""));
    }

    #[test]
    fn camera_preview_tap_uses_the_actual_uplink_dimensions() {
        let branch = camera_preview_tap_branch(1920, 1080, 30);
        assert!(branch.contains("width=1920,height=1080"));
        assert!(branch.contains("framerate=30/1"));
        assert!(!branch.contains("width=128,height=72"));

        let branch = camera_preview_tap_branch(1280, 720, 120);
        assert!(branch.contains("width=1280,height=720"));
        assert!(
            branch.contains("framerate=60/1"),
            "preview tap should cap frame production while preserving resolution"
        );
    }

    #[test]
    fn encoder_helpers_return_supported_defaults() {
        init_gst();
        let (enc, _caps) = CameraCapture::pick_encoder();
        assert!(
            matches!(
                enc,
                "nvh264enc"
                    | "nvautogpuh264enc"
                    | "vulkanh264enc"
                    | "vah264enc"
                    | "vaapih264enc"
                    | "v4l2h264enc"
                    | "x264enc"
                    | "missing-hardware-h264enc"
            ),
            "unexpected encoder: {enc}"
        );
        match CameraCapture::choose_encoder() {
            Ok((enc, key_prop)) => {
                assert!(
                    matches!(
                        enc,
                        "nvh264enc"
                            | "nvautogpuh264enc"
                            | "vulkanh264enc"
                            | "vah264enc"
                            | "vaapih264enc"
                            | "v4l2h264enc"
                            | "x264enc"
                    ),
                    "unexpected encoder: {enc}"
                );
                if let Some(key_prop) = key_prop {
                    assert!(!key_prop.is_empty());
                }
            }
            Err(err) => assert!(
                err.to_string().contains("hardware H.264 encoder required"),
                "unexpected encoder error: {err}"
            ),
        }
    }

    #[test]
    fn nvidia_and_open_source_hevc_routes_get_explicit_raw_caps() {
        let pipeline_source = include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/client/src/input/camera/capture_pipeline.rs"
        ));

        for expected in [
            "\"nvh264enc\" | \"nvh265enc\" if have_nvvidconv",
            "\"nvh264enc\" | \"nvh265enc\" /* else */",
            "\"nvautogpuh264enc\" | \"nvautogpuh265enc\"",
            "\"vulkanh264enc\"",
            "\"vah264enc\" | \"vaapih264enc\" | \"vah265enc\" | \"vaapih265enc\" | \"v4l2h265enc\"",
            "video/x-raw(memory:NVMM),format=NV12",
            "video/x-raw(memory:VulkanImage),format=NV12",
            "video/x-raw,format=NV12",
            "video/x-raw,format=I420",
        ] {
            assert!(
                pipeline_source.contains(expected),
                "camera pipeline should keep driver-compatible caps fragment {expected}"
            );
        }
    }

    #[test]
    fn mjpeg_output_does_not_probe_unused_h264_encoder() {
        let pipeline_source = include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/client/src/input/camera/capture_pipeline.rs"
        ));

        assert!(
            pipeline_source.contains("let (enc, kf_prop) = if output_mjpeg")
                && pipeline_source.contains("(\"jpegenc\", None)"),
            "MJPEG upstream must not fail setup because an unused H.264 encoder is unavailable"
        );
    }

    #[test]
    fn ffmpeg_nvenc_route_keeps_launcher_preview_tap_alive() {
        let pipeline_source = include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/client/src/input/camera/capture_pipeline.rs"
        ));
        let ffmpeg_hevc_source = include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/client/src/input/camera/ffmpeg_hevc_capture.rs"
        ));
        let tap_source = include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/client/src/input/camera/preview_tap.rs"
        ));

        for expected in [
            "spawn_ffmpeg_raw_preview_tap",
            "-filter_complex",
            "CameraSourceProfile::Mjpeg | CameraSourceProfile::AutoDecode",
            "-input_format",
            "mjpeg",
            "[vencsrc]format=nv12[vencout];[vprevsrc]format=rgba[vprevout]",
            ".arg(\"[vprevout]\")",
            ".arg(\"rawvideo\")",
            ".arg(\"pipe:2\")",
            "preview_tap_running",
        ] {
            assert!(
                pipeline_source.contains(expected)
                    || ffmpeg_hevc_source.contains(expected)
                    || tap_source.contains(expected),
                "FFmpeg/NVENC camera path should preserve preview marker {expected}"
            );
        }

        assert!(
            !pipeline_source.contains("launcher preview tap is temporarily disabled"),
            "NVENC route must not regress to disabling the launcher webcam preview"
        );
    }

    #[test]
    fn hevc_prefers_runtime_proven_ffmpeg_nvenc_unless_gstreamer_is_explicit() {
        let encoder_source = include_str!(concat!(
            env!("CARGO_MANIFEST_DIR"),
            "/client/src/input/camera/encoder_selection.rs"
        ));

        for expected in [
            "Path::new(\"/dev/nvidiactl\").exists()",
            "color=c=black:s=128x128:r=1",
            "\"hevc_nvenc\"",
            "\"-f\",",
            "\"null\"",
            "Some(\"ffmpeg_hevc_nvenc\" | \"hevc_nvenc\" | \"nvenc\")",
            "Some(\"gstreamer\" | \"gst\") => false",
            "_ => Self::ffmpeg_hevc_nvenc_available()",
        ] {
            assert!(
                encoder_source.contains(expected),
                "HEVC selection should keep FFmpeg/NVENC crash-avoidance marker {expected}"
            );
        }
        assert!(
            !encoder_source.contains("!Self::gstreamer_hevc_hardware_encoder_available()"),
            "a present GStreamer HEVC encoder must not suppress the proven FFmpeg/NVENC path"
        );
        assert!(
            encoder_source.contains(
                "supported_encoder_property(\"vah264enc\", &[\"key-int-max\", \"keyframe-period\"])",
            ),
            "modern vah264enc must receive its real short-GOP property"
        );
        assert!(
            encoder_source.contains("(\"vah265enc\", &[\"key-int-max\", \"keyframe-period\"][..])"),
            "modern vah265enc must receive its real short-GOP property"
        );
    }

    #[test]
    #[cfg(coverage)]
    fn camera_bus_logger_coverage_stub_is_non_blocking() {
        init_gst();
        let pipeline = gst::Pipeline::new();
        spawn_camera_bus_logger(&pipeline, "test-camera".to_string());
    }

    #[test]
    fn find_device_and_capture_detection_handle_missing_nodes() {
        assert!(CameraCapture::find_device("never-matches-this-fragment").is_none());
        assert!(!CameraCapture::is_capture(
            "/dev/definitely-missing-camera0"
        ));
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn find_device_normalizes_spaces_underscores_and_case() {
        let dir = tempdir().expect("tempdir");
        let by_id = dir.path().join("by-id");
        std::fs::create_dir_all(&by_id).expect("create by-id");
        symlink(
            "../video42",
            by_id.join("usb-046d_Logitech_BRIO-video-index0"),
        )
        .expect("create camera symlink");

        with_var(
            "LESAVKA_CAM_BY_ID_DIR",
            Some(by_id.to_string_lossy().to_string()),
            || {
                with_var("LESAVKA_CAM_DEV_ROOT", Some("/dev".to_string()), || {
                    let found = CameraCapture::find_device("Logitech BRIO");
                    assert_eq!(found.as_deref(), Some("/dev/video42"));
                });
            },
        );
    }

    #[test]
    #[serial]
    fn find_device_honors_override_roots_and_handles_non_capture_targets() {
        let dir = tempdir().expect("tempdir");
        let by_id = dir.path().join("by-id");
        let dev_root = dir.path().join("dev-root");
        std::fs::create_dir_all(&by_id).expect("create by-id");
        std::fs::create_dir_all(&dev_root).expect("create dev root");
        std::fs::write(dev_root.join("video42"), "").expect("create fake node");
        symlink("../dev-root/video42", by_id.join("usb-Cam_42")).expect("create camera symlink");

        with_var(
            "LESAVKA_CAM_BY_ID_DIR",
            Some(by_id.to_string_lossy().to_string()),
            || {
                with_var(
                    "LESAVKA_CAM_DEV_ROOT",
                    Some(dev_root.to_string_lossy().to_string()),
                    || {
                        let found = CameraCapture::find_device("Cam_42");
                        assert!(
                            found.is_none(),
                            "fake file should not pass V4L capture capability checks"
                        );
                    },
                );
            },
        );
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn find_device_returns_dev_path_when_fake_target_matches_capture_shape() {
        let dir = tempdir().expect("tempdir");
        let by_id = dir.path().join("by-id");
        std::fs::create_dir_all(&by_id).expect("create by-id");
        symlink("../video42", by_id.join("usb-Cam_42")).expect("create camera symlink");

        with_var(
            "LESAVKA_CAM_BY_ID_DIR",
            Some(by_id.to_string_lossy().to_string()),
            || {
                with_var("LESAVKA_CAM_DEV_ROOT", Some("/dev".to_string()), || {
                    let found = CameraCapture::find_device("Cam_42");
                    assert_eq!(found.as_deref(), Some("/dev/video42"));
                });
            },
        );
    }

    #[test]
    #[serial]
    fn new_covers_test_pattern_and_mjpg_source_branches() {
        init_gst();
        let _ = CameraCapture::new(Some("test"), None);

        with_var("LESAVKA_CAM_CODEC", Some("mjpeg"), || {
            let _ = CameraCapture::new(Some("test"), None);
        });

        let mjpeg_cfg = CameraConfig {
            codec: CameraCodec::Mjpeg,
            width: 640,
            height: 480,
            fps: 30,
        };
        let _ = CameraCapture::new(Some("test"), Some(mjpeg_cfg));

        with_var("LESAVKA_CAM_MJPG", Some("1"), || {
            let _ = CameraCapture::new(Some("/dev/video0"), None);
        });
    }

    #[test]
    #[serial]
    fn active_camera_capture_can_publish_local_preview_tap() {
        init_gst();
        let dir = tempdir().expect("tempdir");
        let path = dir.path().join("uplink-camera-preview.rgba");
        let cfg = CameraConfig {
            codec: CameraCodec::H264,
            width: 160,
            height: 90,
            fps: 10,
        };

        with_var(
            "LESAVKA_UPLINK_CAMERA_PREVIEW",
            Some(path.to_string_lossy().to_string()),
            || {
                let Ok(cap) = CameraCapture::new(Some("test"), Some(cfg)) else {
                    return;
                };

                for _ in 0..30 {
                    let _ = cap.pull();
                    if let Ok(bytes) = std::fs::read(&path) {
                        assert!(
                            bytes.starts_with(b"LESAVKA_RGBA "),
                            "preview tap should publish an RGBA frame header"
                        );
                        let header_end = bytes
                            .iter()
                            .position(|byte| *byte == b'\n')
                            .expect("preview header newline");
                        let header =
                            std::str::from_utf8(&bytes[..header_end]).expect("utf8 header");
                        assert!(
                            header.starts_with("LESAVKA_RGBA 160 90 "),
                            "preview tap should prove the selected uplink size, got {header:?}"
                        );
                        return;
                    }
                    std::thread::sleep(std::time::Duration::from_millis(50));
                }
                panic!("camera preview tap did not publish a frame");
            },
        );
    }

    #[test]
    fn spawned_camera_preview_tap_tolerates_publish_errors() {
        init_gst();
        let dir = tempdir().expect("tempdir");
        let path = dir.path().join("missing-parent").join("preview.rgba");
        let pipeline: gst::Pipeline = gst::parse::launch(
            "appsrc name=src is-live=true format=time caps=video/x-raw,format=RGBA,width=2,height=2,framerate=1/1 ! \
             appsink name=sink emit-signals=false sync=false max-buffers=4 drop=true",
        )
        .expect("pipeline")
        .downcast()
        .expect("pipeline cast");
        let src: gst_app::AppSrc = pipeline
            .by_name("src")
            .expect("appsrc")
            .downcast()
            .expect("appsrc cast");
        let sink: gst_app::AppSink = pipeline
            .by_name("sink")
            .expect("appsink")
            .downcast()
            .expect("appsink cast");
        pipeline.set_state(gst::State::Playing).expect("playing");

        let running = spawn_camera_preview_tap(sink, path);
        src.push_buffer(gst::Buffer::from_slice(vec![255_u8; 16]))
            .expect("push buffer");
        std::thread::sleep(std::time::Duration::from_millis(150));

        running.store(false, Ordering::Release);
        let _ = pipeline.set_state(gst::State::Null);
    }

    #[test]
    #[serial]
    fn new_covers_preview_tap_output_format_combinations() {
        init_gst();
        let dir = tempdir().expect("tempdir");
        let path = dir.path().join("preview.rgba");
        let mjpeg_cfg = CameraConfig {
            codec: CameraCodec::Mjpeg,
            width: 320,
            height: 240,
            fps: 15,
        };
        let h264_cfg = CameraConfig {
            codec: CameraCodec::H264,
            width: 320,
            height: 240,
            fps: 15,
        };

        with_var(
            "LESAVKA_UPLINK_CAMERA_PREVIEW",
            Some(path.to_string_lossy().to_string()),
            || {
                let mjpeg_out = CameraCapture::new(Some("/dev/video42"), Some(mjpeg_cfg));
                assert!(mjpeg_out.is_ok() || mjpeg_out.is_err());

                with_var("LESAVKA_CAM_MJPG", Some("1"), || {
                    let mjpeg_passthrough =
                        CameraCapture::new(Some("/dev/video42"), Some(mjpeg_cfg));
                    assert!(mjpeg_passthrough.is_ok() || mjpeg_passthrough.is_err());

                    let mjpeg_to_h264 = CameraCapture::new(Some("/dev/video42"), Some(h264_cfg));
                    assert!(mjpeg_to_h264.is_ok() || mjpeg_to_h264.is_err());
                });
            },
        );
    }

    #[test]
    fn new_stub_and_pull_are_stable_without_frames() {
        init_gst();
        let stub = CameraCapture::new_stub();
        assert!(stub.pull().is_none());
    }

    #[test]
    #[serial]
    fn new_covers_device_path_fragment_and_default_source_branches() {
        init_gst();

        let by_path = CameraCapture::new(Some("/dev/video42"), None);
        assert!(by_path.is_ok() || by_path.is_err());

        let by_fragment = CameraCapture::new(Some("definitely-missing-fragment"), None);
        assert!(by_fragment.is_ok() || by_fragment.is_err());

        let default_source = CameraCapture::new(None, None);
        assert!(default_source.is_ok() || default_source.is_err());
    }

    #[test]
    #[serial]
    fn new_covers_output_codec_and_mjpg_source_switches() {
        init_gst();

        let mjpeg_cfg = CameraConfig {
            codec: CameraCodec::Mjpeg,
            width: 320,
            height: 240,
            fps: 15,
        };
        let mjpeg_out = CameraCapture::new(Some("/dev/video42"), Some(mjpeg_cfg));
        assert!(mjpeg_out.is_ok() || mjpeg_out.is_err());

        with_var("LESAVKA_CAM_MJPG", Some("1"), || {
            let mjpeg_passthrough = CameraCapture::new(Some("/dev/video42"), Some(mjpeg_cfg));
            assert!(mjpeg_passthrough.is_ok() || mjpeg_passthrough.is_err());

            let h264_cfg = CameraConfig {
                codec: CameraCodec::H264,
                width: 640,
                height: 480,
                fps: 25,
            };
            let mjpg_source = CameraCapture::new(Some("/dev/video42"), Some(h264_cfg));
            assert!(mjpg_source.is_ok() || mjpg_source.is_err());
        });
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn new_covers_hevc_output_and_mjpg_source_switches() {
        init_gst();
        let hevc_cfg = CameraConfig {
            codec: CameraCodec::Hevc,
            width: 320,
            height: 240,
            fps: 15,
        };

        let hevc_from_testsrc = CameraCapture::new(Some("test"), Some(hevc_cfg));
        assert!(hevc_from_testsrc.is_ok() || hevc_from_testsrc.is_err());

        with_var("LESAVKA_CAM_CODEC", Some("hevc"), || {
            let hevc_from_env = CameraCapture::new(Some("test"), None);
            assert!(hevc_from_env.is_ok() || hevc_from_env.is_err());
        });

        with_var("LESAVKA_CAM_MJPG", Some("1"), || {
            let hevc_from_mjpg_source = CameraCapture::new(Some("/dev/video42"), Some(hevc_cfg));
            assert!(hevc_from_mjpg_source.is_ok() || hevc_from_mjpg_source.is_err());
        });
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn new_covers_non_x264_encoder_option_branch_in_coverage_harness() {
        init_gst();
        let cfg = CameraConfig {
            codec: CameraCodec::H264,
            width: 640,
            height: 480,
            fps: 30,
        };

        with_var("LESAVKA_CAM_TEST_ENCODER", Some("v4l2h264enc"), || {
            let result = CameraCapture::new(Some("test"), Some(cfg));
            assert!(result.is_ok() || result.is_err());
        });
        assert_eq!(
            CameraCapture::encoder_options("nvh264enc", None, 30),
            "nvh264enc"
        );
        assert_eq!(
            CameraCapture::encoder_options("vulkanh264enc", Some("idr-period"), 30),
            "vulkanh264enc bitrate=4500 rate-control=cbr idr-period=30"
        );
    }

    #[test]
    #[serial]
    fn pull_returns_packet_from_test_pattern_pipeline_when_available() {
        init_gst();
        let cfg = CameraConfig {
            codec: CameraCodec::H264,
            width: 320,
            height: 240,
            fps: 15,
        };
        match CameraCapture::new(Some("test"), Some(cfg)) {
            Ok(cap) => {
                for _ in 0..20 {
                    if let Some(pkt) = cap.pull() {
                        assert_eq!(pkt.id, 2);
                        assert!(
                            !pkt.data.is_empty(),
                            "test pattern should emit payload bytes"
                        );
                        return;
                    }
                    std::thread::sleep(std::time::Duration::from_millis(30));
                }
            }
            Err(err) => {
                assert!(!err.to_string().trim().is_empty());
            }
        }
    }
}
