// Synthetic performance contract for the upstream media queues.
//
// Scope: deterministic backpressure simulation without physical devices.
// Targets: `client/src/uplink_latency_harness.rs`.
// Why: A/V sync depends on dropping stale media under stalls instead of
// preserving a growing delay buffer.

#[path = "../../../../client/src/uplink_latency_harness.rs"]
#[allow(warnings)]
mod uplink_latency_harness;

use std::time::Duration;
use uplink_latency_harness::{UplinkHarnessConfig, UplinkQueuePolicy, run_uplink_harness};

#[derive(Clone, Copy)]
struct BundleTiming {
    client_queue_age_ms: u32,
    server_receive_age_ms: u32,
}

fn camera_stall_config() -> UplinkHarnessConfig {
    UplinkHarnessConfig {
        capture_interval: Duration::from_millis(33),
        consume_interval: Duration::from_millis(33),
        queue_capacity: 32,
        freshness_max_age: Some(Duration::from_millis(350)),
        total_packets: 240,
        stall_after: Some(Duration::from_millis(800)),
        stall_duration: Duration::from_secs(2),
    }
}

fn microphone_stall_config() -> UplinkHarnessConfig {
    UplinkHarnessConfig {
        capture_interval: Duration::from_millis(20),
        consume_interval: Duration::from_millis(20),
        queue_capacity: 16,
        freshness_max_age: Some(Duration::from_millis(400)),
        total_packets: 320,
        stall_after: Some(Duration::from_millis(600)),
        stall_duration: Duration::from_secs(2),
    }
}

fn percentile_95(values: &mut [u32]) -> u32 {
    assert!(!values.is_empty(), "p95 requires at least one sample");
    values.sort_unstable();
    let index = ((values.len() - 1) * 95).div_ceil(100);
    values[index]
}

fn synthetic_bundle_timings() -> Vec<BundleTiming> {
    (0..180)
        .map(|frame| {
            let encode_ms = 18 + (frame % 5) * 3;
            let queue_jitter_ms = if frame % 47 == 0 { 95 } else { frame % 7 };
            let network_ms = 58 + (frame % 11) * 4;
            let client_queue_age_ms = encode_ms + queue_jitter_ms;
            BundleTiming {
                client_queue_age_ms,
                server_receive_age_ms: client_queue_age_ms + network_ms,
            }
        })
        .collect()
}

#[test]
fn freshness_first_camera_policy_keeps_video_delivery_age_bounded_under_stall() {
    let result = run_uplink_harness(camera_stall_config(), UplinkQueuePolicy::DropOldestWhenFull);

    assert!(
        result.dropped_packets > 0,
        "camera stall should drop stale frames instead of preserving backlog"
    );
    assert!(
        result.max_delivery_age <= Duration::from_millis(350),
        "camera delivery age must stay <=350ms under synthetic stall, got {:?}",
        result.max_delivery_age
    );
}

#[test]
fn freshness_first_microphone_policy_keeps_audio_delivery_age_bounded_under_stall() {
    let result = run_uplink_harness(
        microphone_stall_config(),
        UplinkQueuePolicy::DropOldestWhenFull,
    );

    assert!(
        result.dropped_packets > 0,
        "microphone stall should drop stale chunks instead of preserving backlog"
    );
    assert!(
        result.max_delivery_age <= Duration::from_millis(400),
        "microphone delivery age must stay <=400ms under synthetic stall, got {:?}",
        result.max_delivery_age
    );
}

#[test]
fn bundled_upstream_queue_p95_stays_inside_phase_one_budget() {
    let timings = synthetic_bundle_timings();
    let mut queue_ages = timings
        .iter()
        .map(|timing| timing.client_queue_age_ms)
        .collect::<Vec<_>>();

    let p95 = percentile_95(&mut queue_ages);

    assert!(
        p95 < 150,
        "client bundle queue p95 should stay under 150ms, got {p95}ms"
    );
}

#[test]
fn server_receive_age_p95_stays_inside_phase_one_budget() {
    let timings = synthetic_bundle_timings();
    let mut receive_ages = timings
        .iter()
        .map(|timing| timing.server_receive_age_ms)
        .collect::<Vec<_>>();

    let p95 = percentile_95(&mut receive_ages);

    assert!(
        p95 < 250,
        "server receive age p95 should stay under 250ms, got {p95}ms"
    );
}

#[test]
fn preserve_backlog_policy_would_violate_the_lip_sync_budget() {
    let mut config = camera_stall_config();
    config.freshness_max_age = None;
    let result = run_uplink_harness(config, UplinkQueuePolicy::PreserveBacklog);

    assert!(
        result.max_delivery_age >= Duration::from_secs(1),
        "preserve-backlog policy should demonstrate the regression class, got {:?}",
        result.max_delivery_age
    );
}
