// Integration coverage for server main RPC handler branches.
//
// Scope: include `server/src/main.rs` and exercise additional RPC paths that
// are awkward to hit from process-level tests.
// Targets: `server/src/main.rs`.
// Why: keep handler-side error/reply behavior stable without HID hardware.

#[allow(warnings)]
mod server_main_rpc {
    include!(env!("LESAVKA_SERVER_MAIN_SRC"));

    use serial_test::serial;
    use temp_env::with_var;
    use tempfile::tempdir;
    fn build_handler_for_tests_with_modes(
        kb_writable: bool,
        ms_writable: bool,
    ) -> (tempfile::TempDir, Handler) {
        let dir = tempdir().expect("tempdir");
        let kb_path = dir.path().join("hidg0.bin");
        let ms_path = dir.path().join("hidg1.bin");
        std::fs::write(&kb_path, []).expect("create kb file");
        std::fs::write(&ms_path, []).expect("create ms file");
        let kb = tokio::fs::File::from_std(
            std::fs::OpenOptions::new()
                .read(true)
                .write(kb_writable)
                .create(kb_writable)
                .truncate(kb_writable)
                .open(&kb_path)
                .expect("open kb"),
        );
        let ms = tokio::fs::File::from_std(
            std::fs::OpenOptions::new()
                .read(true)
                .write(ms_writable)
                .create(ms_writable)
                .truncate(ms_writable)
                .open(&ms_path)
                .expect("open ms"),
        );
        let handler = with_var("LESAVKA_CAPTURE_POWER_UNIT", Some("none"), || Handler {
            kb: std::sync::Arc::new(tokio::sync::Mutex::new(Some(kb))),
            ms: std::sync::Arc::new(tokio::sync::Mutex::new(Some(ms))),
            gadget: UsbGadget::new("lesavka"),
            did_cycle: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
            camera_rt: std::sync::Arc::new(CameraRuntime::new()),
            upstream_media_rt: std::sync::Arc::new(UpstreamMediaRuntime::new()),
            calibration: std::sync::Arc::new(CalibrationStore::load(std::sync::Arc::new(
                UpstreamMediaRuntime::new(),
            ))),
            capture_power: CapturePowerManager::new(),
            eye_hubs: std::sync::Arc::new(
                tokio::sync::Mutex::new(std::collections::HashMap::new()),
            ),
        });

        (dir, handler)
    }
    fn build_handler_for_tests() -> (tempfile::TempDir, Handler) {
        build_handler_for_tests_with_modes(true, true)
    }
    #[test]
    #[serial]
    fn reopen_hid_tolerates_missing_hid_endpoints() {
        let (_dir, handler) = build_handler_for_tests();
        let missing_dir = tempdir().expect("missing hid dir");
        let hid_dir = missing_dir.path().join("missing");
        with_var(
            "LESAVKA_HID_DIR",
            Some(hid_dir.to_string_lossy().to_string()),
            || {
                let rt = tokio::runtime::Runtime::new().expect("runtime");
                let result = rt.block_on(handler.reopen_hid());
                assert!(
                    result.is_ok(),
                    "reopen_hid should keep the server alive while HID endpoints are absent"
                );
                let endpoints = rt.block_on(async {
                    (
                        handler.kb.lock().await.is_none(),
                        handler.ms.lock().await.is_none(),
                    )
                });
                assert_eq!(endpoints, (true, true));
            },
        );
    }

    #[test]
    #[serial]
    fn capture_video_valid_monitor_surfaces_internal_error_without_device() {
        let (_dir, handler) = build_handler_for_tests();
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        let result = rt.block_on(async {
            handler
                .capture_video(tonic::Request::new(MonitorRequest {
                    id: 0,
                    max_bitrate: 3_000,
                    requested_width: 0,
                    requested_height: 0,
                    requested_fps: 0,
                    source_id: None,
                }))
                .await
        });
        let err = match result {
            Ok(_) => panic!("missing camera device should fail"),
            Err(err) => err,
        };
        assert_eq!(err.code(), tonic::Code::Internal);
    }

