use std::{
    collections::BTreeMap,
    fs::File,
    io::{Cursor, Read, Seek, Write},
    path::Path,
    sync::{
        Arc,
        atomic::{AtomicUsize, Ordering},
        mpsc,
    },
    thread,
};

use crate::{
    config::{EncoderBuilder, EncoderConfig},
    error::{Error, Result},
    input::{WavData, read_wav},
    model::encode_frame,
    plan::{EncodePlan, summary_from_stream_info},
    progress::{NoProgress, ProgressSink},
    write::{EncodedFrame, FlacWriter},
};

const FRAME_CHUNK_SIZE: usize = 8;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EncodeSummary {
    pub frame_count: usize,
    pub total_samples: u64,
    pub block_size: u16,
    pub min_frame_size: u32,
    pub max_frame_size: u32,
    pub min_block_size: u16,
    pub max_block_size: u16,
    pub sample_rate: u32,
    pub channels: u8,
    pub bits_per_sample: u8,
}

/// Primary library façade for WAV-to-FLAC conversion.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Encoder {
    config: EncoderConfig,
}

impl Default for Encoder {
    fn default() -> Self {
        Self {
            config: EncoderConfig::default(),
        }
    }
}

impl Encoder {
    #[must_use]
    pub fn builder() -> EncoderBuilder {
        EncoderConfig::builder()
    }

    #[must_use]
    pub fn new(config: EncoderConfig) -> Self {
        Self { config }
    }

    #[must_use]
    pub fn config(&self) -> EncoderConfig {
        self.config
    }

    #[must_use]
    pub fn with_level(self, level: crate::level::Level) -> Self {
        Self::new(self.config.with_level(level))
    }

    #[must_use]
    pub fn with_threads(self, threads: usize) -> Self {
        Self::new(self.config.with_threads(threads))
    }

    #[must_use]
    pub fn with_block_size(self, block_size: u16) -> Self {
        Self::new(self.config.with_block_size(block_size))
    }

    pub fn encode<R, W>(&self, input: R, output: W) -> Result<EncodeSummary>
    where
        R: Read + Seek,
        W: Write + Seek,
    {
        let wav = read_wav(input)?;
        let mut progress = NoProgress;
        self.encode_wav_data(wav, output, &mut progress)
    }

    #[cfg(feature = "progress")]
    pub fn encode_with_progress<R, W, F>(
        &self,
        input: R,
        output: W,
        mut on_progress: F,
    ) -> Result<EncodeSummary>
    where
        R: Read + Seek,
        W: Write + Seek,
        F: FnMut(crate::progress::EncodeProgress) -> Result<()>,
    {
        let wav = read_wav(input)?;
        let mut progress = crate::progress::CallbackProgress::new(&mut on_progress);
        self.encode_wav_data(wav, output, &mut progress)
    }

    pub fn encode_file<P, Q>(&self, input_path: P, output_path: Q) -> Result<EncodeSummary>
    where
        P: AsRef<Path>,
        Q: AsRef<Path>,
    {
        self.encode(File::open(input_path)?, File::create(output_path)?)
    }

    #[cfg(feature = "progress")]
    pub fn encode_file_with_progress<P, Q, F>(
        &self,
        input_path: P,
        output_path: Q,
        on_progress: F,
    ) -> Result<EncodeSummary>
    where
        P: AsRef<Path>,
        Q: AsRef<Path>,
        F: FnMut(crate::progress::EncodeProgress) -> Result<()>,
    {
        self.encode_with_progress(
            File::open(input_path)?,
            File::create(output_path)?,
            on_progress,
        )
    }

    pub fn encode_bytes(&self, input: &[u8]) -> Result<Vec<u8>> {
        let mut output = Cursor::new(Vec::new());
        self.encode(Cursor::new(input), &mut output)?;
        Ok(output.into_inner())
    }

    fn encode_wav_data<W, P>(
        &self,
        wav: WavData,
        output: W,
        progress: &mut P,
    ) -> Result<EncodeSummary>
    where
        W: Write + Seek,
        P: ProgressSink,
    {
        let plan = EncodePlan::new(wav.spec, self.config)?;
        let mut writer = FlacWriter::new(output, plan.stream_info())?;

        if plan.total_frames == 0 {
            let (_, stream_info) = writer.finalize()?;
            return Ok(summary_from_stream_info(stream_info, 0));
        }

        self.encode_frames(plan, wav.samples, &mut writer, progress)?;

        let (_, stream_info) = writer.finalize()?;
        Ok(summary_from_stream_info(stream_info, plan.total_frames))
    }

