#[cfg(not(coverage))]
const VIDEO_UPLINK_QUEUE: crate::uplink_fresh_queue::FreshQueueConfig =
    crate::uplink_fresh_queue::FreshQueueConfig {
        capacity: 32,
        max_age: Duration::from_millis(350),
        policy: crate::uplink_fresh_queue::FreshQueuePolicy::LatestOnly,
    };

#[cfg(not(coverage))]
const AUDIO_UPLINK_QUEUE: crate::uplink_fresh_queue::FreshQueueConfig =
    crate::uplink_fresh_queue::FreshQueueConfig {
        capacity: 64,
        max_age: Duration::from_millis(400),
        policy: crate::uplink_fresh_queue::FreshQueuePolicy::DrainOldest,
    };

#[cfg(not(coverage))]
const BUNDLED_MEDIA_UPLINK_QUEUE: crate::uplink_fresh_queue::FreshQueueConfig =
    crate::uplink_fresh_queue::FreshQueueConfig {
        capacity: 4,
        max_age: Duration::from_secs(1),
        policy: crate::uplink_fresh_queue::FreshQueuePolicy::LatestOnly,
    };

#[cfg(not(coverage))]
const BUNDLED_AUDIO_FLUSH_INTERVAL: Duration = Duration::from_millis(20);

#[cfg(not(coverage))]
const BUNDLED_AUDIO_MAX_PENDING: usize = 8;

#[cfg(not(coverage))]
const BUNDLED_VIDEO_AUDIO_GRACE: Duration = Duration::from_millis(30);
const DEFAULT_BUNDLED_AUDIO_VIDEO_MAX_SPAN_MS: u64 = 90;

#[cfg(not(coverage))]
const BUNDLED_CAPTURE_EVENT_CHANNEL_CAPACITY: usize = 64;

#[cfg(not(coverage))]
#[derive(Debug)]
enum BundledCaptureEvent {
    Audio(AudioPacket),
    Video(VideoPacket),
    Restart,
}

