// End-to-end server coverage for shared upstream media stream helpers.
//
// Scope: run local helper-level and output-delay probe checks around the public
// upstream media RPC implementation.
// Targets: `server/src/main.rs`, `server/src/output_delay_probe.rs`.
// Why: the coverage harness should keep freshness decisions and probe plumbing
// stable without physical UVC, HDMI, or ALSA hardware in CI.

#[cfg(coverage)]
#[allow(warnings)]
mod server_upstream_media {
    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]
    fn coverage_relay_freshness_helpers_keep_live_media_bounded() {
        temp_env::with_var("LESAVKA_UPSTREAM_STALE_DROP_MS", Some("42"), || {
            assert_eq!(
                upstream_stale_drop_budget(),
                std::time::Duration::from_millis(42)
            );
        });

        let mut video = std::collections::VecDeque::from([
            VideoPacket {
                pts: 1,
                ..Default::default()
            },
            VideoPacket {
                pts: 2,
                ..Default::default()
            },
            VideoPacket {
                pts: 3,
                ..Default::default()
            },
        ]);
        assert_eq!(retain_freshest_video_packet(&mut video), 2);
        assert_eq!(video.len(), 1);
        assert_eq!(video[0].pts, 3);

        let mut audio = (0..12)
            .map(|pts| AudioPacket {
                pts,
                ..Default::default()
            })
            .collect::<std::collections::VecDeque<_>>();
        assert_eq!(retain_freshest_audio_packet(&mut audio), 4);
        assert_eq!(audio.front().map(|packet| packet.pts), Some(4));
        assert_eq!(audio.len(), AUDIO_PENDING_LIVE_WINDOW_PACKETS);

        let plan = lesavka_server::upstream_media_runtime::PlannedUpstreamPacket {
            local_pts_us: 7,
            due_at: tokio::time::Instant::now(),
            late_by: std::time::Duration::ZERO,
            source_lag: std::time::Duration::ZERO,
        };
        assert_eq!(
            coverage_playable_plan(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(plan)
            )
            .map(|plan| plan.local_pts_us),
            Some(7)
        );
        assert!(
            coverage_playable_plan(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::AwaitingPair
            )
            .is_none()
        );
        assert!(
            coverage_playable_plan(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::DropBeforeOverlap
            )
            .is_none()
        );
        assert!(
            coverage_playable_plan(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::DropStale("stale")
            )
            .is_none()
        );
        assert!(
            coverage_playable_plan(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::StartupFailed("cold")
            )
            .is_none()
        );

        let mut requeued_audio = std::collections::VecDeque::new();
        assert!(coverage_requeue_audio_packet(
            &mut requeued_audio,
            AudioPacket {
                pts: 11,
                ..Default::default()
            },
            false,
        ));
        assert_eq!(requeued_audio.front().map(|packet| packet.pts), Some(11));
        assert!(!coverage_requeue_audio_packet(
            &mut requeued_audio,
            AudioPacket {
                pts: 12,
                ..Default::default()
            },
            true,
        ));
        assert_eq!(requeued_audio.len(), 1);

        let mut requeued_video = std::collections::VecDeque::new();
        assert!(coverage_requeue_video_packet(
            &mut requeued_video,
            VideoPacket {
                pts: 21,
                ..Default::default()
            },
            false,
        ));
        assert_eq!(requeued_video.front().map(|packet| packet.pts), Some(21));
        assert!(!coverage_requeue_video_packet(
            &mut requeued_video,
            VideoPacket {
                pts: 22,
                ..Default::default()
            },
            true,
        ));
        requeued_video.push_back(VideoPacket {
            pts: 23,
            ..Default::default()
        });
        assert_eq!(coverage_drop_late_video_packet(&mut requeued_video), 1);
        assert_eq!(requeued_video.front().map(|packet| packet.pts), Some(23));

        let wait_runtime = UpstreamMediaRuntime::new();
        coverage_record_video_wait_failure(&wait_runtime);
        assert_eq!(wait_runtime.snapshot().video_freezes, 1);

        let fresh_plan = lesavka_server::upstream_media_runtime::PlannedUpstreamPacket {
            local_pts_us: 31,
            due_at: tokio::time::Instant::now(),
            late_by: std::time::Duration::from_millis(1),
            source_lag: std::time::Duration::ZERO,
        };
        let mut pending_audio = std::collections::VecDeque::new();
        assert_eq!(
            coverage_audio_plan_from_decision(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(fresh_plan),
                &mut pending_audio,
                AudioPacket {
                    pts: 31,
                    ..Default::default()
                },
                false,
                std::time::Duration::from_millis(2),
            )
            .map(|plan| plan.local_pts_us),
            Some(31)
        );
        assert!(
            coverage_audio_plan_from_decision(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::AwaitingPair,
                &mut pending_audio,
                AudioPacket {
                    pts: 32,
                    ..Default::default()
                },
                false,
                std::time::Duration::from_millis(2),
            )
            .is_none()
        );
        assert_eq!(pending_audio.front().map(|packet| packet.pts), Some(32));
        assert!(
            coverage_audio_plan_from_decision(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(
                    lesavka_server::upstream_media_runtime::PlannedUpstreamPacket {
                        local_pts_us: 33,
                        due_at: tokio::time::Instant::now(),
                        late_by: std::time::Duration::from_millis(5),
                        source_lag: std::time::Duration::ZERO,
                    },
                ),
                &mut pending_audio,
                AudioPacket {
                    pts: 33,
                    ..Default::default()
                },
                false,
                std::time::Duration::from_millis(2),
            )
            .is_none()
        );

        let mut pending_video = std::collections::VecDeque::from([
            VideoPacket {
                pts: 40,
                ..Default::default()
            },
            VideoPacket {
                pts: 41,
                ..Default::default()
            },
        ]);
        assert!(
            coverage_video_plan_from_decision(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::AwaitingPair,
                &mut pending_video,
                VideoPacket {
                    pts: 42,
                    ..Default::default()
                },
                false,
                std::time::Duration::from_millis(2),
            )
            .is_none()
        );
        assert_eq!(pending_video.front().map(|packet| packet.pts), Some(42));
        assert!(
            coverage_video_plan_from_decision(
                lesavka_server::upstream_media_runtime::UpstreamPlanDecision::Play(
                    lesavka_server::upstream_media_runtime::PlannedUpstreamPacket {
                        local_pts_us: 43,
                        due_at: tokio::time::Instant::now(),
                        late_by: std::time::Duration::from_millis(5),
                        source_lag: std::time::Duration::ZERO,
                    },
                ),
                &mut pending_video,
                VideoPacket {
                    pts: 43,
                    ..Default::default()
                },
                false,
                std::time::Duration::from_millis(2),
            )
            .is_none()
        );
        assert_eq!(pending_video.len(), 1);

        let master_runtime = UpstreamMediaRuntime::new();
        assert!(coverage_audio_master_ready(&master_runtime, true));
        assert!(!coverage_audio_master_ready(&master_runtime, false));
        assert_eq!(master_runtime.snapshot().video_freezes, 1);
    }

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

                    let mut response = cli
                        .run_output_delay_probe(tonic::Request::new(OutputDelayProbeRequest {
                            duration_seconds: 2,
                            warmup_seconds: 0,
                            pulse_period_ms: 500,
                            pulse_width_ms: 100,
                            event_width_codes: "1,2,3".to_string(),
                            audio_delay_us: 0,
                            video_delay_us: 0,
                        }))
                        .await
                        .expect("output delay probe should open")
                        .into_inner();
                    let reply =
                        tokio::time::timeout(std::time::Duration::from_secs(1), response.message())
                            .await
                            .expect("output delay probe timeout")
                            .expect("output delay probe grpc")
                            .expect("output delay probe item");
                    assert!(reply.ok);
                    assert!(reply.detail.contains("video_frames=1"));
                    assert!(reply.server_timeline_json.contains("lesavka.output-delay"));

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

#[cfg(not(coverage))]
mod server_upstream_media_normal_mode {
    const RELAY_COVERAGE: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/server/src/main/relay_service_coverage.rs"
    ));
    const FRESHNESS_HELPERS: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/server/src/main/relay_service_coverage/freshness_helpers.rs"
    ));

    #[test]
    fn upstream_media_coverage_helpers_stay_isolated_but_discoverable() {
        assert!(
            RELAY_COVERAGE.contains("freshness_helpers"),
            "coverage module should expose freshness helper coverage"
        );
        for helper in [
            "coverage_playable_plan",
            "coverage_audio_plan_from_decision",
            "coverage_video_plan_from_decision",
            "coverage_audio_master_ready",
        ] {
            assert!(
                FRESHNESS_HELPERS.contains(helper),
                "missing upstream coverage helper {helper}"
            );
        }
    }
}