    #[test]
    #[serial]
    fn capture_video_right_eye_surfaces_internal_error_without_device() {
        let (_dir, handler) = build_handler_for_tests();
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        let result = rt.block_on(async {
            handler
                .capture_video(tonic::Request::new(MonitorRequest {
                    id: 1,
                    max_bitrate: 3_000,
                    requested_width: 0,
                    requested_height: 0,
                    requested_fps: 0,
                    source_id: None,
                }))
                .await
        });
        let err = match result {
            Ok(_) => panic!("missing right-eye camera device should fail"),
            Err(err) => err,
        };
        assert_eq!(err.code(), tonic::Code::Internal);
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn capture_video_returns_stream_when_coverage_source_is_overridden() {
        let (_dir, handler) = build_handler_for_tests();
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        with_var(
            "LESAVKA_TEST_VIDEO_SOURCE",
            Some("/dev/lesavka_l_eye"),
            || {
                let mut stream = rt
                    .block_on(async {
                        handler
                            .capture_video(tonic::Request::new(MonitorRequest {
                                id: 0,
                                max_bitrate: 3_000,
                                requested_width: 0,
                                requested_height: 0,
                                requested_fps: 0,
                                source_id: None,
                            }))
                            .await
                    })
                    .expect("coverage video stream should succeed")
                    .into_inner();
                let packet = rt
                    .block_on(async { stream.next().await })
                    .expect("stream item")
                    .expect("packet");
                assert_eq!(packet.id, 0);
                assert!(!packet.data.is_empty());
                drop(stream);
                rt.block_on(async {
                    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
                });
            },
        );
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn capture_video_stream_drop_releases_shared_hub() {
        let (_dir, handler) = build_handler_for_tests();
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        with_var(
            "LESAVKA_TEST_VIDEO_SOURCE",
            Some("/dev/lesavka_l_eye"),
            || {
                rt.block_on(async {
                    let mut stream = handler
                        .capture_video(tonic::Request::new(MonitorRequest {
                            id: 0,
                            max_bitrate: 3_000,
                            requested_width: 0,
                            requested_height: 0,
                            requested_fps: 0,
                            source_id: None,
                        }))
                        .await
                        .expect("coverage video stream")
                        .into_inner();

                    let packet = stream.next().await.expect("first item").expect("packet");
                    assert_eq!(packet.id, 0);
                    drop(stream);

                    let hub = handler
                        .eye_hubs
                        .lock()
                        .await
                        .values()
                        .next()
                        .cloned()
                        .expect("active preview hub");
                    let _ = hub.tx.send(VideoPacket {
                        id: 0,
                        pts: 2,
                        data: vec![0, 0, 0, 1, 0x65],
                        ..Default::default()
                    });

                    for _ in 0..40 {
                        if handler.active_eye_source_count().await == 0 {
                            return;
                        }
                        tokio::time::sleep(std::time::Duration::from_millis(25)).await;
                    }
                    panic!("dropping a preview stream should release its shared hub");
                });
            },
        );
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn mirrored_capture_requests_share_one_source_hub_but_keep_logical_ids() {
        let (_dir, handler) = build_handler_for_tests();
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        with_var(
            "LESAVKA_TEST_VIDEO_SOURCE",
            Some("/dev/lesavka_r_eye"),
            || {
                let (left_packet, right_packet, hub_count) = rt.block_on(async {
                    let mut left = handler
                        .capture_video(tonic::Request::new(MonitorRequest {
                            id: 0,
                            max_bitrate: 3_000,
                            requested_width: 1920,
                            requested_height: 1080,
                            requested_fps: 60,
                            source_id: Some(1),
                        }))
                        .await
                        .expect("left stream")
                        .into_inner();
                    let mut right = handler
                        .capture_video(tonic::Request::new(MonitorRequest {
                            id: 1,
                            max_bitrate: 3_000,
                            requested_width: 1920,
                            requested_height: 1080,
                            requested_fps: 60,
                            source_id: Some(1),
                        }))
                        .await
                        .expect("right stream")
                        .into_inner();
                    let left_packet = left.next().await.expect("left item").expect("left packet");
                    let right_packet = right
                        .next()
                        .await
                        .expect("right item")
                        .expect("right packet");
                    drop(left);
                    drop(right);
                    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
                    let hub_count = handler.eye_hub_count().await;
                    (left_packet, right_packet, hub_count)
                });

                assert_eq!(left_packet.id, 0);
                assert_eq!(right_packet.id, 1);
                assert!(!left_packet.data.is_empty());
                assert!(!right_packet.data.is_empty());
                assert_eq!(hub_count, 1);
            },
        );
    }

    #[test]
    #[serial]
    fn paste_text_accepts_encrypted_payload_and_returns_reply() {
        let (_dir, handler) = build_handler_for_tests();
        with_var(
            "LESAVKA_PASTE_KEY",
            Some("hex:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"),
            || {
                with_var("LESAVKA_PASTE_DELAY_MS", Some("0"), || {
                    let req =
                        lesavka_client::paste::build_paste_request("hello").expect("build request");
                    let rt = tokio::runtime::Runtime::new().expect("runtime");
                    let reply = rt
                        .block_on(async { handler.paste_text(tonic::Request::new(req)).await })
                        .expect("paste rpc should return reply")
                        .into_inner();
                    assert!(
                        reply.ok || !reply.error.is_empty(),
                        "paste path should execute and return a structured reply"
                    );
                });
            },
        );
    }

    #[test]
    #[serial]
    fn paste_text_returns_structured_error_when_hid_write_fails() {
        let (_dir, handler) = build_handler_for_tests_with_modes(false, true);
        with_var(
            "LESAVKA_PASTE_KEY",
            Some("hex:00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"),
            || {
                with_var("LESAVKA_PASTE_DELAY_MS", Some("0"), || {
                    let req =
                        lesavka_client::paste::build_paste_request("hello").expect("build request");
                    let rt = tokio::runtime::Runtime::new().expect("runtime");
                    let reply = rt
                        .block_on(async { handler.paste_text(tonic::Request::new(req)).await })
                        .expect("paste rpc should return structured reply")
                        .into_inner();
                    assert!(!reply.ok);
                    assert!(!reply.error.is_empty());
                });
            },
        );
    }

    #[test]
    #[serial]
    fn capture_audio_accepts_secondary_monitor_id_and_fails_internally_without_sink() {
        let (_dir, handler) = build_handler_for_tests();
        let req = MonitorRequest {
            id: 1,
            max_bitrate: 0,
            requested_width: 0,
            requested_height: 0,
            requested_fps: 0,
            source_id: None,
        };

        let rt = tokio::runtime::Runtime::new().expect("runtime");
        let result = rt.block_on(async { handler.capture_audio(tonic::Request::new(req)).await });
        let err = match result {
            Ok(_) => panic!("missing ALSA source should fail"),
            Err(err) => err,
        };
        assert_eq!(err.code(), tonic::Code::Internal);
    }
}
