use crate::*;
use std::path::PathBuf;

/// Request to process a recorded chunk chain with sync offset
#[derive(Debug)]
pub struct PostRecordRequest {
    pub column: usize,
    pub row: usize,
    pub chunk_chain: Arc<AudioChunk>,
    pub sync_offset: usize,
    pub sample_rate: usize,
}

/// Response containing the consolidated buffer
#[derive(Debug)]
pub struct PostRecordResponse {
    pub column: usize,
    pub row: usize,
    pub consolidated_buffer: Box<[f32]>,
}

/// RT-side interface for post-record operations
#[derive(Debug)]
pub struct PostRecordController {
    request_sender: kanal::Sender<PostRecordRequest>,
    response_receiver: kanal::Receiver<PostRecordResponse>,
}

impl PostRecordController {
    /// Send a post-record processing request (RT-safe)
    pub fn send_request(
        &self,
        column: usize,
        row: usize,
        chunk_chain: Arc<AudioChunk>,
        sync_offset: usize,
        sample_rate: usize,
    ) -> Result<()> {
        let request = PostRecordRequest {
            column,
            row,
            chunk_chain,
            sync_offset,
            sample_rate,
        };

        match self.request_sender.try_send(request) {
            Ok(true) => Ok(()), // Successfully sent
            Ok(false) => Err(LooperError::ChunkAllocation(std::panic::Location::caller())), // Channel full
            Err(_) => Err(LooperError::ChunkAllocation(std::panic::Location::caller())), // Channel closed
        }
    }

    /// Try to receive a processing response (RT-safe)
    pub fn try_recv_response(&self) -> Option<PostRecordResponse> {
        match self.response_receiver.try_recv() {
            Ok(Some(response)) => Some(response),
            Ok(None) | Err(_) => None, // No message or channel closed = None
        }
    }
}

/// Handles post-record processing: consolidation with sync offset and saving
pub struct PostRecordHandler {
    request_receiver: kanal::AsyncReceiver<PostRecordRequest>,
    response_sender: kanal::AsyncSender<PostRecordResponse>,
    directory: PathBuf,
}

impl PostRecordHandler {
    /// Create new handler and return RT controller
    pub fn new(args: &Args) -> Result<(Self, PostRecordController)> {
        // Create channels for bidirectional communication
        let (request_sender, request_receiver) = kanal::bounded(16);
        let (response_sender, response_receiver) = kanal::bounded(16);

        let request_receiver = request_receiver.to_async();
        let response_sender = response_sender.to_async();

        let controller = PostRecordController {
            request_sender,
            response_receiver,
        };

        let handler = Self {
            request_receiver,
            response_sender,
            directory: args.config_file.clone(),
        };

        Ok((handler, controller))
    }

    /// Run the async processing task
    pub async fn run(&mut self) -> Result<()> {
        while let Ok(request) = self.request_receiver.recv().await {
            if let Err(e) = self.process_request(request).await {
                log::error!("Failed to process post-record request: {}", e);
            }
        }

        Ok(())
    }

    /// Process a single post-record request
    async fn process_request(&self, request: PostRecordRequest) -> Result<()> {
        log::debug!(
            "Processing post-record request for {} samples with sync_offset {}",
            request.chunk_chain.len(),
            request.sync_offset
        );

        // Step 1: Consolidate and reorder chunk chain based on sync offset
        let consolidated_buffer =
            self.consolidate_with_sync_offset(&request.chunk_chain, request.sync_offset as usize)?;

        log::debug!(
            "Consolidated and reordered {} samples",
            consolidated_buffer.len()
        );

        // Step 2: Send consolidated buffer back to RT thread immediately
        let response = PostRecordResponse {
            column: request.column,
            row: request.row,
            consolidated_buffer,
        };

        if let Err(_) = self.response_sender.send(response).await {
            log::warn!("Failed to send consolidated buffer to RT thread");
        }

        // Step 3: Save WAV file in background (I/O intensive)
        // Use original chunk chain for saving (not reordered)
        let consolidated_chunk = AudioChunk::consolidate(&request.chunk_chain);
        let file_path = self.get_file_path(request.row);

        match self
            .save_wav_file(&consolidated_chunk, request.sample_rate, &file_path)
            .await
        {
            Ok(_) => log::info!("Saved recording to {:?}", file_path),
            Err(e) => log::error!("Failed to save recording to {:?}: {}", file_path, e),
        }

        Ok(())
    }

    /// Consolidate chunk chain and reorder samples based on sync offset
    fn consolidate_with_sync_offset(
        &self,
        chunk_chain: &Arc<AudioChunk>,
        sync_offset: usize,
    ) -> Result<Box<[f32]>> {
        let total_length = chunk_chain.len();

        if total_length == 0 {
            return Ok(Vec::new().into_boxed_slice());
        }

        // Step 1: Extract all samples from the chunk chain into a temporary buffer
        let mut all_samples = vec![0.0; total_length];
        chunk_chain.copy_samples(&mut all_samples, 0)?;

        // Step 2: Reorder samples based on sync offset
        // The goal is to put the sample that represents "logical beat 1" at index 0
        if sync_offset == 0 {
            // No offset - return samples as-is
            return Ok(all_samples.into_boxed_slice());
        }

        let mut reordered = Vec::with_capacity(total_length);

        // For each output position, calculate which input position to take from
        for i in 0..total_length {
            let source_index = (i + sync_offset) % total_length;
            reordered.push(all_samples[source_index]);
        }

        Ok(reordered.into_boxed_slice())
    }

    /// Save consolidated chunk as WAV file
    async fn save_wav_file(
        &self,
        chunk: &AudioChunk,
        sample_rate: usize,
        file_path: &PathBuf,
    ) -> Result<()> {
        // Run WAV writing in blocking task to avoid blocking async runtime
        let chunk_samples: Vec<f32> = chunk.samples[..chunk.sample_count].to_vec();
        let file_path_clone = file_path.clone();

        tokio::task::spawn_blocking(move || {
            let spec = hound::WavSpec {
                channels: 1,
                sample_rate: sample_rate as _,
                bits_per_sample: 32,
                sample_format: hound::SampleFormat::Float,
            };

            let mut writer = hound::WavWriter::create(&file_path_clone, spec)
                .map_err(|_| LooperError::StateSave(std::panic::Location::caller()))?;

            // Write all samples from the chunk
            for sample in chunk_samples {
                writer
                    .write_sample(sample)
                    .map_err(|_| LooperError::StateSave(std::panic::Location::caller()))?;
            }

            writer
                .finalize()
                .map_err(|_| LooperError::StateSave(std::panic::Location::caller()))?;

            Ok::<(), LooperError>(())
        })
        .await
        .map_err(|_| LooperError::StateSave(std::panic::Location::caller()))?
    }

    fn get_file_path(&self, row: usize) -> PathBuf {
        self.directory.join(format!("row_{row}.wav"))
    }
}
