// Include-based coverage for microphone source-selection helpers.
//
// Scope: include `client/src/input/microphone.rs` and exercise Pulse source
// parsing + fallback behavior without requiring a live audio stack.
// Targets: `client/src/input/microphone.rs`.
// Why: source selection regressions should be caught with deterministic tests.

#[allow(warnings)]
mod live_capture_clock {
    include!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/tests/helpers/support/live_capture_clock_shim.rs"
    ));
}

#[allow(warnings)]
mod input {
    pub mod audio_codec {
        pub use lesavka_client::input::audio_codec::*;
    }
}

#[allow(warnings)]
mod microphone_include_contract {
    include!(env!("LESAVKA_CLIENT_MICROPHONE_SRC"));

    use serial_test::serial;
    use std::fs;
    use std::os::unix::fs::PermissionsExt;
    use std::path::Path;
    use temp_env::with_var;
    use tempfile::tempdir;

    const MICROPHONE_CAPTURE_SRC: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/client/src/input/microphone/capture_runtime.rs"
    ));

    fn write_executable(dir: &Path, name: &str, body: &str) {
        let path = dir.join(name);
        fs::write(&path, body).expect("write script");
        let mut perms = fs::metadata(&path).expect("metadata").permissions();
        perms.set_mode(0o755);
        fs::set_permissions(path, perms).expect("chmod");
    }

    fn with_fake_command(name: &str, script_body: &str, f: impl FnOnce()) {
        let dir = tempdir().expect("tempdir");
        write_executable(dir.path(), name, script_body);
        let prior = std::env::var("PATH").unwrap_or_default();
        let merged = if prior.is_empty() {
            dir.path().display().to_string()
        } else {
            format!("{}:{prior}", dir.path().display())
        };
        with_var("PATH", Some(merged), f);
    }

    fn with_fake_pactl(script_body: &str, f: impl FnOnce()) {
        with_fake_command("pactl", script_body, f);
    }

    fn with_fake_pw_dump(script_body: &str, f: impl FnOnce()) {
        with_fake_command("pw-dump", script_body, f);
    }

    #[test]
    #[serial]
    fn pulse_source_by_substr_matches_expected_device_name() {
        let script = r#"#!/usr/bin/env sh
if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then
  echo "0 alsa_input.pci.monitor module-alsa-card.c s16le 2ch 48000Hz RUNNING"
  echo "1 alsa_input.usb-Mic_1234-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz IDLE"
  exit 0
fi
exit 0
"#;
        with_fake_pactl(script, || {
            let src =
                MicrophoneCapture::pulse_source_by_substr("Mic_1234").expect("matching source");
            assert_eq!(src, "alsa_input.usb-Mic_1234-00.analog-stereo");
        });
    }

    #[test]
    #[serial]
    fn pulse_source_desc_formats_selected_non_monitor_source() {
        let script = r#"#!/usr/bin/env sh
if [ "$1" = "list" ] && [ "$2" = "short" ] && [ "$3" = "sources" ]; then
  echo "0 alsa_input.pci.monitor module-alsa-card.c s16le 2ch 48000Hz RUNNING"
  echo "1 alsa_input.usb-DeskMic_5678-00.analog-stereo module-alsa-card.c s16le 2ch 48000Hz IDLE"
  exit 0
fi
exit 0
"#;
        with_fake_pactl(script, || {
            let source =
                MicrophoneCapture::pulse_source_by_substr("DeskMic_5678").expect("matching source");
            let desc = MicrophoneCapture::pulse_source_desc(Some(&source));
            assert!(
                desc.contains("pulsesrc device=alsa_input.usb-DeskMic_5678-00.analog-stereo"),
                "expected escaped non-monitor source argument: {desc}"
            );
            assert!(desc.contains("buffer-time=40000"));
            assert!(desc.contains("latency-time=10000"));
        });
    }

    #[test]
    #[serial]
    fn pulse_source_by_substr_returns_none_when_pactl_is_unavailable() {
        with_var("PATH", Some("/definitely/missing/path"), || {
            assert!(MicrophoneCapture::pulse_source_by_substr("anything").is_none());
            assert_eq!(
                MicrophoneCapture::pulse_source_desc(None),
                "pulsesrc do-timestamp=true buffer-time=40000 latency-time=10000"
            );
        });
    }

    #[test]
    #[serial]
    fn pulse_source_desc_allows_low_latency_overrides_with_safe_clamp() {
        with_var("LESAVKA_MIC_PULSE_BUFFER_TIME_US", Some("20000"), || {
            with_var("LESAVKA_MIC_PULSE_LATENCY_TIME_US", Some("50000"), || {
                assert_eq!(
                    MicrophoneCapture::pulse_source_desc(None),
                    "pulsesrc do-timestamp=true buffer-time=20000 latency-time=20000"
                );
            });
        });
    }

    #[test]
    #[cfg(coverage)]
    fn microphone_capture_helpers_cover_edge_branches() {
        let packets = split_audio_sample(123_000, &[1, 2, 3, 4, 5, 6, 7, 8], 4);
        assert_eq!(packets.len(), 2);
        assert_eq!(packets[0].pts, 123_000);
        assert_eq!(
            MicrophoneCapture::pulse_source_desc(Some("alsa_input.usb Desk Mic")),
            "pulsesrc device='alsa_input.usb Desk Mic' do-timestamp=true buffer-time=40000 latency-time=10000"
        );
        assert!(duration_matches_pcm_payload(1, 0));
        assert!(!bool_env_enabled("LESAVKA_TEST_BOOL_ENV_NEVER_SET"));
        log_microphone_lag_clamped_source(
            crate::live_capture_clock::RebasedSourcePts {
                packet_pts_us: 1,
                capture_now_us: 10_000,
                source_pts_us: Some(1),
                source_base_us: Some(1),
                capture_base_us: Some(9_999),
                used_source_pts: true,
                lag_clamped: true,
                lead_clamped: false,
            },
            4,
        );
    }

    #[test]
    fn lag_clamped_microphone_timestamp_still_sends_audio() {
        assert!(
            MICROPHONE_CAPTURE_SRC
                .contains("log_microphone_lag_clamped_source(timing, map.len());")
        );
        assert!(MICROPHONE_CAPTURE_SRC.contains("let pts = timing.packet_pts_us;"));
        assert!(
            !MICROPHONE_CAPTURE_SRC.contains(
                "log_microphone_lag_clamped_source(timing, map.len());\n                    return None;"
            ),
            "lag-clamped mic packets should use the repaired live timestamp instead of starving UAC"
        );
    }

    #[test]
    fn pipewire_source_desc_formats_selected_and_default_sources() {
        let selected = MicrophoneCapture::pipewire_source_desc(Some("alsa input/Desk Mic"));
        assert!(
            selected.contains("pipewiresrc target-object='alsa input/Desk Mic'"),
            "expected shell-escaped PipeWire target: {selected}"
        );
        assert_eq!(
            MicrophoneCapture::pipewire_source_desc(Some("   ")),
            "pipewiresrc do-timestamp=true"
        );
        assert_eq!(
            MicrophoneCapture::pipewire_source_desc(None),
            "pipewiresrc do-timestamp=true"
        );
    }

    #[test]
    #[serial]
    fn pipewire_source_by_substr_prefers_audio_sources_and_skips_monitors() {
        let script = r#"#!/usr/bin/env sh
cat <<'JSON'
[
  {"info":{"props":{"media.class":"Audio/Sink","node.name":"alsa_output.usb-headphones"}}},
  {"info":{"props":{"media.class":"Audio/Source","node.name":"alsa_input.usb-DeskMic.monitor"}}},
  {"info":{"props":{"media.class":"Audio/Source","node.name":"alsa_input.usb-DeskMic"}}},
  {"info":{"props":{"media.class":"Audio/Source","node.nick":"Fallback Nick Mic"}}}
]
JSON
"#;
        with_fake_pw_dump(script, || {
            assert_eq!(
                MicrophoneCapture::pipewire_source_by_substr("DeskMic").as_deref(),
                Some("alsa_input.usb-DeskMic")
            );
            assert_eq!(
                MicrophoneCapture::pipewire_source_by_substr("Fallback Nick").as_deref(),
                Some("Fallback Nick Mic")
            );
            assert!(MicrophoneCapture::pipewire_source_by_substr("missing").is_none());
        });
    }

    #[test]
    fn default_source_desc_selects_a_valid_gstreamer_source_description() {
        gst::init().ok();
        let desc = MicrophoneCapture::default_source_desc();
        let pulse_default = desc.starts_with("pulsesrc do-timestamp=true buffer-time=")
            && desc.contains(" latency-time=")
            && !desc.contains(" device=");
        assert!(
            desc == "pipewiresrc do-timestamp=true" || pulse_default,
            "default source should stay a simple PipeWire/Pulse source: {desc}"
        );
    }

    #[test]
    #[serial]
    fn mic_gain_env_defaults_and_clamps_for_uplink_gain() {
        with_var("LESAVKA_MIC_GAIN", None::<&str>, || {
            assert_eq!(mic_gain_from_env(), 1.0);
        });
        with_var("LESAVKA_MIC_GAIN", Some("2.75"), || {
            assert_eq!(mic_gain_from_env(), 2.75);
        });
        with_var("LESAVKA_MIC_GAIN", Some("99"), || {
            assert_eq!(mic_gain_from_env(), 4.0);
        });
        with_var("LESAVKA_MIC_GAIN", Some("-1"), || {
            assert_eq!(mic_gain_from_env(), 0.0);
        });
        with_var("LESAVKA_MIC_GAIN", Some("bad"), || {
            assert_eq!(mic_gain_from_env(), 1.0);
        });
    }

    #[test]
    fn microphone_pipeline_desc_adds_level_tap_only_when_requested() {
        let with_tap = microphone_pipeline_desc("audiotestsrc is-live=true", 2.5, true, false);
        assert!(
            with_tap
                .contains("audiotestsrc is-live=true ! audioconvert ! audioresample ! audio/x-raw")
        );
        assert!(!with_tap.contains("audiotestsrc is-live=true ! audio/x-raw"));
        assert!(with_tap.contains("tee name=t"));
        assert!(with_tap.contains("appsink name=level_sink"));
        assert!(with_tap.contains("volume name=mic_input_gain volume=2.500"));

        let without_tap = microphone_pipeline_desc("audiotestsrc is-live=true", 1.0, false, false);
        assert!(!without_tap.contains("level_sink"));
        assert!(
            without_tap
                .contains("queue max-size-buffers=8 max-size-time=80000000 leaky=downstream")
        );
        assert!(without_tap.contains("appsink name=asink emit-signals=true max-buffers=8"));
    }

    #[test]
    fn microphone_pipeline_desc_can_insert_noise_suppression_stage() {
        let _ = gst::init();
        let raw = microphone_pipeline_desc("audiotestsrc is-live=true", 1.0, false, false);
        let suppressed = microphone_pipeline_desc("audiotestsrc is-live=true", 1.0, false, true);

        assert!(!raw.contains("webrtcdsp"));
        if gst::ElementFactory::find("webrtcdsp").is_some() {
            assert!(suppressed.contains("webrtcdsp"));
            assert!(suppressed.contains("noise-suppression=true"));
            assert!(suppressed.contains("noise-suppression-level=very-high"));
        } else {
            assert_eq!(raw, suppressed);
        }
    }
}
