// Integration coverage for the `lesavka-uvc` binary control-path contracts.
//
// Scope: include the production UVC binary source in the centralized test harness
// crate and exercise selector routing, payload shaping, env parsing, and I/O
// helper behavior.
// Targets: `server/src/bin/lesavka-uvc.rs`.
// Why: this improves coverage for the operational UVC binary while keeping
// source-file hygiene baselines stable.

mod uvc_binary {
    #![allow(warnings)]
    #![allow(clippy::all)]
    #![allow(dead_code)]
    #![allow(unused_imports)]
    #![allow(unused_variables)]

    include!(env!("LESAVKA_SERVER_UVC_BIN_SRC"));

    use serial_test::serial;
    use std::fs;
    use temp_env::{with_var, with_vars};
    use tempfile::NamedTempFile;

    fn sample_cfg() -> UvcConfig {
        UvcConfig {
            width: 1280,
            height: 720,
            fps: 25,
            interval: 400_000,
            max_packet: 1024,
            frame_size: 1_843_200,
            bulk: true,
        }
    }

    fn sample_interfaces() -> UvcInterfaces {
        UvcInterfaces {
            control: UVC_STRING_CONTROL_IDX,
            streaming: UVC_STRING_STREAMING_IDX,
        }
    }

    #[test]
    #[serial]
    fn uvc_config_from_env_applies_payload_caps_and_interval_defaults() {
        with_var("LESAVKA_UVC_WIDTH", Some("640"), || {
            with_var("LESAVKA_UVC_HEIGHT", Some("480"), || {
                with_var("LESAVKA_UVC_FPS", Some("30"), || {
                    with_var("LESAVKA_UVC_INTERVAL", Some("0"), || {
                        with_var("LESAVKA_UVC_MAXPAYLOAD_LIMIT", Some("300"), || {
                            with_var("LESAVKA_UVC_MAXPACKET", Some("4096"), || {
                                with_var("LESAVKA_UVC_BULK", Some("1"), || {
                                    let cfg = UvcConfig::from_env();
                                    assert_eq!(cfg.width, 640);
                                    assert_eq!(cfg.height, 480);
                                    assert_eq!(cfg.fps, 30);
                                    assert_eq!(cfg.interval, 10_000_000 / 30);
                                    assert!(cfg.max_packet <= 300);
                                    assert!(cfg.max_packet <= 512);
                                });
                            });
                        });
                    });
                });
            });
        });
    }

    #[test]
    #[serial]
    fn uvc_config_from_env_keeps_explicit_interval_and_non_bulk_cap() {
        with_var("LESAVKA_UVC_INTERVAL", Some("200000"), || {
            with_var("LESAVKA_UVC_MAXPAYLOAD_LIMIT", Some("1500"), || {
                with_var("LESAVKA_UVC_MAXPACKET", Some("1200"), || {
                    with_var("LESAVKA_UVC_BULK", Some("0"), || {
                        let cfg = UvcConfig::from_env();
                        assert_eq!(cfg.interval, 200_000);
                        assert_eq!(cfg.max_packet, 1024);
                    });
                });
            });
        });
    }

    #[test]
    fn build_streaming_control_populates_core_fields_for_11_and_15_byte_profiles() {
        let cfg = sample_cfg();
        let ctrl_11 = build_streaming_control(&cfg, STREAM_CTRL_SIZE_11);
        assert_eq!(read_le32(&ctrl_11, 4), cfg.interval);
        assert_eq!(read_le32(&ctrl_11, 18), cfg.frame_size);
        assert_eq!(read_le32(&ctrl_11, 22), cfg.max_packet);
        assert_eq!(ctrl_11[30], 0);

        let ctrl_15 = build_streaming_control(&cfg, STREAM_CTRL_SIZE_15);
        assert_eq!(read_le32(&ctrl_15, 4), cfg.interval);
        assert_eq!(read_le32(&ctrl_15, 18), cfg.frame_size);
        assert_eq!(read_le32(&ctrl_15, 22), cfg.max_packet);
        assert_eq!(read_le32(&ctrl_15, 26), 48_000_000);
        assert_eq!(ctrl_15[30], 0x03);
    }

