use super::*;

    #[test]
    /// Keeps `audio_timing_metadata_is_stamped_before_async_queue_pop` explicit because it sits on the live uplink path, where stale media must be dropped instead of queued into latency.
    /// Inputs are the typed parameters; output is the return value or side effect.
    fn audio_timing_metadata_is_stamped_before_async_queue_pop() {
        std::thread::sleep(Duration::from_millis(5));
        let packet_pts_us = crate::live_capture_clock::capture_pts_us().saturating_sub(2_000);
        let mut packet = AudioPacket {
            pts: packet_pts_us,
            ..AudioPacket::default()
        };

        let enqueue_age = stamp_audio_timing_metadata_at_enqueue(&mut packet);
        let capture_pts_us = packet.client_capture_pts_us;
        let send_pts_us = packet.client_send_pts_us;
        std::thread::sleep(Duration::from_millis(5));
        attach_audio_queue_metadata(
            &mut packet,
            3,
            enqueue_age.saturating_add(Duration::from_millis(5)),
        );

        assert!(packet.seq > 0);
        assert_eq!(packet.client_queue_depth, 3);
        assert!(packet.client_queue_age_ms >= 5);
        assert_eq!(packet.client_capture_pts_us, capture_pts_us);
        assert_eq!(packet.client_send_pts_us, send_pts_us);
        assert!(
            packet.client_send_pts_us >= packet.client_capture_pts_us,
            "enqueue/send stamp must be on or after the shared-clock capture estimate"
        );
        let capture_to_enqueue = Duration::from_micros(
            packet
                .client_send_pts_us
                .saturating_sub(packet.client_capture_pts_us),
        );
        assert_eq!(
            capture_to_enqueue, enqueue_age,
            "enqueue timing metadata should stay anchored to the pre-pop stamp"
        );
        assert!(
            packet.client_queue_age_ms >= duration_ms_u32(enqueue_age).saturating_add(4),
            "queue age should include the simulated async pop delay without mutating send timing"
        );
    }

    #[test]
    /// Keeps `video_timing_metadata_is_stamped_before_async_queue_pop` explicit because it sits on the live uplink path, where stale media must be dropped instead of queued into latency.
    /// Inputs are the typed parameters; output is the return value or side effect.
    fn video_timing_metadata_is_stamped_before_async_queue_pop() {
        std::thread::sleep(Duration::from_millis(5));
        let packet_pts_us = crate::live_capture_clock::capture_pts_us().saturating_sub(3_000);
        let mut packet = VideoPacket {
            pts: packet_pts_us,
            ..VideoPacket::default()
        };

        let enqueue_age = stamp_video_timing_metadata_at_enqueue(&mut packet);
        let capture_pts_us = packet.client_capture_pts_us;
        let send_pts_us = packet.client_send_pts_us;
        std::thread::sleep(Duration::from_millis(5));
        attach_video_queue_metadata(
            &mut packet,
            4,
            enqueue_age.saturating_add(Duration::from_millis(5)),
        );

        assert!(packet.seq > 0);
        assert_eq!(packet.client_queue_depth, 4);
        assert!(packet.client_queue_age_ms >= 5);
        assert_eq!(packet.client_capture_pts_us, capture_pts_us);
        assert_eq!(packet.client_send_pts_us, send_pts_us);
        assert!(
            packet.client_send_pts_us >= packet.client_capture_pts_us,
            "enqueue/send stamp must be on or after the shared-clock capture estimate"
        );
        let capture_to_enqueue = Duration::from_micros(
            packet
                .client_send_pts_us
                .saturating_sub(packet.client_capture_pts_us),
        );
        assert_eq!(
            capture_to_enqueue, enqueue_age,
            "enqueue timing metadata should stay anchored to the pre-pop stamp"
        );
        assert!(
            packet.client_queue_age_ms >= duration_ms_u32(enqueue_age).saturating_add(4),
            "queue age should include the simulated async pop delay without mutating send timing"
        );
    }

    #[test]
    /// Keeps `stale_source_timestamps_are_clamped_before_bundling` explicit because it sits on the live uplink path, where stale media must be dropped instead of queued into latency.
    /// Inputs are the typed parameters; output is the return value or side effect.
    fn stale_source_timestamps_are_clamped_before_bundling() {
        let enqueue_pts_us = crate::live_capture_clock::capture_pts_us();
        let stale_pts_us = enqueue_pts_us.saturating_sub(30_000_000);
        let mut audio = AudioPacket {
            pts: stale_pts_us,
            ..AudioPacket::default()
        };
        let mut video = VideoPacket {
            pts: stale_pts_us,
            ..VideoPacket::default()
        };

        let audio_age = stamp_audio_timing_metadata_at_enqueue(&mut audio);
        let video_age = stamp_video_timing_metadata_at_enqueue(&mut video);

        assert_eq!(audio.pts, audio.client_capture_pts_us);
        assert_eq!(video.pts, video.client_capture_pts_us);
        assert!(
            audio_age <= crate::live_capture_clock::upstream_source_lag_cap(),
            "audio capture timestamp should not resurrect stale source timing"
        );
        assert!(
            video_age <= crate::live_capture_clock::upstream_source_lag_cap(),
            "video capture timestamp should not resurrect stale source timing"
        );
    }

    #[test]
    /// Keeps `startup_epoch_auto_heal_defaults_to_one_safe_delayed_recovery` explicit because the first Google Meet join can latch a stale UAC epoch.
    /// Inputs are optional env-style values; output is the parsed recovery delay.
    fn startup_epoch_auto_heal_defaults_to_one_safe_delayed_recovery() {
        assert_eq!(
            parse_upstream_epoch_auto_heal_delay(None, None),
            Some(Duration::from_millis(DEFAULT_UPSTREAM_AUTO_HEAL_AFTER_MS))
        );
        assert_eq!(
            parse_upstream_epoch_auto_heal_delay(Some("1"), Some("12000")),
            Some(Duration::from_millis(12_000))
        );
        assert_eq!(
            parse_upstream_epoch_auto_heal_delay(Some("true"), Some("0")),
            None
        );
    }

    #[test]
    /// Keeps `startup_epoch_auto_heal_can_be_disabled_for_lab_runs` explicit because recovery should be operator-controllable during calibration.
    /// Inputs are disable spellings; output is no scheduled recovery.
    fn startup_epoch_auto_heal_can_be_disabled_for_lab_runs() {
        for raw in ["0", "false", "off", "no", " FALSE "] {
            assert_eq!(parse_upstream_epoch_auto_heal_delay(Some(raw), None), None);
        }
        assert!(!disables_upstream_epoch_auto_heal("yes"));
    }

    #[test]
    /// Keeps live camera codec switching bounded to the negotiated server UVC contract.
    #[serial_test::serial]
    fn live_camera_codec_override_obeys_server_contract_unless_forced() {
        use crate::input::camera::{CameraCodec, CameraConfig};
        use crate::live_media_control::MediaCameraCodecChoice;

        let cfg = CameraConfig {
            codec: CameraCodec::Mjpeg,
            width: 1280,
            height: 720,
            fps: 30,
        };

        assert_eq!(camera_codec_id(CameraCodec::Mjpeg), "mjpeg");
        assert_eq!(camera_codec_id(CameraCodec::Hevc), "hevc");
        assert_eq!(camera_codec_id(CameraCodec::H264), "h264");
        assert_eq!(parse_live_camera_codec("jpeg"), Some(CameraCodec::Mjpeg));
        assert_eq!(parse_live_camera_codec("h.265"), Some(CameraCodec::Hevc));
        assert_eq!(parse_live_camera_codec("h264"), Some(CameraCodec::H264));
        assert_eq!(parse_live_camera_codec("vp9"), None);

        assert_eq!(
            camera_config_with_live_codec(Some(cfg), &MediaCameraCodecChoice::Inherit)
                .expect("inherited config")
                .codec,
            CameraCodec::Mjpeg
        );
        assert_eq!(
            camera_config_with_live_codec(
                Some(cfg),
                &MediaCameraCodecChoice::selected(Some("hevc".to_string())),
            )
            .expect("mismatched config should stay negotiated")
            .codec,
            CameraCodec::Mjpeg
        );
        temp_env::with_var("LESAVKA_CAM_CODEC_FORCE", Some("1"), || {
            assert_eq!(
                camera_config_with_live_codec(
                    Some(cfg),
                    &MediaCameraCodecChoice::selected(Some("hevc".to_string())),
                )
                .expect("forced mismatch should override")
                .codec,
                CameraCodec::Hevc
            );
        });
        assert!(camera_config_with_live_codec(None, &MediaCameraCodecChoice::Inherit).is_none());
    }

    #[test]
    /// Keeps HEVC recovery explicit because freshness drops can otherwise resume from predictive frames.
    fn hevc_keyframe_detection_recognizes_irap_access_units() {
        assert!(contains_hevc_irap(&[0, 0, 0, 1, 0x26, 0x01, 0xaa]));
        assert!(contains_hevc_irap(&[0, 0, 1, 0x28, 0x01, 0xaa]));
        assert!(!contains_hevc_irap(&[0, 0, 0, 1, 0x02, 0x01, 0xaa]));
    }

    #[test]
    /// Keeps queue-drop recovery explicit because stale HEVC bundles should not corrupt the decoder.
    fn hevc_recovery_holds_delta_bundle_until_next_keyframe() {
        let delta_bundle = UpstreamMediaBundle {
            video: Some(VideoPacket {
                data: vec![0, 0, 0, 1, 0x02, 0x01, 0xaa],
                ..Default::default()
            }),
            ..Default::default()
        };
        let keyframe_bundle = UpstreamMediaBundle {
            video: Some(VideoPacket {
                data: vec![0, 0, 0, 1, 0x26, 0x01, 0xaa],
                ..Default::default()
            }),
            ..Default::default()
        };

        assert!(should_hold_hevc_bundle_for_keyframe_recovery(
            true,
            &delta_bundle
        ));
        assert!(!should_hold_hevc_bundle_for_keyframe_recovery(
            true,
            &keyframe_bundle
        ));
        assert!(bundle_has_hevc_recovery_keyframe(&keyframe_bundle));
    }

    #[test]
    /// Keeps capture-gap recovery explicit because dropped encoded HEVC samples can corrupt every following delta frame.
    fn hevc_capture_gap_enters_recovery_until_keyframe() {
        let mut waiting = false;
        note_hevc_capture_gap(false, &mut waiting);
        assert!(!waiting);

        note_hevc_capture_gap(true, &mut waiting);
        assert!(waiting);
    }

    /// Verifies the live uplink queue emits one physically bundled HEVC frame and PCM span.
    ///
    /// Inputs: pre-stamped HEVC video plus two nearby audio packets, exactly as
    /// physical capture workers would hand them to the bundler. Outputs:
    /// assertions over the queued `UpstreamMediaBundle`. Why: the real
    /// client-to-server stream should preserve the same pairing contract as the
    /// synthetic probe before gRPC can add network timing noise.
    #[cfg(not(coverage))]
    #[tokio::test]
    async fn hevc_video_and_nearby_audio_leave_live_uplink_as_one_bundle() {
        let temp_dir = tempfile::tempdir().expect("tempdir");
        let telemetry_path = temp_dir.path().join("uplink.json");
        let telemetry =
            crate::uplink_telemetry::UplinkTelemetryPublisher::new(telemetry_path, true, true);
        let camera_telemetry =
            telemetry.handle(crate::uplink_telemetry::UpstreamStreamKind::Camera);
        let microphone_telemetry =
            telemetry.handle(crate::uplink_telemetry::UpstreamStreamKind::Microphone);
        let queue: crate::uplink_fresh_queue::FreshPacketQueue<UpstreamMediaBundle> =
            crate::uplink_fresh_queue::FreshPacketQueue::new(BUNDLED_MEDIA_UPLINK_QUEUE);
        let drop_log = std::sync::Arc::new(std::sync::Mutex::new(UplinkDropLogLimiter::new(
            "test-bundled-hevc",
            "test",
        )));
        let mut bundle_seq = 0_u64;
        let video = VideoPacket {
            pts: 1_000_000,
            data: vec![0, 0, 0, 1, 0x26, 0x01, 0xaa],
            seq: 10,
            effective_fps: 30,
            client_capture_pts_us: 1_000_000,
            client_send_pts_us: 1_005_000,
            client_queue_depth: 1,
            client_queue_age_ms: 5,
            ..Default::default()
        };
        let audio = vec![
            AudioPacket {
                pts: 980_000,
                data: vec![0x11; 1_920],
                seq: 20,
                client_capture_pts_us: 980_000,
                client_send_pts_us: 1_005_000,
                client_queue_depth: 1,
                client_queue_age_ms: 25,
                ..Default::default()
            },
            AudioPacket {
                pts: 1_010_000,
                data: vec![0x22; 1_920],
                seq: 21,
                client_capture_pts_us: 1_010_000,
                client_send_pts_us: 1_015_000,
                client_queue_depth: 1,
                client_queue_age_ms: 5,
                ..Default::default()
            },
        ];

        emit_bundled_media(
            42,
            &mut bundle_seq,
            Some(video),
            audio,
            &queue,
            &camera_telemetry,
            &microphone_telemetry,
            &drop_log,
        );

        let popped = queue.pop_fresh().await;
        let bundle = popped.packet.expect("queued bundled media");
        let video = bundle.video.as_ref().expect("bundled video");
        assert_eq!(bundle.session_id, 42);
        assert_eq!(bundle.seq, 1);
        assert_eq!(bundle.capture_start_us, 980_000);
        assert_eq!(bundle.capture_end_us, 1_010_000);
        assert_eq!(bundle.audio.len(), 2);
        assert!(video.data.windows(4).any(|window| window == [0, 0, 0, 1]));
        assert_eq!(video.client_capture_pts_us, 1_000_000);
        assert_eq!(video.client_send_pts_us, 1_005_000);
        assert_eq!(video.client_queue_depth, popped.queue_depth as u32);
        assert!(bundle.audio.iter().all(|packet| {
            packet.client_capture_pts_us >= bundle.capture_start_us
                && packet.client_capture_pts_us <= bundle.capture_end_us
                && packet.client_send_pts_us >= packet.client_capture_pts_us
                && packet.client_queue_depth == popped.queue_depth as u32
        }));
    }

    /// Verifies stale mic packets cannot poison an otherwise fresh video bundle.
    ///
    /// Inputs: one fresh HEVC frame plus one mic packet older than the live
    /// pairing window. Output: the queued bundle keeps video and drops the
    /// stale audio. Why: production logs showed Opus-era mic lag creating
    /// 250ms+ capture spans that made the server discard whole A/V bundles.
    #[cfg(not(coverage))]
    #[tokio::test]
    async fn stale_audio_is_dropped_before_it_can_poison_video_bundle() {
        let temp_dir = tempfile::tempdir().expect("tempdir");
        let telemetry_path = temp_dir.path().join("uplink.json");
        let telemetry =
            crate::uplink_telemetry::UplinkTelemetryPublisher::new(telemetry_path, true, true);
        let camera_telemetry =
            telemetry.handle(crate::uplink_telemetry::UpstreamStreamKind::Camera);
        let microphone_telemetry =
            telemetry.handle(crate::uplink_telemetry::UpstreamStreamKind::Microphone);
        let queue: crate::uplink_fresh_queue::FreshPacketQueue<UpstreamMediaBundle> =
            crate::uplink_fresh_queue::FreshPacketQueue::new(BUNDLED_MEDIA_UPLINK_QUEUE);
        let drop_log = std::sync::Arc::new(std::sync::Mutex::new(UplinkDropLogLimiter::new(
            "test-bundled-hevc",
            "test",
        )));
        let mut bundle_seq = 0_u64;
        let video = VideoPacket {
            pts: 1_000_000,
            data: vec![0, 0, 0, 1, 0x26, 0x01, 0xaa],
            seq: 10,
            client_capture_pts_us: 1_000_000,
            client_send_pts_us: 1_005_000,
            ..Default::default()
        };
        let audio = vec![
            AudioPacket {
                pts: 700_000,
                data: vec![0x11; 1_920],
                seq: 20,
                client_capture_pts_us: 700_000,
                client_send_pts_us: 1_005_000,
                ..Default::default()
            },
            AudioPacket {
                pts: 1_020_000,
                data: vec![0x22; 1_920],
                seq: 21,
                client_capture_pts_us: 1_020_000,
                client_send_pts_us: 1_020_000,
                ..Default::default()
            },
        ];

        emit_bundled_media(
            42,
            &mut bundle_seq,
            Some(video),
            audio,
            &queue,
            &camera_telemetry,
            &microphone_telemetry,
            &drop_log,
        );

        let bundle = queue.pop_fresh().await.packet.expect("queued bundle");
        assert!(bundle.video.is_some());
        assert_eq!(bundle.audio.len(), 1);
        assert_eq!(bundle.audio[0].client_capture_pts_us, 1_020_000);
        assert_eq!(bundle.capture_start_us, 1_000_000);
        assert_eq!(bundle.capture_end_us, 1_020_000);
    }
