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

#[test]
fn requested_audio_codec_defaults_to_pcm_and_parses_opus() {
    temp_env::with_var(AUDIO_CODEC_ENV, None::<&str>, || {
        temp_env::with_var(AUDIO_CODEC_LEGACY_ENV, None::<&str>, || {
            assert_eq!(
                requested_upstream_audio_codec_from_env(),
                UpstreamAudioCodec::PcmS16le
            );
        });
    });
    temp_env::with_var(AUDIO_CODEC_ENV, Some("pcm"), || {
        assert_eq!(
            requested_upstream_audio_codec_from_env(),
            UpstreamAudioCodec::PcmS16le
        );
    });
    temp_env::with_var(AUDIO_CODEC_ENV, Some("opus"), || {
        temp_env::with_var(AUDIO_CODEC_LEGACY_ENV, Some("pcm"), || {
            assert_eq!(
                requested_upstream_audio_codec_from_env(),
                UpstreamAudioCodec::Opus
            );
        });
    });
    temp_env::with_var(AUDIO_CODEC_ENV, None::<&str>, || {
        temp_env::with_var(AUDIO_CODEC_LEGACY_ENV, Some("compressed"), || {
            assert_eq!(
                requested_upstream_audio_codec_from_env(),
                UpstreamAudioCodec::Opus
            );
        });
    });
    temp_env::with_var(AUDIO_CODEC_ENV, Some("aac"), || {
        temp_env::with_var(AUDIO_CODEC_LEGACY_ENV, Some("opus"), || {
            assert_eq!(
                requested_upstream_audio_codec_from_env(),
                UpstreamAudioCodec::PcmS16le
            );
        });
    });
}

#[test]
fn opus_encoder_preserves_packet_timing_when_plugin_is_available() {
    let _ = gst::init();
    if gst::ElementFactory::find("opusenc").is_none() {
        return;
    }
    let mut packet = AudioPacket {
        pts: 123_000,
        data: vec![0; AudioTransportProfile::pcm_s16le().expected_payload_bytes() as usize],
        frame_duration_us: 20_000,
        ..AudioPacket::default()
    };
    let raw_len = packet.data.len();
    audio_transport::mark_packet_pcm_s16le(&mut packet);

    let mut encoder = OpusPacketEncoder::new().expect("opus encoder");
    let encoded = encoder
        .encode_packet(packet.clone())
        .expect("encode")
        .unwrap_or_else(|| packet.clone());
    assert_eq!(encoded.pts, 123_000);
    assert_eq!(encoded.sample_rate, 48_000);
    assert_eq!(encoded.channels, 2);
    if encoded.encoding == AudioEncoding::Opus as i32 {
        assert!(
            encoded.data.len() < raw_len,
            "Opus packet should be smaller than raw PCM"
        );
    }
}

#[test]
fn opus_encoder_decodes_non_silent_voice_like_pcm_when_plugin_is_available() {
    let _ = gst::init();
    if gst::ElementFactory::find("opusenc").is_none()
        || gst::ElementFactory::find("opusdec").is_none()
    {
        return;
    }
    let mut packet = AudioPacket {
        pts: 456_000,
        data: sine_pcm_packet(
            440.0,
            AudioTransportProfile::pcm_s16le().expected_payload_bytes() as usize,
        ),
        frame_duration_us: 20_000,
        ..AudioPacket::default()
    };
    audio_transport::mark_packet_pcm_s16le(&mut packet);

    let mut encoder = OpusPacketEncoder::new().expect("opus encoder");
    let mut encoded = None;
    for _ in 0..4 {
        encoded = encoder.encode_packet(packet.clone()).expect("encode");
        if encoded.is_some() {
            break;
        }
    }
    let Some(encoded) = encoded else {
        return;
    };

    let decoded = decode_opus_payload(&encoded.data).expect("decode opus payload");
    let peak = decoded
        .chunks_exact(2)
        .map(|sample| i16::from_le_bytes([sample[0], sample[1]]).unsigned_abs())
        .max()
        .unwrap_or(0);
    assert!(
        peak > 1_000,
        "decoded Opus should contain a real waveform, not silence/garbage"
    );
}

#[test]
fn packet_metadata_keeps_capture_timing_without_pcm_payload() {
    let packet = AudioPacket {
        pts: 987_000,
        encoding: AudioEncoding::PcmS16le as i32,
        sample_rate: 48_000,
        channels: 2,
        frame_duration_us: 20_000,
        data: vec![0x11; 3_840],
        ..AudioPacket::default()
    };

    let metadata = packet_metadata(&packet);

    assert_eq!(metadata.pts, 987_000);
    assert_eq!(metadata.encoding, AudioEncoding::PcmS16le 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_drops_oldest_when_encoder_lags() {
    let mut pending = VecDeque::new();

    for pts in 100..120 {
        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, 104);
    assert_eq!(pending.back().expect("newest retained").pts, 119);
}

fn sine_pcm_packet(freq_hz: f32, len: usize) -> Vec<u8> {
    let mut out = Vec::with_capacity(len);
    let frames = len / 4;
    for frame in 0..frames {
        let phase = (frame as f32 * freq_hz * 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
}

fn decode_opus_payload(payload: &[u8]) -> Option<Vec<u8>> {
    let desc = "\
        appsrc name=src is-live=true block=false format=time \
            caps=audio/x-opus,channel-mapping-family=0 ! \
        opusdec plc=false use-inband-fec=false min-latency=0 tolerance=0 ! \
        audioconvert ! audioresample ! \
        audio/x-raw,format=S16LE,layout=interleaved,channels=2,rate=48000 ! \
        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()?;
    let mut buffer = gst::Buffer::from_slice(payload.to_vec());
    if let Some(meta) = buffer.get_mut() {
        meta.set_pts(Some(gst::ClockTime::from_useconds(456_000)));
        meta.set_duration(Some(gst::ClockTime::from_useconds(20_000)));
    }
    appsrc.push_buffer(buffer).ok()?;
    let sample = appsink.try_pull_sample(gst::ClockTime::from_mseconds(100))?;
    let decoded = sample.buffer()?.map_readable().ok()?.to_vec();
    let _ = pipeline.set_state(gst::State::Null);
    Some(decoded)
}