    #[test]
    #[serial]
    fn stream_ctrl_len_accepts_only_supported_sizes() {
        with_var("LESAVKA_UVC_CTRL_LEN", Some("26"), || {
            assert_eq!(stream_ctrl_len(), STREAM_CTRL_SIZE_11);
        });
        with_var("LESAVKA_UVC_CTRL_LEN", Some("34"), || {
            assert_eq!(stream_ctrl_len(), STREAM_CTRL_SIZE_15);
        });
        with_var("LESAVKA_UVC_CTRL_LEN", Some("99"), || {
            assert_eq!(stream_ctrl_len(), STREAM_CTRL_SIZE_11);
        });
    }

    #[test]
    fn maybe_update_ctrl_len_rebuilds_state_profiles() {
        let mut state = UvcState::new(sample_cfg());
        assert_eq!(state.ctrl_len, STREAM_CTRL_SIZE_15);
        maybe_update_ctrl_len(&mut state, STREAM_CTRL_SIZE_11 as u16, false);
        assert_eq!(state.ctrl_len, STREAM_CTRL_SIZE_11);
        assert_eq!(state.probe[2], 1);
        assert_eq!(state.commit[3], 1);
    }

    #[test]
    fn sanitize_streaming_control_applies_supported_fields() {
        let state = UvcState::new(sample_cfg());
        let mut data = [0u8; STREAM_CTRL_SIZE_MAX];
        data[2] = 1;
        data[3] = 2;
        write_le32(&mut data[4..8], 333_333);
        write_le32(&mut data[22..26], 4096);
        let out = sanitize_streaming_control(&data, &state);
        assert_eq!(out[2], 1);
        assert_eq!(out[3], 1);
        assert_eq!(read_le32(&out, 4), state.cfg.interval);
        assert_eq!(read_le32(&out, 18), state.cfg.frame_size);
        assert_eq!(read_le32(&out, 22), state.cfg.max_packet);
    }

    #[test]
    fn handle_data_updates_probe_and_commit_requests() {
        let interfaces = sample_interfaces();
        let mut state = UvcState::new(sample_cfg());
        let mut pending = Some(PendingRequest {
            interface: interfaces.streaming,
            selector: UVC_VS_PROBE_CONTROL,
            expected_len: STREAM_CTRL_SIZE_11,
        });
        let mut payload = [0u8; UVC_DATA_SIZE];
        payload[2] = 1;
        payload[3] = 1;
        write_le32(&mut payload[4..8], 250_000);
        write_le32(&mut payload[22..26], 2048);
        handle_data(
            -1,
            0,
            &mut state,
            &mut pending,
            interfaces,
            UvcRequestData {
                length: STREAM_CTRL_SIZE_11 as i32,
                data: payload,
            },
            false,
        );
        assert!(pending.is_none());
        assert_eq!(read_le32(&state.probe, 4), state.cfg.interval);
        assert_eq!(read_le32(&state.probe, 22), state.cfg.max_packet);

        let mut pending = Some(PendingRequest {
            interface: interfaces.streaming,
            selector: UVC_VS_COMMIT_CONTROL,
            expected_len: STREAM_CTRL_SIZE_11,
        });
        handle_data(
            -1,
            0,
            &mut state,
            &mut pending,
            interfaces,
            UvcRequestData {
                length: STREAM_CTRL_SIZE_11 as i32,
                data: payload,
            },
            false,
        );
        assert_eq!(read_le32(&state.commit, 4), state.cfg.interval);
    }

    #[test]
    fn map_interface_prefers_selector_specific_routes() {
        let interfaces = sample_interfaces();
        assert_eq!(
            map_interface(interfaces.control, UVC_VS_PROBE_CONTROL, interfaces, false),
            interfaces.streaming
        );
        assert_eq!(
            map_interface(
                interfaces.control,
                UVC_VC_REQUEST_ERROR_CODE_CONTROL,
                interfaces,
                false
            ),
            interfaces.control
        );
        assert_eq!(
            map_interface(
                interfaces.streaming,
                UVC_VC_REQUEST_ERROR_CODE_CONTROL,
                interfaces,
                false
            ),
            interfaces.streaming
        );
        assert_eq!(
            map_interface(interfaces.streaming, 0xff, interfaces, false),
            interfaces.streaming
        );
    }

    #[test]
    fn build_in_response_and_streaming_response_return_expected_shapes() {
        let state = UvcState::new(sample_cfg());
        let interfaces = sample_interfaces();
        let cur = build_streaming_response(&state, UVC_VS_PROBE_CONTROL, UVC_GET_CUR)
            .expect("streaming GET_CUR");
        assert_eq!(cur.len(), state.ctrl_len);
        let info = build_streaming_response(&state, UVC_VS_COMMIT_CONTROL, UVC_GET_INFO)
            .expect("streaming GET_INFO");
        assert_eq!(info, vec![0x03]);
        let none = build_streaming_response(&state, 0xff, UVC_GET_CUR);
        assert!(none.is_none());

        let built = build_in_response(
            &state,
            interfaces,
            interfaces.streaming,
            UVC_VS_PROBE_CONTROL,
            UVC_GET_CUR,
            8,
        )
        .expect("build in response");
        assert_eq!(built.len(), 8);
    }

