// Integration coverage for server state-oriented RPC handler branches.
//
// Scope: include `server/src/main.rs` and exercise calibration, capture-power,
// and upstream-sync RPC surfaces.
// Targets: `server/src/main.rs`.
// Why: these RPCs expose live operational state, so tests should guard reply
// shapes without requiring gadget, HID, or capture hardware.

#[allow(warnings)]
mod server_main_state_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]
    #[cfg(coverage)]
    #[serial]
    fn capture_power_rpcs_surface_stub_snapshot_and_manual_modes() {
        let (_dir, handler) = build_handler_for_tests();
        let rt = tokio::runtime::Runtime::new().expect("runtime");

        with_var(
            "LESAVKA_TEST_UDEV_CAPTURE_DEVICES",
            Some("not-a-number"),
            || {
                assert_eq!(Handler::detected_capture_devices_from_udev(), 0);
            },
        );
        with_var("LESAVKA_TEST_UDEV_CAPTURE_DEVICES", Some("9"), || {
            assert_eq!(Handler::detected_capture_devices_from_udev(), 2);
        });

        let snapshot = rt
            .block_on(async {
                handler
                    .get_capture_power(tonic::Request::new(Empty {}))
                    .await
            })
            .expect("capture power snapshot")
            .into_inner();
        assert!(snapshot.available);
        assert!(!snapshot.enabled);
        assert_eq!(snapshot.mode, "auto");

        let forced_on = rt
            .block_on(async {
                handler
                    .set_capture_power(tonic::Request::new(SetCapturePowerRequest {
                        enabled: true,
                        command: CapturePowerCommand::ForceOn as i32,
                    }))
                    .await
            })
            .expect("force capture power on")
            .into_inner();
        assert!(forced_on.available);
        assert!(forced_on.enabled);
        assert_eq!(forced_on.mode, "forced-on");

        let forced_off = rt
            .block_on(async {
                handler
                    .set_capture_power(tonic::Request::new(SetCapturePowerRequest {
                        enabled: false,
                        command: CapturePowerCommand::ForceOff as i32,
                    }))
                    .await
            })
            .expect("force capture power off")
            .into_inner();
        assert!(forced_off.available);
        assert!(!forced_off.enabled);
        assert_eq!(forced_off.mode, "forced-off");

        let auto = rt
            .block_on(async {
                handler
                    .set_capture_power(tonic::Request::new(SetCapturePowerRequest {
                        enabled: false,
                        command: CapturePowerCommand::Auto as i32,
                    }))
                    .await
            })
            .expect("return capture power to auto")
            .into_inner();
        assert!(auto.available);
        assert!(!auto.enabled);
        assert_eq!(auto.mode, "auto");

        let legacy_fallback = rt
            .block_on(async {
                handler
                    .set_capture_power(tonic::Request::new(SetCapturePowerRequest {
                        enabled: true,
                        command: CapturePowerCommand::Unspecified as i32,
                    }))
                    .await
            })
            .expect("legacy bool fallback")
            .into_inner();
        assert!(legacy_fallback.available);
        assert!(legacy_fallback.enabled);
        assert_eq!(legacy_fallback.mode, "forced-on");
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn calibration_rpcs_surface_current_state_and_apply_updates() {
        let dir = tempdir().expect("calibration dir");
        let calibration_path = dir.path().join("calibration.toml");
        with_var(
            "LESAVKA_CALIBRATION_PATH",
            Some(calibration_path.to_string_lossy().to_string()),
            || {
                let (_dir, handler) = build_handler_for_tests();
                let rt = tokio::runtime::Runtime::new().expect("runtime");

                let initial = rt
                    .block_on(async {
                        handler.get_calibration(tonic::Request::new(Empty {})).await
                    })
                    .expect("initial calibration")
                    .into_inner();
                assert_eq!(initial.profile, "mjpeg+pcm");
                assert_eq!(initial.active_audio_offset_us, 0);
                let initial_video_offset_us = initial.active_video_offset_us;

                let adjusted = rt
                    .block_on(async {
                        handler
                            .calibrate(tonic::Request::new(CalibrationRequest {
                                action: lesavka_common::lesavka::CalibrationAction::BlindEstimate
                                    as i32,
                                audio_delta_us: 10_000,
                                video_delta_us: 2_000,
                                observed_delivery_skew_ms: 42.0,
                                observed_enqueue_skew_ms: 2.5,
                                note: "coverage estimate".to_string(),
                            }))
                            .await
                    })
                    .expect("calibrate")
                    .into_inner();
                assert_eq!(adjusted.source, "blind");
                assert_eq!(adjusted.active_audio_offset_us, 10_000);
                assert_eq!(
                    adjusted.active_video_offset_us,
                    initial_video_offset_us + 2_000
                );
                assert!(
                    std::fs::read_to_string(calibration_path)
                        .expect("persisted")
                        .contains("active_audio_offset_us=10000")
                );
            },
        );
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn upstream_sync_rpc_surfaces_planner_snapshot() {
        let (_dir, handler) = build_handler_for_tests();
        let rt = tokio::runtime::Runtime::new().expect("runtime");

        let lease_camera = handler.upstream_media_rt.activate_camera();
        let lease_microphone = handler.upstream_media_rt.activate_microphone();
        assert_eq!(lease_camera.session_id, lease_microphone.session_id);

        let initial = rt
            .block_on(async {
                handler
                    .get_upstream_sync(tonic::Request::new(Empty {}))
                    .await
            })
            .expect("planner sync state")
            .into_inner();
        assert_eq!(initial.phase, "acquiring");
        assert_eq!(initial.session_id, lease_camera.session_id);

        handler.upstream_media_rt.record_client_timing(
            UpstreamMediaKind::Camera,
            UpstreamClientTiming {
                capture_pts_us: 1_000_000,
                send_pts_us: 1_010_000,
                queue_depth: 2,
                queue_age_ms: 20,
            },
        );
        std::thread::sleep(Duration::from_millis(1));
        handler.upstream_media_rt.record_client_timing(
            UpstreamMediaKind::Microphone,
            UpstreamClientTiming {
                capture_pts_us: 1_001_500,
                send_pts_us: 1_012_000,
                queue_depth: 3,
                queue_age_ms: 25,
            },
        );
        let due = tokio::time::Instant::now() - Duration::from_millis(3);
        handler.upstream_media_rt.mark_video_presented(10_000, due);
        handler.upstream_media_rt.mark_audio_presented(11_500, due);

        let live = rt
            .block_on(async {
                handler
                    .get_upstream_sync(tonic::Request::new(Empty {}))
                    .await
            })
            .expect("live planner sync state")
            .into_inner();
        assert_eq!(live.phase, "live");
        assert_eq!(live.latest_camera_remote_pts_us, Some(1_000_000));
        assert_eq!(live.latest_microphone_remote_pts_us, Some(1_001_500));
        assert_eq!(live.last_video_presented_pts_us, Some(10_000));
        assert_eq!(live.last_audio_presented_pts_us, Some(11_500));
        assert!(live.live_lag_ms.is_some_and(|lag| lag >= 20.0));
        assert_eq!(live.planner_skew_ms, Some(1.5));
        assert_eq!(live.client_capture_skew_ms, Some(1.5));
        assert_eq!(live.client_send_skew_ms, Some(2.0));
        assert!(live.server_receive_skew_ms.is_some());
        assert_eq!(live.camera_client_queue_age_ms, Some(20.0));
        assert_eq!(live.microphone_client_queue_age_ms, Some(25.0));
        assert!(live.camera_server_receive_age_ms.is_some());
        assert!(live.microphone_server_receive_age_ms.is_some());
        assert!(live.client_capture_abs_skew_p95_ms.is_some());
        assert!(live.client_send_abs_skew_p95_ms.is_some());
        assert!(live.server_receive_abs_skew_p95_ms.is_some());
        assert!(live.camera_client_queue_age_p95_ms.is_some());
        assert!(live.microphone_client_queue_age_p95_ms.is_some());
        assert!(live.sink_handoff_skew_ms.is_some());
        assert!(live.sink_handoff_abs_skew_p95_ms.is_some());
        assert!(live.camera_sink_late_ms.is_some());
        assert!(live.microphone_sink_late_ms.is_some());
        assert!(live.camera_sink_late_p95_ms.is_some());
        assert!(live.microphone_sink_late_p95_ms.is_some());
        assert_eq!(live.client_timing_window_samples, 1);
        assert_eq!(live.sink_handoff_window_samples, 1);

        handler
            .upstream_media_rt
            .record_video_freeze("coverage freeze");
        let healing = rt
            .block_on(async {
                handler
                    .get_upstream_sync(tonic::Request::new(Empty {}))
                    .await
            })
            .expect("healing planner sync state")
            .into_inner();
        assert_eq!(healing.phase, "healing");
        assert_eq!(healing.video_freezes, 1);
        assert_eq!(healing.last_reason, "coverage freeze");
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn recover_soft_rpcs_surface_uac_success_and_non_uvc_guard() {
        with_var("LESAVKA_CAM_OUTPUT", Some("hdmi"), || {
            let (_dir, handler) = build_handler_for_tests();
            let rt = tokio::runtime::Runtime::new().expect("runtime");

            let uac = rt
                .block_on(async { handler.recover_uac(tonic::Request::new(Empty {})).await })
                .expect("uac recovery")
                .into_inner();
            assert!(uac.ok);

            let uvc = rt
                .block_on(async { handler.recover_uvc(tonic::Request::new(Empty {})).await })
                .expect_err("HDMI output should reject soft UVC recovery");
            assert_eq!(uvc.code(), tonic::Code::FailedPrecondition);
            assert!(
                uvc.to_string().contains("hdmi"),
                "unexpected UVC recovery error: {uvc}"
            );
        });
    }

    #[test]
    #[cfg(coverage)]
    #[serial]
    fn recover_soft_helpers_cover_usb_state_failure_and_uvc_success() {
        let (_dir, handler) = build_handler_for_tests();
        let rt = tokio::runtime::Runtime::new().expect("runtime");

        with_var("LESAVKA_TEST_RECOVERY_STATE_ERROR", Some("1"), || {
            let usb = rt
                .block_on(async { handler.recover_usb_reply().await })
                .expect_err("forced fake UDC read failure should be reported clearly");
            assert_eq!(usb.code(), tonic::Code::FailedPrecondition);
            assert!(usb.to_string().contains("could not read UDC state"));

            let usb_rpc = rt
                .block_on(async { handler.recover_usb(tonic::Request::new(Empty {})).await })
                .expect_err("RPC wrapper should surface the same fake UDC read failure");
            assert_eq!(usb_rpc.code(), tonic::Code::FailedPrecondition);
            assert!(usb_rpc.to_string().contains("could not read UDC state"));
        });

        with_var("LESAVKA_CAM_OUTPUT", Some("uvc"), || {
            let uvc = rt
                .block_on(async { handler.recover_uvc_reply().await })
                .expect("UVC soft recovery should retire the active relay")
                .into_inner();
            assert!(uvc.ok);
        });
    }
}
