// Extra media and helper coverage for server main relay branches.
//
// Scope: include `server/src/main.rs` and exercise camera, eye-hub, and UVC
// helper branches without pushing the binary contract past the LOC cap.
// Targets: `server/src/main.rs`.
// Why: live media paths need bounded, deterministic contracts even when no
// real camera, UVC helper, or capture hardware is present.

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

    use futures_util::stream;
    use lesavka_common::lesavka::relay_client::RelayClient;
    use serial_test::serial;
    use std::os::unix::fs::PermissionsExt;
    use std::path::Path;
    use temp_env::with_var;
    use tempfile::tempdir;

    async fn connect_with_retry(addr: std::net::SocketAddr) -> tonic::transport::Channel {
        let endpoint = tonic::transport::Endpoint::from_shared(format!("http://{addr}"))
            .expect("endpoint")
            .tcp_nodelay(true);
        for _ in 0..40 {
            if let Ok(channel) = endpoint.clone().connect().await {
                return channel;
            }
            tokio::time::sleep(std::time::Duration::from_millis(25)).await;
        }
        panic!("failed to connect to local tonic server");
    }

    fn write_file(path: &Path, content: &str) {
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent).expect("create parent");
        }
        std::fs::write(path, content).expect("write file");
    }

    fn write_helper(path: &Path, body: &str) {
        write_file(path, body);
        let mut perms = std::fs::metadata(path)
            .expect("helper metadata")
            .permissions();
        perms.set_mode(0o755);
        std::fs::set_permissions(path, perms).expect("chmod helper");
    }

    fn with_capture_power_disabled(f: impl FnOnce()) {
        with_var("LESAVKA_CAPTURE_POWER_UNIT", Some("none"), f);
    }

    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"),
        );

        (
            dir,
            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(),
                )),
            },
        )
    }

    fn build_handler_for_tests() -> (tempfile::TempDir, Handler) {
        build_handler_for_tests_with_modes(true, true)
    }

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

            let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
            let addr = listener.local_addr().expect("addr");
            drop(listener);

            let server = tokio::spawn(async move {
                let _ = tonic::transport::Server::builder()
                    .add_service(RelayServer::new(handler))
                    .serve(addr)
                    .await;
            });

            let channel = connect_with_retry(addr).await;
            let mut cli = RelayClient::new(channel);
            let (tx, rx) = tokio::sync::mpsc::channel(4);
            tx.send(VideoPacket {
                id: 2,
                pts: 1,
                data: vec![0, 1, 2, 3],
                ..Default::default()
            })
            .await
            .expect("send camera packet");
            drop(tx);

            let outbound = tokio_stream::wrappers::ReceiverStream::new(rx);
            let result = cli.stream_camera(tonic::Request::new(outbound)).await;
            match result {
                Ok(mut stream) => {
                    let _ = stream.get_mut().message().await;
                }
                Err(err) => {
                    assert!(
                        matches!(
                            err.code(),
                            tonic::Code::Internal | tonic::Code::Unavailable | tonic::Code::Unknown
                        ),
                        "unexpected camera stream error code: {}",
                        err.code()
                    );
                }
            }

            server.abort();
        });
    }

    #[test]
    #[serial]
    fn stream_camera_drops_frames_when_audio_master_never_advances() {
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
            rt.block_on(async {
                let (_dir, handler) = build_handler_for_tests();
                let _stalled_microphone = handler.upstream_media_rt.activate_microphone();

                let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind");
                let addr = listener.local_addr().expect("addr");
                drop(listener);

                let server = tokio::spawn(async move {
                    let _ = tonic::transport::Server::builder()
                        .add_service(RelayServer::new(handler))
                        .serve(addr)
                        .await;
                });

                let channel = connect_with_retry(addr).await;
                let mut cli = RelayClient::new(channel);
                let (tx, rx) = tokio::sync::mpsc::channel(4);
                tx.send(VideoPacket {
                    id: 2,
                    pts: 1,
                    data: vec![0, 1, 2, 3],
                    ..Default::default()
                })
                .await
                .expect("send camera packet");
                drop(tx);

                let outbound = tokio_stream::wrappers::ReceiverStream::new(rx);
                let mut resp = match cli.stream_camera(tonic::Request::new(outbound)).await {
                    Ok(resp) => resp,
                    Err(err)
                        if err.code() == tonic::Code::Internal
                            && err.message().contains("no Lesavka video_output") =>
                    {
                        server.abort();
                        return;
                    }
                    Err(err) => panic!("stream camera should terminate cleanly: {err:?}"),
                };
                let _ = tokio::time::timeout(
                    std::time::Duration::from_secs(2),
                    resp.get_mut().message(),
                )
                .await
                .expect("camera stream should not block forever")
                .expect("grpc message read");

                server.abort();
            });
        });
    }

    #[test]
    #[serial]
    fn shared_eye_hub_covers_conflict_idle_and_error_shutdown_paths() {
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        with_capture_power_disabled(|| {
            rt.block_on(async {
                let (_dir, handler) = build_handler_for_tests();
                let first_key = EyeHubKey {
                    source_id: 0,
                    requested_width: 640,
                    requested_height: 480,
                    requested_fps: 30,
                };
                let conflicting_key = EyeHubKey {
                    source_id: 0,
                    requested_width: 1280,
                    requested_height: 720,
                    requested_fps: 30,
                };

                #[cfg(coverage)]
                {
                    let first = handler
                        .eye_hub("testsrc", first_key, 3_000)
                        .await
                        .expect("first eye hub");
                    assert_eq!(handler.active_eye_source_count().await, 1);
                    let replacement = handler
                        .eye_hub("testsrc", conflicting_key, 3_000)
                        .await
                        .expect("replacement eye hub");
                    assert!(!std::sync::Arc::ptr_eq(&first, &replacement));
                    assert!(!first.running.load(std::sync::atomic::Ordering::Relaxed));
                    replacement.shutdown();
                }

                #[cfg(not(coverage))]
                {
                    let first_lease = handler.capture_power.acquire().await;
                    let first = EyeHub::spawn(
                        stream::pending::<Result<VideoPacket, tonic::Status>>(),
                        first_lease,
                    );
                    handler
                        .eye_hubs
                        .lock()
                        .await
                        .insert(first_key, std::sync::Arc::clone(&first));
                    assert_eq!(handler.active_eye_source_count().await, 1);
                    let stale = {
                        let mut hubs = handler.eye_hubs.lock().await;
                        take_conflicting_eye_hubs(&mut hubs, conflicting_key)
                    };
                    assert_eq!(stale.len(), 1);
                    assert!(std::sync::Arc::ptr_eq(&first, &stale[0]));
                    for hub in stale {
                        hub.shutdown();
                    }
                }

                let manager = CapturePowerManager::new();
                let idle_lease = manager.acquire().await;
                let idle_packets = (0..61).map(|idx| {
                    Ok(VideoPacket {
                        id: 0,
                        pts: idx,
                        data: vec![idx as u8],
                        ..Default::default()
                    })
                });
                let idle_hub = EyeHub::spawn(stream::iter(idle_packets), idle_lease);
                tokio::time::sleep(std::time::Duration::from_millis(50)).await;
                assert!(!idle_hub.running.load(std::sync::atomic::Ordering::Relaxed));

                let error_lease = manager.acquire().await;
                let error_hub = EyeHub::spawn(
                    stream::iter(vec![Err(tonic::Status::internal("boom"))]),
                    error_lease,
                );
                tokio::time::sleep(std::time::Duration::from_millis(50)).await;
                assert!(!error_hub.running.load(std::sync::atomic::Ordering::Relaxed));
            });
        });
    }

    #[test]
    #[serial]
    fn uvc_helper_restart_systemctl_branches_are_classified() {
        let dir = tempdir().expect("tempdir");
        let systemctl = dir.path().join("systemctl");
        write_helper(
            &systemctl,
            r#"#!/usr/bin/env sh
case "$*" in
  "reset-failed lesavka-uvc.service")
    case "$LESAVKA_FAKE_SYSTEMCTL_MODE" in
      resetfail) echo "reset failed first" >&2; exit 1 ;;
      *) exit 0 ;;
    esac
    ;;
  "restart lesavka-uvc.service")
    case "$LESAVKA_FAKE_SYSTEMCTL_MODE" in
      refused) echo "Operation refused, unit may be requested by dependency only" >&2; exit 1 ;;
      fail) echo "stderr detail" >&2; echo "stdout detail"; exit 1 ;;
      stdout-only) echo "stdout detail"; exit 1 ;;
      *) exit 0 ;;
    esac
    ;;