    #[test]
    fn build_control_response_covers_supported_and_unknown_selectors() {
        let info = build_control_response(UVC_VC_REQUEST_ERROR_CODE_CONTROL, UVC_GET_INFO)
            .expect("control info");
        assert_eq!(info, vec![0x03]);
        let cur = build_control_response(UVC_VC_REQUEST_ERROR_CODE_CONTROL, UVC_GET_CUR)
            .expect("control cur");
        assert_eq!(cur, vec![0x00]);
        let unk = build_control_response(0xff, UVC_GET_CUR).expect("unknown selector placeholder");
        assert_eq!(unk, vec![0x00]);
        assert!(build_control_response(0xff, 0x55).is_none());
    }

    #[test]
    fn adjust_length_truncates_and_pads_payloads() {
        assert_eq!(adjust_length(vec![1, 2, 3, 4], 2), vec![1, 2]);
        assert_eq!(adjust_length(vec![1, 2], 4), vec![1, 2, 0, 0]);
        assert_eq!(adjust_length(vec![1], 0), Vec::<u8>::new());
    }

    #[test]
    fn parse_helpers_decode_usb_structures() {
        let mut raw = [0u8; 64];
        raw[0] = 0x81;
        raw[1] = UVC_GET_CUR;
        raw[2] = 0x34;
        raw[3] = 0x12;
        raw[4] = 0x78;
        raw[5] = 0x56;
        raw[6] = 0xBC;
        raw[7] = 0x9A;
        let req = parse_ctrl_request(raw);
        assert_eq!(req.b_request_type, 0x81);
        assert_eq!(req.b_request, UVC_GET_CUR);
        assert_eq!(req.w_value, 0x1234);
        assert_eq!(req.w_index, 0x5678);
        assert_eq!(req.w_length, 0x9ABC);

        let mut raw = [0u8; 64];
        raw[0..4].copy_from_slice(&(26_i32.to_le_bytes()));
        raw[4] = 0xAA;
        raw[63] = 0xBB;
        let data = parse_request_data(raw);
        assert_eq!(data.length, 26);
        assert_eq!(data.data[0], 0xAA);
        assert_eq!(data.data[59], 0xBB);
    }

    #[test]
    #[serial]
    fn env_helpers_and_payload_cap_env_override_behave() {
        with_var("LESAVKA_UVC_MAXPAYLOAD_LIMIT", Some("777"), || {
            let cap = compute_payload_cap(false).expect("payload cap from env");
            assert_eq!(cap.limit, 777);
            assert_eq!(cap.source, "env");
            assert_eq!(cap.pct, 100);
        });
        with_var("LESAVKA_TEST_U32", Some("42"), || {
            assert_eq!(env_u32("LESAVKA_TEST_U32", 1), 42);
        });
        with_var("LESAVKA_TEST_U8", Some("9"), || {
            assert_eq!(env_u8("LESAVKA_TEST_U8"), Some(9));
        });
        with_var("LESAVKA_TEST_U32_OPT", Some("77"), || {
            assert_eq!(env_u32_opt("LESAVKA_TEST_U32_OPT"), Some(77));
        });
    }

    #[test]
    #[serial]
    fn uvc_missing_configfs_falls_back_to_isochronous_budget() {
        with_vars(
            [
                ("LESAVKA_UVC_WIDTH", Some("1280")),
                ("LESAVKA_UVC_HEIGHT", Some("720")),
                ("LESAVKA_UVC_FPS", Some("30")),
                ("LESAVKA_UVC_INTERVAL", None::<&str>),
                ("LESAVKA_UVC_FRAME_SIZE", None::<&str>),
                ("LESAVKA_UVC_BULK", None::<&str>),
                ("LESAVKA_UVC_MAXPACKET", Some("1024")),
                ("LESAVKA_UVC_MAXPAYLOAD_LIMIT", None::<&str>),
                ("LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC", Some("9000000")),
            ],
            || {
                let cfg = UvcConfig::from_env();
                assert_eq!(cfg.interval, 10_000_000 / 30);
                assert_eq!(cfg.frame_size, 232_106);
                assert_eq!(cfg.max_packet, 1024);
                assert!(!cfg.bulk);
                assert!(!uvc_bulk_transfer_enabled());
            },
        );

        with_var("LESAVKA_UVC_BULK", Some("0"), || {
            assert!(!uvc_bulk_transfer_enabled());
        });
    }

