// End-to-end server coverage for upstream webcam video streams.
//
// Scope: run a local gRPC server and push synthetic client video packets
// through the public `StreamCamera` RPC.
// Targets: `server/src/main.rs`, `server/src/video_sinks.rs`.
// Why: upstream video needs freshness-first delivery while still waiting for
// an audio timing master before playout.

#[cfg(coverage)]
#[allow(warnings)]
mod server_upstream_media_video {
    include!(env!("LESAVKA_SERVER_MAIN_SRC"));
    include!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/tests/helpers/support/server_upstream_media_harness.rs"
    ));

    use serial_test::serial;
    use temp_env::with_var;

    #[test]
    #[serial]
    fn stream_camera_accepts_upstream_video_packets() {
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        with_var("LESAVKA_CAPTURE_POWER_UNIT", Some("none"), || {
            with_var("LESAVKA_DISABLE_UVC", None::<&str>, || {
                with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
                    rt.block_on(async {
                        let (_dir, handler) = build_handler_for_tests();
                        let (server, mut cli) = serve_handler(handler).await;
                        let (tx, rx) = tokio::sync::mpsc::channel(4);

                        let outbound = tokio_stream::wrappers::ReceiverStream::new(rx);
                        let mut response = cli
                            .stream_camera(tonic::Request::new(outbound))
                            .await
                            .expect("camera stream should open");

                        tokio::time::sleep(std::time::Duration::from_millis(35)).await;
                        tx.send(VideoPacket {
                            id: 2,
                            pts: 54_321,
                            data: vec![0, 0, 0, 1, 0x65, 0x88],
                            ..Default::default()
                        })
                        .await
                        .expect("send synthetic upstream video");
                        drop(tx);

                        let ack = tokio::time::timeout(
                            std::time::Duration::from_secs(1),
                            response.get_mut().message(),
                        )
                        .await
                        .expect("camera ack timeout")
                        .expect("camera ack grpc")
                        .expect("camera ack item");
                        assert_eq!(ack, Empty {});

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

    #[test]
    #[serial]
    fn stream_camera_waits_for_the_pairing_window_then_plays_with_audio() {
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        with_var("LESAVKA_CAPTURE_POWER_UNIT", Some("none"), || {
            with_var("LESAVKA_DISABLE_UVC", None::<&str>, || {
                with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("80"), || {
                    rt.block_on(async {
                        let (_dir, handler) = build_handler_for_tests();
                        let (server, mut cli) = serve_handler(handler).await;
                        let (video_tx, video_rx) = tokio::sync::mpsc::channel(4);
                        let (audio_tx, audio_rx) = tokio::sync::mpsc::channel(4);

                        let mut video_response = cli
                            .stream_camera(tonic::Request::new(
                                tokio_stream::wrappers::ReceiverStream::new(video_rx),
                            ))
                            .await
                            .expect("camera stream should open")
                            .into_inner();
                        let mut audio_response = cli
                            .stream_microphone(tonic::Request::new(
                                tokio_stream::wrappers::ReceiverStream::new(audio_rx),
                            ))
                            .await
                            .expect("microphone stream should open")
                            .into_inner();

                        video_tx
                            .send(VideoPacket {
                                id: 2,
                                pts: 1_000_000,
                                data: vec![0, 0, 0, 1, 0x65, 0x99],
                                ..Default::default()
                            })
                            .await
                            .expect("send video packet");
                        tokio::time::sleep(std::time::Duration::from_millis(20)).await;
                        audio_tx
                            .send(AudioPacket {
                                id: 0,
                                pts: 1_000_000,
                                data: vec![1, 2, 3, 4],
                                ..AudioPacket::default()
                            })
                            .await
                            .expect("send matching audio packet");
                        drop(video_tx);
                        drop(audio_tx);

                        let video_ack = tokio::time::timeout(
                            std::time::Duration::from_secs(1),
                            video_response.message(),
                        )
                        .await
                        .expect("camera ack timeout")
                        .expect("camera ack grpc")
                        .expect("camera ack item");
                        let audio_ack = tokio::time::timeout(
                            std::time::Duration::from_secs(1),
                            audio_response.message(),
                        )
                        .await
                        .expect("microphone ack timeout")
                        .expect("microphone ack grpc")
                        .expect("microphone ack item");
                        assert_eq!(video_ack, Empty {});
                        assert_eq!(audio_ack, Empty {});

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

    #[test]
    #[serial]
    fn stream_camera_stops_a_superseded_session_cleanly() {
        let rt = tokio::runtime::Runtime::new().expect("runtime");
        with_var("LESAVKA_CAPTURE_POWER_UNIT", Some("none"), || {
            with_var("LESAVKA_DISABLE_UVC", None::<&str>, || {
                with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
                    rt.block_on(async {
                        let (_dir, handler) = build_handler_for_tests();
                        let (server, mut cli) = serve_handler(handler).await;
                        let (first_tx, first_rx) = tokio::sync::mpsc::channel(4);
                        let (second_tx, second_rx) = tokio::sync::mpsc::channel(1);

                        let mut first = cli
                            .stream_camera(tonic::Request::new(
                                tokio_stream::wrappers::ReceiverStream::new(first_rx),
                            ))
                            .await
                            .expect("first camera stream")
                            .into_inner();

                        let _second = cli
                            .stream_camera(tonic::Request::new(
                                tokio_stream::wrappers::ReceiverStream::new(second_rx),
                            ))
                            .await
                            .expect("second camera stream supersedes first");

                        drop(first_tx);

                        let ack = tokio::time::timeout(
                            std::time::Duration::from_secs(1),
                            first.message(),
                        )
                        .await
                        .expect("superseded camera ack timeout")
                        .expect("superseded camera ack grpc")
                        .expect("superseded camera ack item");
                        assert_eq!(ack, Empty {});
                        drop(second_tx);

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

#[cfg(not(coverage))]
mod server_upstream_media_video_normal_mode {
    const CAMERA_RPC: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/server/src/main/relay_service/camera_stream_rpc.rs"
    ));
    const RELAY_TRAIT: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/server/src/main/relay_service.rs"
    ));
    const VIDEO_SINKS: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/server/src/video_sinks.rs"
    ));
    const CAMERA_RELAY: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/server/src/video_sinks/camera_relay.rs"
    ));
    const WEBCAM_SINK: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/server/src/video_sinks/webcam_sink.rs"
    ));

    #[test]
    fn camera_rpc_stays_wired_to_video_sink_and_audio_mastering() {
        assert!(RELAY_TRAIT.contains("stream_camera"));
        assert!(CAMERA_RPC.contains("camera_rt.activate"));
        assert!(CAMERA_RPC.contains("audio_master"));
        assert!(CAMERA_RELAY.contains("CameraSink"));
        assert!(VIDEO_SINKS.contains("webcam_sink.rs"));
        assert!(WEBCAM_SINK.contains("mjpeg_spool"));
    }
}