#[cfg(not(coverage))]
/// Keeps `bundle_captured_media` 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 bundle_captured_media(
    event_rx: std::sync::mpsc::Receiver<BundledCaptureEvent>,
    stop: Arc<AtomicBool>,
    queue: crate::uplink_fresh_queue::FreshPacketQueue<UpstreamMediaBundle>,
    video_required: bool,
    camera_telemetry: crate::uplink_telemetry::UplinkTelemetryHandle,
    microphone_telemetry: crate::uplink_telemetry::UplinkTelemetryHandle,
    drop_log: Arc<std::sync::Mutex<UplinkDropLogLimiter>>,
) {
    static BUNDLED_SESSION: AtomicU64 = AtomicU64::new(0);
    let session_id = BUNDLED_SESSION
        .fetch_add(1, Ordering::Relaxed)
        .saturating_add(1);
    let mut bundle_seq = 0_u64;
    let mut pending_audio = Vec::<AudioPacket>::new();
    let mut pending_video = None::<VideoPacket>;
    let mut pending_video_deadline = None::<Instant>;
    let mut next_audio_flush = Instant::now() + BUNDLED_AUDIO_FLUSH_INTERVAL;

    loop {
        if stop.load(Ordering::Relaxed) {
            break;
        }
        let now = Instant::now();
        let timeout = if video_required {
            pending_video_deadline
                .unwrap_or(now + BUNDLED_AUDIO_FLUSH_INTERVAL)
                .saturating_duration_since(now)
        } else {
            next_audio_flush.saturating_duration_since(now)
        };
        match event_rx.recv_timeout(timeout) {
            Ok(BundledCaptureEvent::Audio(packet)) => {
                pending_audio.push(packet);
                if video_required {
                    let dropped = retain_newest_pending_audio(&mut pending_audio);
                    if dropped > 0 {
                        microphone_telemetry.record_stale_drop(dropped as u64);
                        log_uplink_drop(
                            &drop_log,
                            UplinkDropReason::Stale,
                            dropped as u64,
                            pending_audio.len(),
                            0.0,
                        );
                    }
                } else if pending_audio.len() >= BUNDLED_AUDIO_MAX_PENDING {
                    emit_bundled_media(
                        session_id,
                        &mut bundle_seq,
                        None,
                        std::mem::take(&mut pending_audio),
                        &queue,
                        &camera_telemetry,
                        &microphone_telemetry,
                        &drop_log,
                    );
                    next_audio_flush = Instant::now() + BUNDLED_AUDIO_FLUSH_INTERVAL;
                }
            }
            Ok(BundledCaptureEvent::Video(packet)) => {
                if let Some(video) = pending_video.take() {
                    emit_bundled_media(
                        session_id,
                        &mut bundle_seq,
                        Some(video),
                        std::mem::take(&mut pending_audio),
                        &queue,
                        &camera_telemetry,
                        &microphone_telemetry,
                        &drop_log,
                    );
                }
                pending_video = Some(packet);
                pending_video_deadline = Some(Instant::now() + BUNDLED_VIDEO_AUDIO_GRACE);
                if !video_required {
                    emit_bundled_media(
                        session_id,
                        &mut bundle_seq,
                        pending_video.take(),
                        std::mem::take(&mut pending_audio),
                        &queue,
                        &camera_telemetry,
                        &microphone_telemetry,
                        &drop_log,
                    );
                    pending_video_deadline = None;
                    next_audio_flush = Instant::now() + BUNDLED_AUDIO_FLUSH_INTERVAL;
                }
            }
            Ok(BundledCaptureEvent::Restart) => {
                if pending_video.is_some() || (!video_required && !pending_audio.is_empty()) {
                    emit_bundled_media(
                        session_id,
                        &mut bundle_seq,
                        pending_video.take(),
                        std::mem::take(&mut pending_audio),
                        &queue,
                        &camera_telemetry,
                        &microphone_telemetry,
                        &drop_log,
                    );
                }
                stop.store(true, Ordering::Relaxed);
                break;
            }
            Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
                if video_required {
                    if pending_video_deadline.is_some_and(|deadline| Instant::now() >= deadline) {
                        emit_bundled_media(
                            session_id,
                            &mut bundle_seq,
                            pending_video.take(),
                            std::mem::take(&mut pending_audio),
                            &queue,
                            &camera_telemetry,
                            &microphone_telemetry,
                            &drop_log,
                        );
                        pending_video_deadline = None;
                    } else {
                        let dropped = retain_newest_pending_audio(&mut pending_audio);
                        if dropped > 0 {
                            microphone_telemetry.record_stale_drop(dropped as u64);
                        }
                    }
                } else if !pending_audio.is_empty() {
                    emit_bundled_media(
                        session_id,
                        &mut bundle_seq,
                        None,
                        std::mem::take(&mut pending_audio),
                        &queue,
                        &camera_telemetry,
                        &microphone_telemetry,
                        &drop_log,
                    );
                }
                next_audio_flush = Instant::now() + BUNDLED_AUDIO_FLUSH_INTERVAL;
            }
            Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => break,
        }
    }
    if pending_video.is_some() || (!video_required && !pending_audio.is_empty()) {
        emit_bundled_media(
            session_id,
            &mut bundle_seq,
            pending_video.take(),
            std::mem::take(&mut pending_audio),
            &queue,
            &camera_telemetry,
            &microphone_telemetry,
            &drop_log,
        );
    }
    queue.close();
}

fn bundled_audio_video_max_span() -> Duration {
    std::env::var("LESAVKA_BUNDLED_AUDIO_VIDEO_MAX_SPAN_MS")
        .ok()
        .and_then(|value| value.trim().parse::<u64>().ok())
        .filter(|value| *value > 0)
        .map(Duration::from_millis)
        .unwrap_or_else(|| Duration::from_millis(DEFAULT_BUNDLED_AUDIO_VIDEO_MAX_SPAN_MS))
}