    #[test]
    #[serial]
    fn uvc_isochronous_transport_caps_mjpeg_budget_to_wire_rate() {
        with_vars(
            [
                ("LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC", Some("10000000")),
                ("LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT", None::<&str>),
            ],
            || {
                assert_eq!(
                    derived_uvc_frame_max_bytes_for_transport(30, 1024, false),
                    232_106
                );
                assert_eq!(
                    derived_uvc_frame_max_bytes_for_transport(20, 1024, false),
                    348_160
                );
                assert_eq!(
                    derived_uvc_frame_max_bytes_for_transport(30, 1024, true),
                    333_333
                );
            },
        );
    }

    #[test]
    fn uvc_bulk_mode_is_tied_to_live_configfs_support() {
        let source = include_str!("../../../../server/src/bin/lesavka-uvc.real.inc");

        assert!(source.contains("bulk_enabled_from_configfs"));
        assert!(source.contains("streaming_bulk is not 1; using isochronous payload sizing"));
    }

    #[test]
    #[serial]
    fn uvc_stats_snapshot_can_be_disabled_or_written() {
        let stats = UvcVideoStats {
            queued: 7,
            reloaded: 6,
            replayed_stale: 2,
            rejected_oversize: 1,
            rejected_invalid: 3,
            fallback_idle: 4,
            held_last_good: 5,
            read_errors: 6,
            strict_validation_failures: 7,
            dqbuf_ioctl_errors: 8,
            dqbuf_flag_errors: 9,
            qbuf_ioctl_errors: 10,
            latest_bytes: 77_036,
            last_rejected_oversize_bytes: 300_001,
            last_rejected_oversize_cap: 300_000,
            paced_sleeps: 5,
            paced_sleep_ms: 123,
            last_report: None,
        };
        let json = uvc_stats_snapshot_json(&stats, 333_333);
        assert!(json.contains("\"queued\":7"));
        assert!(json.contains("\"latest_bytes\":77036"));
        assert!(json.contains("\"frame_cap\":333333"));
        assert!(json.contains("\"last_rejected_oversize_bytes\":300001"));
        assert!(json.contains("\"last_rejected_oversize_cap\":300000"));
        assert!(json.contains("\"paced_sleeps\":5"));
        assert!(json.contains("\"paced_sleep_ms\":123"));

        let kernel_path = tempfile::NamedTempFile::new().expect("kernel stats");
        fs::write(
            kernel_path.path(),
            r#"{"schema":"lesavka.uvc-kernel-watch.v1","counters":{"dwc2_errors":2}}"#,
        )
        .expect("write kernel stats");
        with_var(
            "LESAVKA_UVC_KERNEL_STATS_PATH",
            Some(kernel_path.path().to_str().expect("kernel stats path")),
            || {
                let json = uvc_stats_snapshot_json(&stats, 333_333);
                assert!(json.contains("\"kernel\":{\"counters\":{\"dwc2_errors\":2}"));
            },
        );

        let dir = tempfile::tempdir().expect("tempdir");
        let path = dir.path().join("uvc").join("stats.json");
        write_atomic_text(&path, &json).expect("write stats");
        assert_eq!(fs::read_to_string(&path).expect("read stats"), json);

        with_var("LESAVKA_UVC_STATS_PATH", Some("0"), || {
            assert!(uvc_stats_path().is_none());
        });
        with_var("LESAVKA_UVC_STATS_PATH", None::<&str>, || {
            assert_eq!(
                uvc_stats_path().expect("default stats path"),
                std::path::PathBuf::from(DEFAULT_UVC_STATS_PATH)
            );
        });
    }

    #[test]
    #[serial]
    fn uvc_control_open_mode_defaults_read_only_with_escape_hatch() {
        with_var("LESAVKA_UVC_CONTROL_READ_ONLY", None::<&str>, || {
            assert!(uvc_control_read_only());
        });
        for disabled in ["0", "false", "no", "off"] {
            with_var("LESAVKA_UVC_CONTROL_READ_ONLY", Some(disabled), || {
                assert!(!uvc_control_read_only());
            });
        }
        with_var("LESAVKA_UVC_CONTROL_READ_ONLY", Some("1"), || {
            assert!(uvc_control_read_only());
        });
    }

