#![allow(dead_code)]

// Unit coverage for client audio recovery knobs.
//
// Scope: include the client recovery parser helpers without starting GTK,
// GStreamer, or any relay process.
// Targets: `client/src/app/audio_recovery_config.rs` and
// `client/src/app/uplink_media/webcam_media_loop.rs`.
// Why: the real-world Google Meet fix depends on one safe startup UAC epoch
// retirement and bounded recovery retries, not on manual operator folklore.

use std::time::{Duration, Instant};

use lesavka_common::lesavka::{KeyboardReport, MouseReport};
use serial_test::serial;
use temp_env::{with_var, with_var_unset, with_vars};
use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
use tracing::warn;

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

const WEBCAM_MEDIA_LOOP_SRC: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/client/src/app/uplink_media/webcam_media_loop.rs"
));

#[test]
#[serial]
fn remote_audio_usb_recovery_is_opt_in_and_cooldown_bounded() {
    with_var_unset("LESAVKA_AUDIO_AUTO_RECOVER_USB", || {
        assert!(
            !audio_usb_auto_recover_enabled(),
            "USB-level audio recovery must stay opt-in because hard gadget recovery can disturb a live call"
        );
    });

    with_vars(
        [
            ("LESAVKA_AUDIO_AUTO_RECOVER_USB", Some("1")),
            ("LESAVKA_AUDIO_AUTO_RECOVER_AFTER", Some("0")),
            ("LESAVKA_AUDIO_AUTO_RECOVER_COOLDOWN_MS", Some("2500")),
        ],
        || {
            assert!(audio_usb_auto_recover_enabled());
            assert_eq!(
                audio_usb_recover_after(),
                3,
                "invalid thresholds should fall back to the safe default"
            );
            assert_eq!(audio_usb_recover_cooldown(), Duration::from_millis(2500));
        },
    );

    with_var(
        "LESAVKA_AUDIO_AUTO_RECOVER_COOLDOWN_MS",
        Some("not-a-number"),
        || {
            assert_eq!(
                audio_usb_recover_cooldown(),
                Duration::from_secs(60),
                "malformed cooldowns should not create a tight recovery loop"
            );
        },
    );
}

#[test]
fn remote_audio_error_classifier_targets_source_epoch_failures_only() {
    for message in [
        "remote speaker capture produced no audio samples after 3000 ms on hw:UAC2Gadget,0",
        "remote speaker capture stalled for 3000 ms on hw:UAC2Gadget,0",
        "remote speaker capture cadence is too low on hw:UAC2Gadget,0",
    ] {
        assert!(
            is_recoverable_remote_audio_error(message),
            "expected recoverable remote audio source error: {message}"
        );
    }

    for message in [
        "TLS handshake failed",
        "recover_uac returned permission denied",
        "video decoder stalled",
        "microphone capture device missing locally",
    ] {
        assert!(
            !is_recoverable_remote_audio_error(message),
            "unrelated failures must not trigger USB/audio recovery: {message}"
        );
    }
}

#[test]
fn startup_epoch_auto_heal_is_one_shot_uac_recovery_after_three_seconds() {
    for marker in [
        "const DEFAULT_UPSTREAM_AUTO_HEAL_AFTER_MS: u64 = 3_000;",
        "let mut startup_epoch_heal_delay = upstream_epoch_auto_heal_delay();",
        "startup_epoch_heal_delay.take()",
        "spawn_upstream_epoch_auto_heal(ep.clone(), heal_delay);",
        "client.recover_uac(Request::new(Empty {})).await",
        "automatic upstream A/V epoch heal requested",
    ] {
        assert!(
            WEBCAM_MEDIA_LOOP_SRC.contains(marker),
            "startup epoch auto-heal should preserve marker {marker}"
        );
    }

    for forbidden in ["reset_usb(Request::new(Empty {})).await", "recover_uvc("] {
        assert!(
            !WEBCAM_MEDIA_LOOP_SRC.contains(forbidden),
            "startup epoch auto-heal must not affect USB or video recovery path: {forbidden}"
        );
    }
}
