use std::{
    io,
    pin::Pin,
    task::{Context, Poll},
};

use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf};

use crate::{
    decode_aiff_stream, decode_flac_stream, decode_mp3_stream, decode_ogg_opus_stream,
    decode_ogg_vorbis_stream, decode_wav_stream, pcm::StreamInfo, prefixed_reader::PrefixedReader,
    AiffDecodedStream, AiffError, FlacDecodedStream, FlacError, Mp3DecodedStream, Mp3Error,
    OggDecodedStream, OggError, OggOpusDecodedStream, OggOpusError, WavDecodedStream, WavError,
};

const MAX_SNIFF_BYTES: usize = 64 * 1024;
const READ_CHUNK: usize = 4096;

#[derive(thiserror::Error, Debug)]
pub enum DecodeAudioError {
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
    #[error("unknown or unsupported audio format")]
    UnknownFormat,
    #[error("FLAC decode error: {0}")]
    Flac(FlacError),
    #[error("MP3 decode error: {0}")]
    Mp3(Mp3Error),
    #[error("Ogg/Vorbis decode error: {0}")]
    Vorbis(OggError),
    #[error("Ogg/Opus decode error: {0}")]
    Opus(OggOpusError),
    #[error("WAV decode error: {0}")]
    Wav(WavError),
    #[error("AIFF decode error: {0}")]
    Aiff(AiffError),
}

pub async fn decode_audio_stream<R>(reader: R) -> Result<DecodedAudioStream, DecodeAudioError>
where
    R: AsyncRead + Unpin + Send + 'static,
{
    let mut reader = reader;
    let mut initial = Vec::new();
    let mut tmp = vec![0u8; READ_CHUNK];
    let mut detected = detect_format(&initial);

    while detected.is_none() && initial.len() < MAX_SNIFF_BYTES {
        let read = reader.read(&mut tmp).await?;
        if read == 0 {
            break;
        }
        initial.extend_from_slice(&tmp[..read]);
        detected = detect_format(&initial);
    }

    let format = detected.ok_or(DecodeAudioError::UnknownFormat)?;
    let prefixed = PrefixedReader::new(initial, reader);

    let stream = match format {
        DetectedFormat::Flac => {
            let stream = decode_flac_stream(prefixed)
                .await
                .map_err(DecodeAudioError::Flac)?;
            DecodedAudioStream::Flac(stream)
        }
        DetectedFormat::Mp3 => {
            let stream = decode_mp3_stream(prefixed)
                .await
                .map_err(DecodeAudioError::Mp3)?;
            DecodedAudioStream::Mp3(stream)
        }
        DetectedFormat::OggVorbis => {
            let stream = decode_ogg_vorbis_stream(prefixed)
                .await
                .map_err(DecodeAudioError::Vorbis)?;
            DecodedAudioStream::OggVorbis(stream)
        }
        DetectedFormat::OggOpus => {
            let stream = decode_ogg_opus_stream(prefixed)
                .await
                .map_err(DecodeAudioError::Opus)?;
            DecodedAudioStream::OggOpus(stream)
        }
        DetectedFormat::Wav => {
            let stream = decode_wav_stream(prefixed)
                .await
                .map_err(DecodeAudioError::Wav)?;
            DecodedAudioStream::Wav(stream)
        }
        DetectedFormat::Aiff => {
            let stream = decode_aiff_stream(prefixed)
                .await
                .map_err(DecodeAudioError::Aiff)?;
            DecodedAudioStream::Aiff(stream)
        }
    };

    Ok(stream)
}

pub enum DecodedAudioStream {
    Flac(FlacDecodedStream),
    Mp3(Mp3DecodedStream),
    OggVorbis(OggDecodedStream),
    OggOpus(OggOpusDecodedStream),
    Wav(WavDecodedStream),
    Aiff(AiffDecodedStream),
}

impl DecodedAudioStream {
    pub fn info(&self) -> &StreamInfo {
        match self {
            DecodedAudioStream::Flac(inner) => inner.info(),
            DecodedAudioStream::Mp3(inner) => inner.info(),
            DecodedAudioStream::OggVorbis(inner) => inner.info(),
            DecodedAudioStream::OggOpus(inner) => inner.info(),
            DecodedAudioStream::Wav(inner) => inner.info(),
            DecodedAudioStream::Aiff(inner) => inner.info(),
        }
    }

    pub async fn wait(self) -> Result<(), DecodeAudioError> {
        match self {
            DecodedAudioStream::Flac(inner) => inner.wait().await.map_err(DecodeAudioError::Flac),
            DecodedAudioStream::Mp3(inner) => inner.wait().await.map_err(DecodeAudioError::Mp3),
            DecodedAudioStream::OggVorbis(inner) => {
                inner.wait().await.map_err(DecodeAudioError::Vorbis)
            }
            DecodedAudioStream::OggOpus(inner) => {
                inner.wait().await.map_err(DecodeAudioError::Opus)
            }
            DecodedAudioStream::Wav(inner) => inner.wait().await.map_err(DecodeAudioError::Wav),
            DecodedAudioStream::Aiff(inner) => inner.wait().await.map_err(DecodeAudioError::Aiff),
        }
    }

