// crates/rutster-trunk/tests/reflex_on_trunk.rs
//
// T7 — Reflex-on-trunk-leg verification test (slice-5 spec §7).
//
// Proves that slice-4's `Reflex<TapAudioPipe>` + `LocalVadReflex` decorate the
// trunk leg's `TapAudioPipe` identically to a WebRTC leg. A PSTN caller's loud
// PCM triggers the same local-VAD state machine and the same `barge-in` kill
// that a WebRTC caller's audio does.
//
// This is a *unit* integration test: it constructs the wrapped pipe stack
// directly, without the binary's MediaThread or a real WSS brain. The heavier
// end-to-end WSS sim lives in `sim_integ.rs` (T8).

use std::sync::atomic::Ordering;

use rutster_media::{
    AudioSink, AudioSource, LocalVadReflex, PcmFrame, Reflex, ReflexMetrics, VAD_DEBOUNCE_FRAMES,
};
use rutster_tap::TapAudioPipe;
use tokio::sync::mpsc;

/// Build a loud 24 kHz PCM frame whose RMS energy is well above
/// `VAD_RMS_THRESHOLD`. The mock caller uses a constant amplitude of 1000,
/// the same value slice-4's reflex unit tests use.
fn loud_frame() -> PcmFrame {
    let mut frame = PcmFrame::zeroed();
    for s in frame.samples.iter_mut() {
        *s = 1000;
    }
    frame
}

#[tokio::test]
async fn local_vad_on_trunk_pipe_kills_playout_and_resumes_on_fresh_brain_audio() {
    // Given: a TapAudioPipe + the same Reflex<LocalVadReflex> composition the
    // FOB builds for every leg (WebRTC or trunk).
    let (tx_pcm_in, rx_pcm_in) = mpsc::channel(32);
    let (tx_audio_out, rx_audio_out) = mpsc::channel(32);
    let _rx_pcm_in = rx_pcm_in; // brain side; not used in this unit test
    let tap_metrics = rutster_tap::TapMetrics::new();
    let tap_pipe = TapAudioPipe::new(tx_pcm_in, rx_audio_out, tap_metrics);

    let (advisory_tx, advisory_rx) = mpsc::channel(16);
    let metrics = ReflexMetrics::new();
    let reflex = Reflex::new(tap_pipe, advisory_rx, metrics.clone());
    let mut wrapped_pipe = LocalVadReflex::new(reflex, advisory_tx);

    // Pre-load one brain audio_out frame so the playout ring is non-empty.
    tx_audio_out.send(PcmFrame::zeroed()).await.unwrap();

    // When: the PSTN caller speaks for `VAD_DEBOUNCE_FRAMES` consecutive ticks.
    let loud = loud_frame();
    for _ in 0..VAD_DEBOUNCE_FRAMES {
        wrapped_pipe.on_pcm_frame(loud.clone());
    }

    // Then: the third `next_pcm_frame` drains the local-VAD advisory, mutes the
    // pipe, flushes the ring, and returns `None` (playout is killed).
    let killed = wrapped_pipe.next_pcm_frame();
    assert!(
        killed.is_none(),
        "local VAD must barge-in and suppress playout on the trunk leg"
    );
    assert_eq!(
        metrics.barge_in_count.load(Ordering::Relaxed),
        1,
        "barge-in must fire exactly once for the first loud utterance"
    );

    // And when: a fresh brain reply arrives after the barge.
    tx_audio_out.send(PcmFrame::zeroed()).await.unwrap();

    // Then: the Reflex un-mutes and returns the fresh frame.
    let resumed = wrapped_pipe.next_pcm_frame();
    assert!(
        resumed.is_some(),
        "first fresh audio_out post-barge must resume trunk-leg playout"
    );
}