    #[test]
    fn interface_helpers_and_configfs_snapshot_are_stable_without_sysfs() {
        let tmp = NamedTempFile::new().expect("tmp");
        fs::write(tmp.path(), "7\n").expect("write");
        assert_eq!(read_interface(tmp.path().to_str().expect("path")), Some(7));
        fs::write(tmp.path(), "bad\n").expect("write bad");
        assert_eq!(read_interface(tmp.path().to_str().expect("path")), None);

        let interfaces = load_interfaces();
        assert_eq!(interfaces.control, UVC_STRING_CONTROL_IDX);
        assert_eq!(interfaces.streaming, UVC_STRING_STREAMING_IDX);

        assert!(read_configfs_snapshot().is_none());

        let mut state = UvcState::new(sample_cfg());
        state.cfg_snapshot = Some(ConfigfsSnapshot {
            width: 640,
            height: 480,
            default_interval: 333_333,
            frame_interval: 333_333,
            frame_size: 150_000,
            maxpacket: 1024,
            maxburst: 0,
            streaming_bulk: Some(0),
        });
        log_configfs_snapshot(&mut state, "contract");
        assert!(state.cfg_snapshot.is_some());
    }

    #[test]
    fn io_helpers_read_values_and_fifo_minimums() {
        let tmp = NamedTempFile::new().expect("tmp");
        fs::write(tmp.path(), "123\n").expect("write");
        assert_eq!(read_u32_file(tmp.path().to_str().unwrap()), Some(123));

        let tmp = NamedTempFile::new().expect("tmp");
        fs::write(tmp.path(), "400000 500000 600000\n").expect("write");
        assert_eq!(read_u32_first(tmp.path().to_str().unwrap()), Some(400000));

        let tmp = NamedTempFile::new().expect("tmp");
        fs::write(tmp.path(), "512, 256 1024\n").expect("write");
        assert_eq!(read_fifo_min(tmp.path().to_str().unwrap()), Some(256));
    }

    #[test]
    fn ioctl_helpers_produce_non_zero_numbers() {
        let r = ioctl_read::<V4l2Event>(b'V', 89);
        let w = ioctl_write::<UvcRequestData>(b'U', 1);
        assert_ne!(r, 0);
        assert_ne!(w, 0);
        assert_ne!(r, w);
        assert_ne!(ioc(IOC_READ, b'V', 1, 64), 0);
    }

    #[test]
    fn open_with_retry_works_for_existing_temp_file() {
        let tmp = NamedTempFile::new().expect("tmp");
        let file = open_with_retry(tmp.path().to_str().unwrap()).expect("open with retry");
        assert!(file.metadata().is_ok());
    }

    #[test]
    fn send_response_and_stall_fail_with_invalid_fd() {
        assert!(send_response(-1, 0, &[1, 2, 3]).is_err());
        assert!(send_stall(-1, 0).is_err());
    }

    #[test]
    fn handle_setup_tracks_streaming_set_cur_pending_request() {
        let interfaces = sample_interfaces();
        let mut state = UvcState::new(sample_cfg());
        let mut pending = None;

        let req = UsbCtrlRequest {
            b_request_type: 0x00,
            b_request: UVC_SET_CUR,
            w_value: (UVC_VS_PROBE_CONTROL as u16) << 8,
            w_index: interfaces.streaming as u16,
            w_length: STREAM_CTRL_SIZE_11 as u16,
        };

        handle_setup(-1, 0, &mut state, &mut pending, interfaces, req, false);
        let pending = pending.expect("expected pending request");
        assert_eq!(pending.interface, interfaces.streaming);
        assert_eq!(pending.selector, UVC_VS_PROBE_CONTROL);
        assert_eq!(pending.expected_len, STREAM_CTRL_SIZE_11);
    }

    #[test]
    fn handle_setup_accepts_control_set_cur_without_pending() {
        let interfaces = sample_interfaces();
        let mut state = UvcState::new(sample_cfg());
        let mut pending = None;
        let req = UsbCtrlRequest {
            b_request_type: 0x00,
            b_request: UVC_SET_CUR,
            w_value: (UVC_VC_REQUEST_ERROR_CODE_CONTROL as u16) << 8,
            w_index: interfaces.control as u16,
            w_length: 1,
        };

        handle_setup(-1, 0, &mut state, &mut pending, interfaces, req, false);
        assert!(pending.is_none());
    }
}