    pub fn into_reader(self) -> (StreamInfo, DecodedReader) {
        match self {
            DecodedAudioStream::Flac(inner) => {
                let (info, reader) = inner.into_parts();
                (info, DecodedReader::Flac(reader))
            }
            DecodedAudioStream::Mp3(inner) => {
                let (info, reader) = inner.into_parts();
                (info, DecodedReader::Mp3(reader))
            }
            DecodedAudioStream::OggVorbis(inner) => {
                let (info, reader) = inner.into_parts();
                (info, DecodedReader::OggVorbis(reader))
            }
            DecodedAudioStream::OggOpus(inner) => {
                let (info, reader) = inner.into_parts();
                (info, DecodedReader::OggOpus(reader))
            }
            DecodedAudioStream::Wav(inner) => {
                let (info, reader) = inner.into_parts();
                (info, DecodedReader::Wav(reader))
            }
            DecodedAudioStream::Aiff(inner) => {
                let (info, reader) = inner.into_parts();
                (info, DecodedReader::Aiff(reader))
            }
        }
    }
}

impl AsyncRead for DecodedAudioStream {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut ReadBuf<'_>,
    ) -> Poll<io::Result<()>> {
        match self.get_mut() {
            DecodedAudioStream::Flac(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedAudioStream::Mp3(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedAudioStream::OggVorbis(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedAudioStream::OggOpus(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedAudioStream::Wav(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedAudioStream::Aiff(inner) => Pin::new(inner).poll_read(cx, buf),
        }
    }
}

pub enum DecodedReader {
    Flac(crate::stream::ManagedAsyncReader<FlacError>),
    Mp3(crate::stream::ManagedAsyncReader<Mp3Error>),
    OggVorbis(crate::stream::ManagedAsyncReader<OggError>),
    OggOpus(crate::stream::ManagedAsyncReader<OggOpusError>),
    Wav(crate::stream::ManagedAsyncReader<WavError>),
    Aiff(crate::stream::ManagedAsyncReader<AiffError>),
}

impl DecodedReader {
    pub async fn wait(self) -> Result<(), DecodeAudioError> {
        match self {
            DecodedReader::Flac(inner) => inner.wait().await.map_err(DecodeAudioError::Flac),
            DecodedReader::Mp3(inner) => inner.wait().await.map_err(DecodeAudioError::Mp3),
            DecodedReader::OggVorbis(inner) => inner.wait().await.map_err(DecodeAudioError::Vorbis),
            DecodedReader::OggOpus(inner) => inner.wait().await.map_err(DecodeAudioError::Opus),
            DecodedReader::Wav(inner) => inner.wait().await.map_err(DecodeAudioError::Wav),
            DecodedReader::Aiff(inner) => inner.wait().await.map_err(DecodeAudioError::Aiff),
        }
    }
}

impl AsyncRead for DecodedReader {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut ReadBuf<'_>,
    ) -> Poll<io::Result<()>> {
        match self.get_mut() {
            DecodedReader::Flac(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedReader::Mp3(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedReader::OggVorbis(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedReader::OggOpus(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedReader::Wav(inner) => Pin::new(inner).poll_read(cx, buf),
            DecodedReader::Aiff(inner) => Pin::new(inner).poll_read(cx, buf),
        }
    }
}

fn detect_format(bytes: &[u8]) -> Option<DetectedFormat> {
    if bytes.len() >= 4 && &bytes[..4] == b"fLaC" {
        return Some(DetectedFormat::Flac);
    }
    if bytes.len() >= 12 && &bytes[..4] == b"RIFF" && &bytes[8..12] == b"WAVE" {
        return Some(DetectedFormat::Wav);
    }
    if bytes.len() >= 12
        && &bytes[..4] == b"FORM"
        && (&bytes[8..12] == b"AIFF" || &bytes[8..12] == b"AIFC")
    {
        return Some(DetectedFormat::Aiff);
    }
    if let Some(fmt) = detect_ogg(bytes) {
        return Some(fmt);
    }
    if is_mp3(bytes) {
        return Some(DetectedFormat::Mp3);
    }
    None
}

fn detect_ogg(bytes: &[u8]) -> Option<DetectedFormat> {
    if bytes.len() < 27 || &bytes[..4] != b"OggS" {
        return None;
    }
    let segment_count = bytes[26] as usize;
    let header_len = 27 + segment_count;
    if bytes.len() < header_len {
        return None;
    }
    let mut packet_len = 0usize;
    for lace in &bytes[27..27 + segment_count] {
        packet_len += *lace as usize;
        if *lace < 255 {
            break;
        }
    }
    if bytes.len() < header_len + packet_len {
        return None;
    }
    let packet = &bytes[header_len..header_len + packet_len];
    if packet.starts_with(b"OpusHead") {
        Some(DetectedFormat::OggOpus)
    } else if packet.starts_with(b"\x01vorbis") {
        Some(DetectedFormat::OggVorbis)
    } else {
        None
    }
}

fn is_mp3(bytes: &[u8]) -> bool {
    if bytes.len() >= 3 && &bytes[..3] == b"ID3" {
        return true;
    }
    if bytes.len() >= 2 && bytes[0] == 0xFF && (bytes[1] & 0xE0) == 0xE0 {
        return true;
    }
    false
}

enum DetectedFormat {
    Flac,
    Mp3,
    OggVorbis,
    OggOpus,
    Wav,
    Aiff,
}