esac
exit 1
"#,
        );
        let prior = std::env::var("PATH").unwrap_or_default();
        let path = if prior.is_empty() {
            dir.path().display().to_string()
        } else {
            format!("{}:{prior}", dir.path().display())
        };

        with_var("PATH", Some(path), || {
            with_var("LESAVKA_GADGET_SYSFS_ROOT", None::<&str>, || {
                with_var("LESAVKA_GADGET_CONFIGFS_ROOT", None::<&str>, || {
                    with_var("LESAVKA_FAKE_SYSTEMCTL_MODE", None::<&str>, || {
                        restart_uvc_helper().expect("successful fake restart");
                    });
                    with_var("LESAVKA_FAKE_SYSTEMCTL_MODE", Some("refused"), || {
                        restart_uvc_helper().expect("dependency-managed restart is acceptable");
                    });
                    with_var("LESAVKA_FAKE_SYSTEMCTL_MODE", Some("fail"), || {
                        let err = restart_uvc_helper().expect_err("generic restart failure");
                        let message = err.to_string();
                        assert!(message.contains("stderr detail"));
                        assert!(message.contains("also see stdout"));
                    });
                    with_var("LESAVKA_FAKE_SYSTEMCTL_MODE", Some("stdout-only"), || {
                        let err = restart_uvc_helper().expect_err("stdout-only restart failure");
                        let message = err.to_string();
                        assert!(message.contains("stdout detail"));
                        assert!(!message.contains("also see stdout"));
                    });
                    with_var("LESAVKA_FAKE_SYSTEMCTL_MODE", Some("resetfail"), || {
                        let err = restart_uvc_helper().expect_err("reset-failed should propagate");
                        assert!(err.to_string().contains("reset failed first"));
                    });
                    with_var("LESAVKA_FAKE_SYSTEMCTL_MODE", Some("stdout-only"), || {
                        let err = run_systemctl(&["restart", "lesavka-uvc.service"])
                            .expect_err("stdout-only direct systemctl failure");
                        let message = err.to_string();
                        assert!(message.contains("stdout detail"));
                        assert!(!message.contains("also see stdout"));
                    });
                    assert!(uvc_helper_restart_was_dependency_refused(
                        "unit may be requested by dependency only"
                    ));
                    assert!(!uvc_helper_restart_was_dependency_refused("plain failure"));
                });
            });
        });
    }

    #[test]
    #[serial]
    fn coverage_main_and_status_helpers_report_expected_edges() {
        assert_eq!(
            remote_audio_status("remote USB gadget is not attached".to_string()).code(),
            tonic::Code::Unavailable
        );
        assert_eq!(
            remote_audio_status("alsa failed".to_string()).code(),
            tonic::Code::Internal
        );
        with_var("LESAVKA_LIVE_KEYBOARD_REPORT_DELAY_MS", Some("12"), || {
            assert_eq!(
                live_keyboard_report_delay(),
                std::time::Duration::from_millis(12)
            );
        });

        #[cfg(coverage)]
        {
            let err = main().expect_err("coverage main should skip live gRPC serve loop");
            assert!(
                err.to_string()
                    .contains("coverage mode skips live gRPC serve loop")
            );
        }
    }

    #[test]
    /// Keeps `upstream_packet_timing_helpers_fall_back_to_pts_when_client_sidecars_are_missing` explicit because freshness telemetry must remain meaningful for older clients and transitional probes.
    /// Inputs are audio/video packets with and without client timing sidecars; output is normalized timing metadata for planner snapshots.
    fn upstream_packet_timing_helpers_fall_back_to_pts_when_client_sidecars_are_missing() {
        let audio = audio_client_timing(&AudioPacket {
            pts: 50_000,
            client_capture_pts_us: 0,
            client_send_pts_us: 0,
            client_queue_depth: 7,
            client_queue_age_ms: 42,
            ..Default::default()
        });
        let video = video_client_timing(&VideoPacket {
            pts: 60_000,
            client_capture_pts_us: 55_000,
            client_send_pts_us: 0,
            client_queue_depth: 3,
            client_queue_age_ms: 21,
            ..Default::default()
        });

        assert_eq!(audio.capture_pts_us, 50_000);
        assert_eq!(audio.send_pts_us, 50_000);
        assert_eq!(audio.queue_depth, 7);
        assert_eq!(audio.queue_age_ms, 42);
        assert_eq!(video.capture_pts_us, 55_000);
        assert_eq!(video.send_pts_us, 55_000);
        assert_eq!(video.queue_depth, 3);
        assert_eq!(video.queue_age_ms, 21);

        let stamped_audio = audio_client_timing(&AudioPacket {
            pts: 70_000,
            client_capture_pts_us: 71_000,
            client_send_pts_us: 72_500,
            client_queue_depth: 2,
            client_queue_age_ms: 6,
            ..Default::default()
        });
        let stamped_video = video_client_timing(&VideoPacket {
            pts: 80_000,
            client_capture_pts_us: 81_000,
            client_send_pts_us: 83_000,
            client_queue_depth: 4,
            client_queue_age_ms: 8,
            ..Default::default()
        });
        assert_eq!(stamped_audio.capture_pts_us, 71_000);
        assert_eq!(stamped_audio.send_pts_us, 72_500);
        assert_eq!(stamped_video.capture_pts_us, 81_000);
        assert_eq!(stamped_video.send_pts_us, 83_000);
    }
}
