use super::{UpstreamMediaRuntime, play, runtime_without_offsets};
use serial_test::serial;
use std::time::Duration;

fn with_info_tracing<T>(f: impl FnOnce() -> T) -> T {
    let subscriber = tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .with_test_writer()
        .finish();
    tracing::subscriber::with_default(subscriber, f)
}

#[test]
#[serial(upstream_media_runtime)]
fn shared_playout_epoch_is_reused_across_audio_and_video() {
    let runtime = runtime_without_offsets();
    let _camera = runtime.activate_camera();
    let _microphone = runtime.activate_microphone();

    assert!(matches!(
        runtime.plan_video_pts(1_000_000, 16_666),
        super::UpstreamPlanDecision::AwaitingPair
    ));
    let audio_first = play(runtime.plan_audio_pts(1_000_000));
    let video_first = play(runtime.plan_video_pts(1_000_000, 16_666));
    let audio_next = play(runtime.plan_audio_pts(1_010_000));

    assert_eq!(video_first.local_pts_us, 0);
    assert_eq!(audio_first.local_pts_us, 0);
    assert_eq!(video_first.due_at, audio_first.due_at);
    assert_eq!(
        audio_next
            .due_at
            .saturating_duration_since(audio_first.due_at),
        Duration::from_micros(10_000)
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn bundled_media_uses_client_epoch_without_pairing_wait() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
        let runtime = runtime_without_offsets();
        let _camera = runtime.activate_camera();
        let _microphone = runtime.activate_microphone();
        let epoch = tokio::time::Instant::now();

        let audio = play(runtime.plan_bundled_pts(
            super::UpstreamMediaKind::Microphone,
            1_000_000,
            1,
            1_000_000,
            epoch,
        ));
        let video = play(runtime.plan_bundled_pts(
            super::UpstreamMediaKind::Camera,
            1_033_333,
            16_666,
            1_000_000,
            epoch,
        ));

        assert_eq!(audio.local_pts_us, 0);
        assert_eq!(video.local_pts_us, 33_333);
        assert_eq!(
            video.due_at.saturating_duration_since(audio.due_at),
            Duration::from_micros(33_333)
        );
        let snapshot = runtime.snapshot();
        assert_eq!(snapshot.latest_microphone_remote_pts_us, Some(1_000_000));
        assert_eq!(snapshot.latest_camera_remote_pts_us, Some(1_033_333));
        assert_eq!(
            snapshot.last_reason,
            "client-bundled upstream media epoch established"
        );
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn bundled_media_uses_runtime_output_calibration_offsets_by_default() {
    temp_env::with_var(
        "LESAVKA_UPSTREAM_BUNDLED_PLAYOUT_DELAY_MS",
        Some("0"),
        || {
            temp_env::with_var_unset("LESAVKA_UPSTREAM_BUNDLED_AUDIO_PLAYOUT_OFFSET_US", || {
                temp_env::with_var_unset(
                    "LESAVKA_UPSTREAM_BUNDLED_VIDEO_PLAYOUT_OFFSET_US",
                    || {
                        let runtime = UpstreamMediaRuntime::new();
                        runtime.set_playout_offsets(120_000, 0);
                        let _camera = runtime.activate_camera();
                        let _microphone = runtime.activate_microphone();
                        let epoch = tokio::time::Instant::now();

                        let audio = play(runtime.plan_bundled_pts(
                            super::UpstreamMediaKind::Microphone,
                            1_000_000,
                            1,
                            1_000_000,
                            epoch,
                        ));
                        let video = play(runtime.plan_bundled_pts(
                            super::UpstreamMediaKind::Camera,
                            1_000_000,
                            16_666,
                            1_000_000,
                            epoch,
                        ));

                        assert_eq!(audio.local_pts_us, video.local_pts_us);
                        assert_eq!(
                            video.due_at.saturating_duration_since(audio.due_at),
                            Duration::from_millis(120)
                        );
                    },
                );
            });
        },
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn bundled_media_explicit_offsets_can_disable_runtime_output_calibration() {
    temp_env::with_var(
        "LESAVKA_UPSTREAM_BUNDLED_PLAYOUT_DELAY_MS",
        Some("0"),
        || {
            temp_env::with_var(
                "LESAVKA_UPSTREAM_BUNDLED_AUDIO_PLAYOUT_OFFSET_US",
                Some("0"),
                || {
                    temp_env::with_var(
                        "LESAVKA_UPSTREAM_BUNDLED_VIDEO_PLAYOUT_OFFSET_US",
                        Some("0"),
                        || {
                            let runtime = UpstreamMediaRuntime::new();
                            runtime.set_playout_offsets(120_000, 0);
                            let _camera = runtime.activate_camera();
                            let _microphone = runtime.activate_microphone();
                            let epoch = tokio::time::Instant::now();

                            let audio = play(runtime.plan_bundled_pts(
                                super::UpstreamMediaKind::Microphone,
                                1_000_000,
                                1,
                                1_000_000,
                                epoch,
                            ));
                            let video = play(runtime.plan_bundled_pts(
                                super::UpstreamMediaKind::Camera,
                                1_000_000,
                                16_666,
                                1_000_000,
                                epoch,
                            ));

                            assert_eq!(audio.due_at, video.due_at);
                        },
                    );
                },
            );
        },
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn bundled_media_applies_output_compensation_without_clipping_for_freshness() {
    temp_env::with_var(
        "LESAVKA_UPSTREAM_BUNDLED_PLAYOUT_DELAY_MS",
        Some("20"),
        || {
            temp_env::with_var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS", Some("1000"), || {
                let runtime = UpstreamMediaRuntime::new();
                runtime.set_playout_offsets(950_000, 0);
                let _camera = runtime.activate_camera();
                let _microphone = runtime.activate_microphone();
                let now = tokio::time::Instant::now();
                let epoch = now + Duration::from_millis(20);

                let audio = play(runtime.plan_bundled_pts(
                    super::UpstreamMediaKind::Microphone,
                    1_000_000,
                    1,
                    1_000_000,
                    epoch,
                ));
                let video = play(runtime.plan_bundled_pts(
                    super::UpstreamMediaKind::Camera,
                    1_000_000,
                    16_666,
                    1_000_000,
                    epoch,
                ));

                assert!(
                    audio.due_at.saturating_duration_since(now) <= Duration::from_millis(50),
                    "audio should be released promptly when video needs nearly the full freshness budget"
                );
                assert_eq!(
                    video.due_at.saturating_duration_since(audio.due_at),
                    Duration::from_millis(950)
                );
                assert!(
                    video.due_at.saturating_duration_since(now) <= Duration::from_secs(1),
                    "compensated video should still land within the one-second live budget"
                );
            });
        },
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn bundled_media_preserves_large_measured_output_compensation_for_sync() {
    temp_env::with_var(
        "LESAVKA_UPSTREAM_BUNDLED_PLAYOUT_DELAY_MS",
        Some("20"),
        || {
            temp_env::with_var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS", Some("1000"), || {
                let runtime = UpstreamMediaRuntime::new();
                runtime.set_playout_offsets(1_090_000, 0);
                let _camera = runtime.activate_camera();
                let _microphone = runtime.activate_microphone();
                let now = tokio::time::Instant::now();
                let epoch = now + Duration::from_millis(20);

                let audio = play(runtime.plan_bundled_pts(
                    super::UpstreamMediaKind::Microphone,
                    1_000_000,
                    1,
                    1_000_000,
                    epoch,
                ));
                let video = play(runtime.plan_bundled_pts(
                    super::UpstreamMediaKind::Camera,
                    1_000_000,
                    16_666,
                    1_000_000,
                    epoch,
                ));

                assert_eq!(
                    video.due_at.saturating_duration_since(audio.due_at),
                    Duration::from_millis(1090),
                    "measured output compensation is sync-critical and must not be clipped"
                );
                assert!(
                    video.due_at.saturating_duration_since(now) > Duration::from_secs(1),
                    "sync wins over freshness when measured UVC/UAC egress latency requires it"
                );
            });
        },
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn bundled_media_keeps_default_smooth_buffer_and_measured_output_compensation() {
    temp_env::with_var_unset("LESAVKA_UPSTREAM_BUNDLED_PLAYOUT_DELAY_MS", || {
        temp_env::with_var_unset("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", || {
            temp_env::with_var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS", Some("1000"), || {
                let runtime = UpstreamMediaRuntime::new();
                runtime.set_playout_offsets(1_090_000, 0);
                let _camera = runtime.activate_camera();
                let _microphone = runtime.activate_microphone();
                let now = tokio::time::Instant::now();
                let epoch = now + Duration::from_millis(350);

                let audio = play(runtime.plan_bundled_pts(
                    super::UpstreamMediaKind::Microphone,
                    1_000_000,
                    1,
                    1_000_000,
                    epoch,
                ));
                let video = play(runtime.plan_bundled_pts(
                    super::UpstreamMediaKind::Camera,
                    1_000_000,
                    16_666,
                    1_000_000,
                    epoch,
                ));

                assert!(
                    audio.due_at.saturating_duration_since(now) >= Duration::from_millis(300),
                    "the default bundled jitter buffer must remain available for smoothness"
                );
                assert_eq!(
                    video.due_at.saturating_duration_since(audio.due_at),
                    Duration::from_millis(1090),
                    "measured UVC/UAC egress compensation remains authoritative after the buffer"
                );
                assert!(
                    video.due_at.saturating_duration_since(now) > Duration::from_secs(1),
                    "sync-critical output delay may exceed the live budget because sync wins"
                );
            });
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn bundled_media_reanchors_future_wait_inside_one_second_freshness_budget() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("350"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS", Some("1000"), || {
            let runtime = runtime_without_offsets();
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();
            let now = tokio::time::Instant::now();
            let stale_epoch = now + Duration::from_secs(20);

            let video = play(runtime.plan_bundled_pts(
                super::UpstreamMediaKind::Camera,
                1_000_000,
                16_666,
                1_000_000,
                stale_epoch,
            ));

            assert!(
                video
                    .due_at
                    .saturating_duration_since(tokio::time::Instant::now())
                    <= Duration::from_secs(1),
                "bundled playout must not preserve a many-second future wait"
            );
            assert!(runtime.snapshot().freshness_reanchors >= 1);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn pairing_window_holds_one_sided_playout_by_default() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
        let runtime = UpstreamMediaRuntime::new();
        let _camera = runtime.activate_camera();

        assert!(matches!(
            runtime.plan_video_pts(1_000_000, 16_666),
            super::UpstreamPlanDecision::AwaitingPair
        ));
        assert!(matches!(
            runtime.plan_video_pts(1_016_666, 16_666),
            super::UpstreamPlanDecision::AwaitingPair
        ));
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn explicit_override_allows_one_sided_playout_for_compatibility() {
    temp_env::with_var("LESAVKA_UPSTREAM_REQUIRE_PAIRED_STARTUP", Some("0"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
            let runtime = UpstreamMediaRuntime::new();
            let _camera = runtime.activate_camera();

            let first = play(runtime.plan_video_pts(1_000_000, 16_666));
            let second = play(runtime.plan_video_pts(1_016_666, 16_666));

            assert_eq!(first.local_pts_us, 0);
            assert_eq!(second.local_pts_us, 16_666);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn overdue_pairing_refreshes_waiting_anchor_before_late_counterpart_arrives() {
    temp_env::with_var(
        "LESAVKA_UPSTREAM_CAMERA_STARTUP_GRACE_MS",
        Some("0"),
        || {
            temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
                let runtime = runtime_without_offsets();
                let _camera = runtime.activate_camera();
                let _microphone = runtime.activate_microphone();

                assert!(matches!(
                    runtime.plan_video_pts(1_000_000, 16_666),
                    super::UpstreamPlanDecision::AwaitingPair
                ));
                assert!(matches!(
                    runtime.plan_video_pts(9_000_000, 16_666),
                    super::UpstreamPlanDecision::AwaitingPair
                ));

                let audio = play(runtime.plan_audio_pts(9_010_000));
                assert!(matches!(
                    runtime.plan_video_pts(9_000_000, 16_666),
                    super::UpstreamPlanDecision::DropBeforeOverlap
                ));
                let video = play(runtime.plan_video_pts(9_016_666, 16_666));

                assert_eq!(audio.local_pts_us, 0);
                assert_eq!(video.local_pts_us, 6_666);
            });
        },
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn map_wrappers_hide_unpaired_and_pre_overlap_packets() {
    let runtime = UpstreamMediaRuntime::new();
    let _camera = runtime.activate_camera();
    let _microphone = runtime.activate_microphone();

    assert_eq!(runtime.map_video_pts(1_000_000, 16_666), None);
    assert_eq!(runtime.map_audio_pts(1_000_000), Some(0));
    assert_eq!(runtime.map_audio_pts(999_999), None);
}

#[test]
#[serial(upstream_media_runtime)]
fn shared_playout_trace_path_keeps_planned_pts_stable() {
    temp_env::with_var("LESAVKA_UPSTREAM_TIMING_TRACE", Some("1"), || {
        let runtime = UpstreamMediaRuntime::new();
        let _camera = runtime.activate_camera();
        let _microphone = runtime.activate_microphone();

        assert!(matches!(
            runtime.plan_video_pts(1_000_000, 16_666),
            super::UpstreamPlanDecision::AwaitingPair
        ));
        let audio = play(runtime.plan_audio_pts(1_000_000));
        let video = play(runtime.plan_video_pts(1_000_000, 16_666));

        assert_eq!(video.local_pts_us, 0);
        assert_eq!(audio.local_pts_us, 0);
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn catastrophic_lateness_reanchors_the_shared_playout_epoch() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("20"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_REANCHOR_LATE_MS", Some("5"), || {
            let runtime = UpstreamMediaRuntime::new();
            runtime.set_playout_offsets(0, 0);
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            let _audio_first = play(runtime.plan_audio_pts(1_000_000));
            let _video_first = play(runtime.plan_video_pts(1_000_000, 16_666));

            std::thread::sleep(Duration::from_millis(30));

            let recovered_audio = play(runtime.plan_audio_pts(1_000_000));
            assert!(
                recovered_audio.due_at > tokio::time::Instant::now(),
                "recovered packet should be scheduled back into the future"
            );
            assert!(
                recovered_audio.late_by <= Duration::from_millis(1),
                "recovered packet should no longer be catastrophically late"
            );

            let recovered_video = play(runtime.plan_video_pts(1_016_666, 16_666));
            assert!(
                recovered_video.due_at > tokio::time::Instant::now(),
                "shared epoch recovery should also move video back into the future"
            );
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn overlap_anchor_gets_a_fresh_playout_budget_when_pairing_finishes_late() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("20"), || {
        let runtime = UpstreamMediaRuntime::new();
        runtime.set_playout_offsets(0, 0);
        let _camera = runtime.activate_camera();
        let _microphone = runtime.activate_microphone();

        assert!(matches!(
            runtime.plan_video_pts(1_000_000, 16_666),
            super::UpstreamPlanDecision::AwaitingPair
        ));

        std::thread::sleep(Duration::from_millis(15));
        let before_pair = tokio::time::Instant::now();
        let audio_first = play(runtime.plan_audio_pts(1_000_000));
        let video_first = play(runtime.plan_video_pts(1_000_000, 16_666));

        assert!(
            audio_first.due_at.saturating_duration_since(before_pair) >= Duration::from_millis(15),
            "audio should keep most of the configured playout budget after late pairing"
        );
        assert!(
            video_first.due_at.saturating_duration_since(before_pair) >= Duration::from_millis(15),
            "video should keep most of the configured playout budget after late pairing"
        );
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn catastrophic_lateness_reanchors_repeatedly_to_preserve_freshness() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("20"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_REANCHOR_LATE_MS", Some("5"), || {
            let runtime = UpstreamMediaRuntime::new();
            runtime.set_playout_offsets(0, 0);
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            let _audio_first = play(runtime.plan_audio_pts(1_000_000));
            let _video_first = play(runtime.plan_video_pts(1_000_000, 16_666));

            std::thread::sleep(Duration::from_millis(30));
            let first_recovered = play(runtime.plan_audio_pts(1_000_000));
            assert!(first_recovered.due_at > tokio::time::Instant::now());
            assert!(first_recovered.late_by <= Duration::from_millis(1));

            std::thread::sleep(Duration::from_millis(30));
            let second_recovered = play(runtime.plan_audio_pts(1_000_001));
            assert!(second_recovered.due_at > tokio::time::Instant::now());
            assert!(
                second_recovered.late_by <= Duration::from_millis(1),
                "0.17 planner must keep healing instead of preserving stale timing"
            );
            assert!(
                runtime.snapshot().freshness_reanchors >= 2,
                "repeated freshness reanchors should be counted for diagnostics"
            );
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn catastrophic_lateness_reanchors_even_after_startup_window() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("20"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_REANCHOR_LATE_MS", Some("5"), || {
            let runtime = UpstreamMediaRuntime::new();
            runtime.set_playout_offsets(0, 0);
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            let _audio_first = play(runtime.plan_audio_pts(1_000_000));
            let _video_first = play(runtime.plan_video_pts(1_000_000, 16_666));
            std::thread::sleep(Duration::from_millis(130));

            let late_audio = play(runtime.plan_audio_pts(1_100_000));
            assert_eq!(late_audio.local_pts_us, 100_000);
            assert!(
                late_audio.late_by <= Duration::from_millis(1),
                "0.17 planner should heal mid-session lateness instead of preserving drift"
            );
            assert!(
                late_audio.due_at > tokio::time::Instant::now(),
                "mid-session freshness healing should push due_at back into the live budget"
            );
            assert!(runtime.snapshot().freshness_reanchors >= 1);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn stale_audio_behind_the_freshest_audio_frontier_is_dropped() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS", Some("50"), || {
            let runtime = runtime_without_offsets();
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            let _audio = play(runtime.plan_audio_pts(1_000_000));
            let _video = play(runtime.plan_video_pts(1_000_000, 16_666));
            let _fresh_audio = play(runtime.plan_audio_pts(2_000_000));

            assert!(matches!(
                runtime.plan_audio_pts(1_900_000),
                super::UpstreamPlanDecision::DropStale("packet exceeded max live lag")
            ));
            assert_eq!(runtime.snapshot().stale_audio_drops, 1);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn stale_video_behind_the_freshest_video_frontier_is_dropped() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS", Some("50"), || {
            let runtime = runtime_without_offsets();
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            let _audio = play(runtime.plan_audio_pts(1_000_000));
            let _video = play(runtime.plan_video_pts(1_000_000, 16_666));
            let _fresh_video = play(runtime.plan_video_pts(2_000_000, 16_666));

            assert!(matches!(
                runtime.plan_video_pts(1_900_000, 16_666),
                super::UpstreamPlanDecision::DropStale("packet exceeded max live lag")
            ));
            let snapshot = runtime.snapshot();
            assert_eq!(snapshot.stale_video_drops, 1);
            assert_eq!(snapshot.video_freezes, 1);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn video_too_far_behind_audio_master_is_dropped_and_counted_as_freeze() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_PAIR_SLACK_US", Some("50000"), || {
            let runtime = runtime_without_offsets();
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            let audio = play(runtime.plan_audio_pts(1_000_000));
            let _video = play(runtime.plan_video_pts(1_000_000, 16_666));
            runtime.mark_audio_presented(audio.local_pts_us, audio.due_at);
            let audio_master = play(runtime.plan_audio_pts(1_200_000));

            assert!(
                matches!(
                    runtime.plan_video_pts(1_100_000, 16_666),
                    super::UpstreamPlanDecision::Play(_)
                ),
                "future planned audio alone must not freeze video before UAC presentation"
            );
            runtime.mark_audio_presented(audio_master.local_pts_us, audio_master.due_at);

            assert!(matches!(
                runtime.plan_video_pts(1_116_666, 16_666),
                super::UpstreamPlanDecision::DropStale(
                    "video frame was too far behind audio master"
                )
            ));
            let snapshot = runtime.snapshot();
            assert_eq!(snapshot.skew_video_drops, 1);
            assert_eq!(snapshot.video_freezes, 1);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn configured_video_delay_does_not_make_the_planner_freeze_video() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_PAIR_SLACK_US", Some("50000"), || {
            let runtime = UpstreamMediaRuntime::new();
            runtime.set_playout_offsets(350_000, 0);
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            let _audio = play(runtime.plan_audio_pts(1_000_000));
            let _video = play(runtime.plan_video_pts(1_000_000, 16_666));
            let _audio_master = play(runtime.plan_audio_pts(1_300_000));

            let video = play(runtime.plan_video_pts(1_100_000, 16_666));
            assert_eq!(video.local_pts_us, 100_000);
            let snapshot = runtime.snapshot();
            assert_eq!(snapshot.skew_video_drops, 0);
            assert_eq!(snapshot.video_freezes, 0);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn browser_visible_video_delay_is_not_reanchored_away() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("350"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS", Some("1000"), || {
            let runtime = runtime_without_offsets();
            runtime.set_playout_offsets(1_090_000, 0);
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            let audio = play(runtime.plan_audio_pts(1_000_000));
            let video = play(runtime.plan_video_pts(1_000_000, 16_666));

            assert!(
                video.due_at.saturating_duration_since(audio.due_at) >= Duration::from_millis(1080),
                "intentional browser-visible video delay must survive the freshness reanchor"
            );
            let snapshot = runtime.snapshot();
            assert_eq!(snapshot.freshness_reanchors, 0);
            assert_eq!(snapshot.stale_video_drops, 0);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn paired_startup_times_out_instead_of_waiting_forever() {
    temp_env::with_var("LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS", Some("0"), || {
        temp_env::with_var("LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS", Some("1"), || {
            let runtime = UpstreamMediaRuntime::new();
            runtime.set_playout_offsets(0, 0);
            let _camera = runtime.activate_camera();

            assert!(matches!(
                runtime.plan_video_pts(1_000_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            std::thread::sleep(Duration::from_millis(3));
            assert!(matches!(
                runtime.plan_video_pts(1_016_666, 16_666),
                super::UpstreamPlanDecision::StartupFailed(
                    "paired upstream startup did not converge before timeout"
                )
            ));
            let snapshot = runtime.snapshot();
            assert_eq!(snapshot.phase, "failed");
            assert_eq!(snapshot.startup_timeouts, 1);
        });
    });
}

#[test]
#[serial(upstream_media_runtime)]
fn planner_snapshot_tracks_presented_playheads_and_skew() {
    let runtime = runtime_without_offsets();
    let _camera = runtime.activate_camera();
    let _microphone = runtime.activate_microphone();

    assert!(matches!(
        runtime.plan_video_pts(1_000_000, 16_666),
        super::UpstreamPlanDecision::AwaitingPair
    ));
    let audio = play(runtime.plan_audio_pts(1_000_000));
    let video = play(runtime.plan_video_pts(1_000_000, 16_666));
    runtime.mark_audio_presented(audio.local_pts_us, audio.due_at);
    runtime.mark_video_presented(video.local_pts_us, video.due_at);

    let snapshot = runtime.snapshot();
    assert_eq!(snapshot.phase, "live");
    assert_eq!(snapshot.last_audio_presented_pts_us, Some(0));
    assert_eq!(snapshot.last_video_presented_pts_us, Some(0));
    assert_eq!(snapshot.planner_skew_ms, Some(0.0));
}

#[test]
#[serial(upstream_media_runtime)]
fn planner_snapshot_tracks_sink_handoff_timing_windows() {
    let runtime = runtime_without_offsets();
    let _camera = runtime.activate_camera();
    let _microphone = runtime.activate_microphone();
    let due_at = tokio::time::Instant::now()
        .checked_sub(Duration::from_millis(5))
        .unwrap_or_else(tokio::time::Instant::now);

    runtime.mark_audio_presented(123_000, due_at);
    std::thread::sleep(Duration::from_millis(1));
    runtime.mark_video_presented(123_000, due_at);

    let snapshot = runtime.snapshot();

    assert!(
        snapshot.sink_handoff_skew_ms.is_some_and(|skew| skew > 0.0),
        "video was handed to its sink after audio"
    );
    assert!(
        snapshot
            .sink_handoff_abs_skew_p95_ms
            .is_some_and(|skew| skew > 0.0),
        "the rolling handoff window should include the audio/video handoff gap"
    );
    assert!(
        snapshot.camera_sink_late_ms.is_some_and(|late| late > 0.0),
        "handoff after due_at should be reported as positive lateness"
    );
    assert!(
        snapshot
            .microphone_sink_late_p95_ms
            .is_some_and(|late| late > 0.0),
        "audio sink lateness should be retained in the rolling window"
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn sink_handoff_window_pairs_by_due_time_not_offset_local_pts() {
    let runtime = runtime_without_offsets();
    let _camera = runtime.activate_camera();
    let _microphone = runtime.activate_microphone();
    let due_at = tokio::time::Instant::now()
        .checked_sub(Duration::from_millis(5))
        .unwrap_or_else(tokio::time::Instant::now);

    runtime.mark_audio_presented(1_200_000, due_at);
    std::thread::sleep(Duration::from_millis(1));
    runtime.mark_video_presented(100_000, due_at);

    let snapshot = runtime.snapshot();

    assert_eq!(snapshot.sink_handoff_window_samples, 1);
    assert!(
        snapshot.sink_handoff_skew_ms.is_some(),
        "offset-compensated streams should still produce handoff evidence when their due times match"
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn planner_snapshot_tracks_client_timing_sidecar_metrics() {
    let runtime = runtime_without_offsets();

    runtime.record_client_timing(
        super::UpstreamMediaKind::Camera,
        super::UpstreamClientTiming {
            capture_pts_us: 1_060_000,
            send_pts_us: 1_080_000,
            queue_depth: 2,
            queue_age_ms: 20,
        },
    );
    std::thread::sleep(Duration::from_millis(2));
    runtime.record_client_timing(
        super::UpstreamMediaKind::Microphone,
        super::UpstreamClientTiming {
            capture_pts_us: 1_000_000,
            send_pts_us: 1_030_000,
            queue_depth: 3,
            queue_age_ms: 30,
        },
    );

    let snapshot = runtime.snapshot();

    assert_eq!(snapshot.client_capture_skew_ms, Some(60.0));
    assert_eq!(snapshot.client_send_skew_ms, Some(50.0));
    assert_eq!(snapshot.client_capture_abs_skew_p95_ms, Some(60.0));
    assert_eq!(snapshot.client_send_abs_skew_p95_ms, Some(50.0));
    assert_eq!(snapshot.camera_client_queue_age_ms, Some(20.0));
    assert_eq!(snapshot.microphone_client_queue_age_ms, Some(30.0));
    assert_eq!(snapshot.camera_client_queue_age_p95_ms, Some(20.0));
    assert_eq!(snapshot.microphone_client_queue_age_p95_ms, Some(30.0));
    assert!(
        snapshot
            .server_receive_skew_ms
            .is_some_and(|skew| skew < 0.0),
        "camera was received before microphone, so camera-minus-mic receive skew should be negative"
    );
    assert!(
        snapshot
            .server_receive_abs_skew_p95_ms
            .is_some_and(|skew| skew > 0.0),
        "server receive jitter should be retained as an absolute p95"
    );
    assert!(
        snapshot
            .camera_server_receive_age_ms
            .zip(snapshot.microphone_server_receive_age_ms)
            .is_some_and(|(camera_age, microphone_age)| camera_age >= microphone_age),
        "the earlier camera receive sample should be at least as old as the later mic sample"
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn planner_pairs_client_timing_by_nearby_send_time_not_latest_packet() {
    let runtime = runtime_without_offsets();

    runtime.record_client_timing(
        super::UpstreamMediaKind::Camera,
        super::UpstreamClientTiming {
            capture_pts_us: 1_000_000,
            send_pts_us: 1_000_000,
            queue_depth: 1,
            queue_age_ms: 5,
        },
    );
    runtime.record_client_timing(
        super::UpstreamMediaKind::Microphone,
        super::UpstreamClientTiming {
            capture_pts_us: 1_010_000,
            send_pts_us: 1_010_000,
            queue_depth: 1,
            queue_age_ms: 5,
        },
    );
    runtime.record_client_timing(
        super::UpstreamMediaKind::Microphone,
        super::UpstreamClientTiming {
            capture_pts_us: 2_500_000,
            send_pts_us: 2_500_000,
            queue_depth: 1,
            queue_age_ms: 5,
        },
    );

    let snapshot = runtime.snapshot();

    assert_eq!(snapshot.client_capture_skew_ms, Some(-10.0));
    assert_eq!(snapshot.client_send_skew_ms, Some(-10.0));
    assert_eq!(snapshot.client_capture_abs_skew_p95_ms, Some(10.0));
    assert_eq!(snapshot.client_send_abs_skew_p95_ms, Some(10.0));
}

#[test]
#[serial(upstream_media_runtime)]
fn default_runtime_covers_video_map_play_path() {
    let runtime = UpstreamMediaRuntime::default();
    let _camera = runtime.activate_camera();
    let _microphone = runtime.activate_microphone();

    assert!(matches!(
        runtime.plan_video_pts(1_000_000, 16_666),
        super::UpstreamPlanDecision::AwaitingPair
    ));
    let _audio = play(runtime.plan_audio_pts(1_000_000));
    assert_eq!(runtime.map_video_pts(1_000_000, 16_666), Some(0));
}

#[tokio::test(flavor = "current_thread")]
#[serial(upstream_media_runtime)]
async fn wait_for_audio_master_returns_false_after_sync_grace_has_already_passed() {
    let runtime = UpstreamMediaRuntime::new();
    let _camera = runtime.activate_camera();
    let _microphone = runtime.activate_microphone();

    assert!(
        !runtime
            .wait_for_audio_master(
                123_456,
                tokio::time::Instant::now()
                    .checked_sub(Duration::from_millis(400))
                    .unwrap_or_else(tokio::time::Instant::now),
            )
            .await
    );
}

#[test]
#[serial(upstream_media_runtime)]
fn timing_trace_paths_emit_overlap_and_dropbeforeoverlap_details() {
    temp_env::with_var("LESAVKA_UPSTREAM_TIMING_TRACE", Some("1"), || {
        with_info_tracing(|| {
            let runtime = UpstreamMediaRuntime::new();
            let _camera = runtime.activate_camera();
            let _microphone = runtime.activate_microphone();

            assert!(matches!(
                runtime.plan_video_pts(1_300_000, 16_666),
                super::UpstreamPlanDecision::AwaitingPair
            ));
            assert!(matches!(
                runtime.plan_audio_pts(1_000_000),
                super::UpstreamPlanDecision::DropBeforeOverlap
            ));
            let _video = play(runtime.plan_video_pts(1_300_000, 16_666));
            assert!(matches!(
                runtime.plan_audio_pts(1_000_000),
                super::UpstreamPlanDecision::DropBeforeOverlap
            ));
        });
    });
}
