// Chaos contract for upstream media backpressure behavior.
//
// Scope: prove the freshness-first code path still models stalls, queue
// pressure, and stale media drops as first-class failure modes.
// Targets: `client/src/uplink_latency_harness.rs`,
// `client/src/uplink_fresh_queue.rs`, and `client/src/app/uplink_media/*`.
// Why: real WAN calls fail by forming backlog; chaos coverage keeps the code
// biased toward bounded stutter over minutes of delayed media.

const LATENCY_HARNESS: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/client/src/uplink_latency_harness.rs"
));
const FRESH_QUEUE: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/client/src/uplink_fresh_queue.rs"
));
const DROP_LOGGING: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/client/src/app/uplink_media/drop_logging.rs"
));

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

mod input {
    pub mod camera {
        #[derive(Clone, Copy)]
        pub enum CameraCodec {
            Hevc,
        }

        #[derive(Clone, Copy)]
        pub struct CameraConfig {
            pub codec: CameraCodec,
        }
    }
}

use lesavka_common::lesavka::{AudioEncoding, AudioPacket, UpstreamMediaBundle, VideoPacket};
use std::time::Duration;
use uplink_fresh_queue::{FreshPacketQueue, FreshQueueConfig, FreshQueuePolicy};

include!("../../../../client/src/app/uplink_media/video_keyframes.rs");

fn bundle(seq: u64, capture_pts_us: u64, video_data: Vec<u8>) -> UpstreamMediaBundle {
    UpstreamMediaBundle {
        session_id: 13,
        seq,
        capture_start_us: capture_pts_us.saturating_sub(20_000),
        capture_end_us: capture_pts_us.saturating_add(20_000),
        video: Some(VideoPacket {
            seq,
            pts: capture_pts_us,
            data: video_data,
            client_capture_pts_us: capture_pts_us,
            ..Default::default()
        }),
        audio: vec![AudioPacket {
            seq,
            pts: capture_pts_us,
            client_capture_pts_us: capture_pts_us,
            ..Default::default()
        }],
        audio_sample_rate: 48_000,
        audio_channels: 2,
        audio_encoding: AudioEncoding::PcmS16le as i32,
        video_width: 1280,
        video_height: 720,
        video_fps: 30,
    }
}

#[test]
fn upstream_backpressure_model_prefers_bounded_drops_over_latency_debt() {
    for marker in [
        "freshness_max_age",
        "max_queue_depth",
        "preserve_backlog_camera_policy_accumulates_stale_video_after_a_stall",
        "drop_oldest_policy_keeps_media_live_by_sacrificing_old_packets",
    ] {
        assert!(
            LATENCY_HARNESS.contains(marker),
            "latency harness should preserve chaos marker {marker}"
        );
    }

    for marker in [
        "FreshQueuePolicy::LatestOnly",
        "dropped_queue_full",
        "dropped_stale",
        "pop_fresh_discards_stale_packets_before_returning_live_media",
    ] {
        assert!(
            FRESH_QUEUE.contains(marker),
            "fresh queue should preserve chaos marker {marker}"
        );
    }

    assert!(
        DROP_LOGGING
            .contains("queue is dropping stale/superseded packets to preserve live A/V sync"),
        "operator logs should explain freshness-first drops during chaos conditions"
    );
}

#[tokio::test]
async fn jitter_burst_latest_only_policy_outputs_the_newest_complete_bundle() {
    let queue = FreshPacketQueue::new(FreshQueueConfig {
        capacity: 8,
        max_age: Duration::from_millis(150),
        policy: FreshQueuePolicy::LatestOnly,
    });

    let _ = queue.push(
        bundle(1, 1_000_000, vec![0, 0, 0, 1, 0x26, 0x01]),
        Duration::from_millis(120),
    );
    let _ = queue.push(
        bundle(2, 1_033_333, vec![0, 0, 0, 1, 0x02, 0x01]),
        Duration::from_millis(80),
    );
    let _ = queue.push(
        bundle(3, 1_066_666, vec![0, 0, 0, 1, 0x26, 0x01]),
        Duration::from_millis(20),
    );

    let popped = queue.pop_fresh().await;
    let packet = popped.packet.expect("fresh bundle after jitter burst");

    assert_eq!(popped.dropped_stale, 2);
    assert_eq!(packet.seq, 3);
    assert!(packet.video.is_some());
    assert_eq!(packet.audio.len(), 1);
}

#[tokio::test]
async fn reordered_stale_arrival_is_dropped_as_a_whole_bundle() {
    let queue = FreshPacketQueue::new(FreshQueueConfig {
        capacity: 4,
        max_age: Duration::from_millis(150),
        policy: FreshQueuePolicy::DrainOldest,
    });

    let _ = queue.push(
        bundle(10, 1_300_000, vec![0, 0, 0, 1, 0x26, 0x01]),
        Duration::from_millis(220),
    );
    let _ = queue.push(
        bundle(8, 1_233_334, vec![0, 0, 0, 1, 0x26, 0x01]),
        Duration::from_millis(15),
    );

    let popped = queue.pop_fresh().await;
    let packet = popped.packet.expect("fresh reordered bundle");

    assert_eq!(popped.dropped_stale, 1);
    assert_eq!(packet.seq, 8);
    assert!(packet.video.is_some());
    assert_eq!(packet.audio.len(), 1);
}

#[test]
fn missing_keyframe_after_capture_gap_holds_predictive_video_until_irap() {
    let mut waiting_for_keyframe = false;
    assert!(upstream_camera_uses_hevc(Some(
        input::camera::CameraConfig {
            codec: input::camera::CameraCodec::Hevc,
        }
    )));
    note_hevc_capture_gap(true, &mut waiting_for_keyframe);

    let delta = bundle(20, 2_000_000, vec![0, 0, 0, 1, 0x02, 0x01]);
    let recovery = bundle(21, 2_033_333, vec![0, 0, 0, 1, 0x26, 0x01]);

    assert!(waiting_for_keyframe);
    assert!(should_hold_hevc_bundle_for_keyframe_recovery(
        waiting_for_keyframe,
        &delta
    ));
    assert!(!should_hold_hevc_bundle_for_keyframe_recovery(
        waiting_for_keyframe,
        &recovery
    ));
    assert!(bundle_has_hevc_recovery_keyframe(&recovery));
}