    fn encode_frames<W, P>(
        &self,
        plan: EncodePlan,
        samples: Vec<i32>,
        writer: &mut FlacWriter<W>,
        progress: &mut P,
    ) -> Result<()>
    where
        W: Write + Seek,
        P: ProgressSink,
    {
        let worker_count = self.config.threads.max(1).min(plan.total_frames.max(1));
        let next_frame = Arc::new(AtomicUsize::new(0));
        let samples: Arc<[i32]> = Arc::from(samples);
        let channels = usize::from(plan.spec.channels);

        thread::scope(|scope| -> Result<()> {
            let (sender, receiver) = mpsc::channel();
            for _ in 0..worker_count {
                let sender = sender.clone();
                let next_frame = Arc::clone(&next_frame);
                let samples = Arc::clone(&samples);
                let channels_in_scope = channels;
                let plan = plan;

                scope.spawn(move || {
                    loop {
                        let chunk_start = next_frame.fetch_add(FRAME_CHUNK_SIZE, Ordering::Relaxed);
                        if chunk_start >= plan.total_frames {
                            break;
                        }
                        let chunk_end = (chunk_start + FRAME_CHUNK_SIZE).min(plan.total_frames);
                        let mut encoded_chunk = Vec::with_capacity(chunk_end - chunk_start);
                        let mut saw_error = false;

                        for frame_index in chunk_start..chunk_end {
                            let start_sample = frame_index as u64 * u64::from(plan.block_size);
                            let frame_samples = (plan.spec.total_samples - start_sample)
                                .min(u64::from(plan.block_size))
                                as usize;
                            let sample_start =
                                frame_index * usize::from(plan.block_size) * channels_in_scope;
                            let sample_end = sample_start + frame_samples * channels_in_scope;
                            let frame = encode_frame(
                                &samples[sample_start..sample_end],
                                plan.spec.channels,
                                plan.spec.bits_per_sample,
                                plan.spec.sample_rate,
                                frame_index as u64,
                                plan.profile,
                            );
                            saw_error |= frame.is_err();
                            encoded_chunk.push((frame_index, frame));
                            if saw_error {
                                break;
                            }
                        }

                        if sender.send(encoded_chunk).is_err() {
                            return;
                        }
                        if saw_error {
                            return;
                        }
                    }
                });
            }

            drop(sender);
            let mut next_expected = 0usize;
            let mut processed_samples = 0u64;
            let mut pending: BTreeMap<usize, EncodedFrame> = BTreeMap::new();
            while next_expected < plan.total_frames {
                let encoded_chunk = receiver.recv().map_err(|_| {
                    Error::Thread(
                        "frame worker channel closed before all frames were encoded".into(),
                    )
                })?;
                for (frame_index, frame) in encoded_chunk {
                    let frame = frame?;
                    if frame_index == next_expected {
                        processed_samples = write_encoded_frame(
                            writer,
                            &frame,
                            processed_samples,
                            plan.spec.total_samples,
                            next_expected + 1,
                            plan.total_frames,
                            progress,
                        )?;
                        next_expected += 1;
                        while let Some(frame) = pending.remove(&next_expected) {
                            processed_samples = write_encoded_frame(
                                writer,
                                &frame,
                                processed_samples,
                                plan.spec.total_samples,
                                next_expected + 1,
                                plan.total_frames,
                                progress,
                            )?;
                            next_expected += 1;
                        }
                    } else {
                        pending.insert(frame_index, frame);
                    }
                }
            }

            Ok(())
        })
    }
}

pub fn encode_file<P, Q>(input_path: P, output_path: Q) -> Result<EncodeSummary>
where
    P: AsRef<Path>,
    Q: AsRef<Path>,
{
    Encoder::default().encode_file(input_path, output_path)
}

pub fn encode_bytes(input: &[u8]) -> Result<Vec<u8>> {
    Encoder::default().encode_bytes(input)
}

fn write_encoded_frame<W, P>(
    writer: &mut FlacWriter<W>,
    frame: &EncodedFrame,
    processed_samples: u64,
    total_samples: u64,
    completed_frames: usize,
    total_frames: usize,
    progress: &mut P,
) -> Result<u64>
where
    W: Write + Seek,
    P: ProgressSink,
{
    writer.write_frame(&frame.bytes)?;
    let processed_samples = processed_samples + u64::from(frame.sample_count);
    progress.on_frame(
        processed_samples,
        total_samples,
        completed_frames,
        total_frames,
    )?;
    Ok(processed_samples)
}
