use super::*;
use lesavka_common::lesavka::AudioEncoding;

#[test]
fn opus_decoder_roundtrips_to_pcm_when_plugins_are_available() {
    let _ = gst::init();
    if gst::ElementFactory::find("opusenc").is_none()
        || gst::ElementFactory::find("opusdec").is_none()
    {
        return;
    }

    let Some(opus_payload) = encode_silent_opus_payload() else {
        return;
    };
    let packet = AudioPacket {
        pts: 42_000,
        encoding: AudioEncoding::Opus as i32,
        sample_rate: 48_000,
        channels: 2,
        frame_duration_us: 20_000,
        data: opus_payload,
        ..AudioPacket::default()
    };

    let mut decoder = OpusPacketDecoder::new().expect("opus decoder");
    let decoded = decoder
        .decode_packet(&packet)
        .expect("decode")
        .expect("decoded pcm");

    assert_eq!(decoded.pts, 42_000);
    assert_eq!(decoded.encoding, AudioEncoding::PcmS16le as i32);
    assert_eq!(decoded.sample_rate, 48_000);
    assert_eq!(decoded.channels, 2);
    assert!(
        decoded.data.len() >= 1_000,
        "decoded PCM should be far larger than one compressed Opus frame"
    );
    assert!(
        decoded.data.chunks_exact(2).any(|sample| {
            i16::from_le_bytes([sample[0], sample[1]]).unsigned_abs() > 250
        }),
        "decoded Opus payload should preserve non-silent waveform energy"
    );
}

#[test]
fn packet_metadata_keeps_timing_and_clears_payload() {
    let packet = AudioPacket {
        pts: 123_000,
        encoding: AudioEncoding::Opus as i32,
        sample_rate: 48_000,
        channels: 2,
        frame_duration_us: 20_000,
        data: vec![1, 2, 3, 4],
        ..AudioPacket::default()
    };

    let metadata = packet_metadata(&packet);

    assert_eq!(metadata.pts, 123_000);
    assert_eq!(metadata.encoding, AudioEncoding::Opus as i32);
    assert_eq!(metadata.sample_rate, 48_000);
    assert_eq!(metadata.channels, 2);
    assert_eq!(metadata.frame_duration_us, 20_000);
    assert!(metadata.data.is_empty());
}

#[test]
fn pending_packet_metadata_is_bounded_to_recent_frames() {
    let mut pending = VecDeque::new();

    for pts in 0..20 {
        push_pending_packet(
            &mut pending,
            AudioPacket {
                pts,
                ..AudioPacket::default()
            },
        );
    }

    assert_eq!(pending.len(), MAX_PENDING_OPUS_METADATA);
    assert_eq!(pending.front().expect("oldest retained").pts, 4);
    assert_eq!(pending.back().expect("newest retained").pts, 19);
}

fn encode_silent_opus_payload() -> Option<Vec<u8>> {
    let desc = "\
        appsrc name=src is-live=true block=false format=time \
            caps=audio/x-raw,format=S16LE,layout=interleaved,channels=2,rate=48000 ! \
        opusenc audio-type=restricted-lowdelay bitrate=96000 bitrate-type=cbr complexity=7 frame-size=20 perfect-timestamp=true hard-resync=true ! \
        appsink name=sink emit-signals=false sync=false max-buffers=8 drop=true";
    let pipeline: gst::Pipeline = gst::parse::launch(desc).ok()?.downcast().ok()?;
    let appsrc = pipeline
        .by_name("src")?
        .downcast::<gst_app::AppSrc>()
        .ok()?;
    let appsink = pipeline
        .by_name("sink")?
        .downcast::<gst_app::AppSink>()
        .ok()?;
    pipeline.set_state(gst::State::Playing).ok()?;

    for index in 0..4u64 {
        let mut buffer = gst::Buffer::from_slice(sine_pcm_packet(index, 3_840));
        if let Some(meta) = buffer.get_mut() {
            let pts = gst::ClockTime::from_useconds(index * 20_000);
            meta.set_pts(Some(pts));
            meta.set_dts(Some(pts));
            meta.set_duration(Some(gst::ClockTime::from_useconds(20_000)));
        }
        appsrc.push_buffer(buffer).ok()?;
        if let Some(sample) = appsink.try_pull_sample(gst::ClockTime::from_mseconds(50)) {
            let payload = sample.buffer()?.map_readable().ok()?.to_vec();
            let _ = pipeline.set_state(gst::State::Null);
            if !payload.is_empty() {
                return Some(payload);
            }
        }
    }
    let _ = pipeline.set_state(gst::State::Null);
    None
}

fn sine_pcm_packet(packet_index: u64, len: usize) -> Vec<u8> {
    let mut out = Vec::with_capacity(len);
    let frames = len / 4;
    for frame in 0..frames {
        let absolute = packet_index as usize * frames + frame;
        let phase = (absolute as f32 * 440.0 * std::f32::consts::TAU) / 48_000.0;
        let sample = (phase.sin() * 12_000.0) as i16;
        out.extend_from_slice(&sample.to_le_bytes());
        out.extend_from_slice(&sample.to_le_bytes());
    }
    out
}
