//! Audio capture and processing using cpal.
//!
//! This module handles audio recording in a thread-safe way by using
//! a shared buffer that can be accessed from the Tauri state.
//!
//! On Windows, it captures both microphone input AND system audio (loopback)
//! to record both sides of a meeting conversation.

use parking_lot::Mutex;
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use thiserror::Error;
use tracing::{error, info, warn};

/// Sample rate expected by Whisper (16kHz mono)
pub const WHISPER_SAMPLE_RATE: u32 = 16000;

#[derive(Error, Debug)]
pub enum AudioError {
    #[error("No input device available")]
    NoInputDevice,
    #[error("Failed to get default input config: {0}")]
    ConfigError(String),
    #[error("Failed to build input stream: {0}")]
    StreamError(String),
    #[error("Stream playback error: {0}")]
    PlaybackError(String),
    #[error("Recording thread error: {0}")]
    ThreadError(String),
}

/// Shared audio state that can be safely stored in Tauri state.
/// The actual audio capture happens in a separate thread.
pub struct AudioCapture {
    buffer: Arc<Mutex<Vec<f32>>>,
    is_recording: Arc<Mutex<bool>>,
    should_stop: Arc<Mutex<bool>>,
    recording_thread: Option<JoinHandle<()>>,
    #[cfg(target_os = "windows")]
    loopback_capture: Option<super::wasapi_loopback::WasapiLoopback>,
}

// Implement Send + Sync manually since our struct only contains thread-safe types
unsafe impl Send for AudioCapture {}
unsafe impl Sync for AudioCapture {}

impl AudioCapture {
    /// Create a new audio capture instance.
    pub fn new() -> Result<Self, AudioError> {
        Ok(Self {
            buffer: Arc::new(Mutex::new(Vec::new())),
            is_recording: Arc::new(Mutex::new(false)),
            should_stop: Arc::new(Mutex::new(false)),
            recording_thread: None,
            #[cfg(target_os = "windows")]
            loopback_capture: None,
        })
    }

    /// Start recording audio in a background thread.
    /// Captures both microphone input and system audio (loopback) on Windows.
    pub fn start_recording(&mut self) -> Result<(), AudioError> {
        if *self.is_recording.lock() {
            warn!("Already recording");
            return Ok(());
        }

        // Clear the buffer and reset stop flag
        self.buffer.lock().clear();
        *self.should_stop.lock() = false;
        *self.is_recording.lock() = true;

        // Start WASAPI loopback capture for system audio (Windows only)
        #[cfg(target_os = "windows")]
        {
            let mut loopback = super::wasapi_loopback::WasapiLoopback::new();
            if let Err(e) = loopback.start_capture(Arc::clone(&self.buffer)) {
                warn!("Failed to start WASAPI loopback: {}", e);
            } else {
                info!("WASAPI loopback capture initialized");
            }
            self.loopback_capture = Some(loopback);
        }

        let buffer = Arc::clone(&self.buffer);
        let is_recording = Arc::clone(&self.is_recording);
        let should_stop = Arc::clone(&self.should_stop);

        // Spawn a thread to handle microphone capture via cpal
        let handle = thread::spawn(move || {
            use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};

            let host = cpal::default_host();

            // Get microphone (input device)
            let input_device = host.default_input_device();

            if input_device.is_none() {
                warn!("No microphone available - only system audio will be captured");
            }

            let mut streams: Vec<cpal::Stream> = Vec::new();

            // Set up microphone capture
            if let Some(ref device) = input_device {
                info!("Microphone device: {}", device.name().unwrap_or_default());

                if let Ok(config) = device.default_input_config() {
                    let sample_rate = config.sample_rate().0;
                    let channels = config.channels();
                    let sample_format = config.sample_format();
                    info!("Microphone config: {} Hz, {} ch, {:?}", sample_rate, channels, sample_format);

                    let buffer_clone = Arc::clone(&buffer);
                    let stream_config: cpal::StreamConfig = config.into();

                    let stream = build_input_stream(
                        device,
                        &stream_config,
                        sample_format,
                        sample_rate,
                        channels,
                        buffer_clone,
                        "mic",
                    );

                    if let Some(s) = stream {
                        if s.play().is_ok() {
                            info!("Microphone capture started");
                            streams.push(s);
                        }
                    }
                }
            }

            // Note: Even if mic fails, WASAPI loopback may still be capturing system audio
            if streams.is_empty() {
                warn!("No microphone stream started - relying on WASAPI loopback for system audio");
            } else {
                info!("Audio recording started with {} microphone stream(s)", streams.len());
            }

            // Keep the streams alive until stop is requested
            while !*should_stop.lock() {
                thread::sleep(std::time::Duration::from_millis(100));
            }

            // Streams are automatically stopped when dropped
            drop(streams);
            *is_recording.lock() = false;
            info!("Audio recording stopped");
        });

        self.recording_thread = Some(handle);
        Ok(())
    }

    /// Stop recording and return the captured audio samples.
    pub fn stop_recording(&mut self) -> Vec<f32> {
        // Signal the thread to stop
        *self.should_stop.lock() = true;

        // Stop WASAPI loopback capture
        #[cfg(target_os = "windows")]
        if let Some(ref mut loopback) = self.loopback_capture {
            loopback.stop_capture();
        }

        // Wait for the microphone thread to finish
        if let Some(handle) = self.recording_thread.take() {
            let _ = handle.join();
        }

        let samples = std::mem::take(&mut *self.buffer.lock());
        info!("Captured {} samples ({:.1}s)", samples.len(), samples.len() as f32 / WHISPER_SAMPLE_RATE as f32);
        samples
    }

    /// Get a copy of the current audio buffer without stopping recording.
    pub fn get_current_buffer(&self) -> Vec<f32> {
        self.buffer.lock().clone()
    }

    /// Clear the audio buffer.
    pub fn clear_buffer(&self) {
        self.buffer.lock().clear();
    }

    /// Check if currently recording.
    pub fn is_recording(&self) -> bool {
        *self.is_recording.lock()
    }

    /// Get the duration of captured audio in seconds.
    pub fn get_duration_seconds(&self) -> f32 {
        let sample_count = self.buffer.lock().len();
        sample_count as f32 / WHISPER_SAMPLE_RATE as f32
    }

    /// Extract audio chunk for real-time processing.
    /// This method retrieves audio starting from the given offset and returns only
    /// the new samples, keeping a small overlap for context.
    pub fn extract_chunk(&self, from_sample: usize) -> (Vec<f32>, usize) {
        let mut buffer = self.buffer.lock();
        let current_len = buffer.len();

        // If we don't have enough new samples, return empty
        if from_sample >= current_len {
            return (Vec::new(), current_len);
        }

        // Extract new samples
        let chunk: Vec<f32> = buffer[from_sample..].to_vec();

        // Keep only the last 30 seconds of audio (at 16kHz) to prevent memory exhaustion
        // This provides enough overlap for context while limiting memory usage
        const MAX_BUFFER_SECONDS: usize = 30;
        const MAX_BUFFER_SAMPLES: usize = WHISPER_SAMPLE_RATE as usize * MAX_BUFFER_SECONDS;

        if buffer.len() > MAX_BUFFER_SAMPLES {
            // Calculate how many samples to remove from the beginning
            let samples_to_remove = buffer.len() - MAX_BUFFER_SAMPLES;
            buffer.drain(..samples_to_remove);

            // Return the chunk and adjust the offset
            return (chunk, current_len - samples_to_remove);
        }

        // Return the chunk and the new offset
        (chunk, current_len)
    }

    /// Get all audio samples from the given offset without modifying the buffer.
    /// This is used when stopping recording to get any remaining audio.
    pub fn get_remaining_audio(&self, from_sample: usize) -> Vec<f32> {
        let buffer = self.buffer.lock();

        if from_sample >= buffer.len() {
            return Vec::new();
        }

        buffer[from_sample..].to_vec()
    }
}

impl Default for AudioCapture {
    fn default() -> Self {
        Self::new().expect("Failed to create audio capture")
    }
}

impl Drop for AudioCapture {
    fn drop(&mut self) {
        // Ensure recording is stopped when dropped
        if *self.is_recording.lock() {
            *self.should_stop.lock() = true;
            if let Some(handle) = self.recording_thread.take() {
                let _ = handle.join();
            }
        }
    }
}

/// Build an input stream for the given device with automatic format handling.
fn build_input_stream(
    device: &cpal::Device,
    config: &cpal::StreamConfig,
    sample_format: cpal::SampleFormat,
    sample_rate: u32,
    channels: u16,
    buffer: Arc<Mutex<Vec<f32>>>,
    source_name: &'static str,
) -> Option<cpal::Stream> {
    use cpal::traits::DeviceTrait;

    let err_fn = move |err| error!("Audio stream error ({}): {}", source_name, err);

    // Create a processing closure that handles mono conversion and resampling
    let make_processor = move || {
        let buffer = Arc::clone(&buffer);
        move |samples: Vec<f32>| {
            // Convert to mono if stereo
            let mono_samples: Vec<f32> = if channels > 1 {
                samples
                    .chunks(channels as usize)
                    .map(|chunk| chunk.iter().sum::<f32>() / channels as f32)
                    .collect()
            } else {
                samples
            };

            // Resample to 16kHz if needed
            let resampled = if sample_rate != WHISPER_SAMPLE_RATE {
                resample(&mono_samples, sample_rate, WHISPER_SAMPLE_RATE)
            } else {
                mono_samples
            };

            buffer.lock().extend_from_slice(&resampled);
        }
    };

    let stream = match sample_format {
        cpal::SampleFormat::F32 => {
            let process = make_processor();
            device.build_input_stream(
                config,
                move |data: &[f32], _: &cpal::InputCallbackInfo| {
                    process(data.to_vec());
                },
                err_fn,
                None,
            )
        }
        cpal::SampleFormat::I16 => {
            let process = make_processor();
            device.build_input_stream(
                config,
                move |data: &[i16], _: &cpal::InputCallbackInfo| {
                    let samples: Vec<f32> = data.iter().map(|&s| s as f32 / 32768.0).collect();
                    process(samples);
                },
                err_fn,
                None,
            )
        }
        cpal::SampleFormat::I32 => {
            let process = make_processor();
            device.build_input_stream(
                config,
                move |data: &[i32], _: &cpal::InputCallbackInfo| {
                    let samples: Vec<f32> = data.iter().map(|&s| s as f32 / 2147483648.0).collect();
                    process(samples);
                },
                err_fn,
                None,
            )
        }
        format => {
            error!("Unsupported sample format for {}: {:?}", source_name, format);
            return None;
        }
    };

    match stream {
        Ok(s) => Some(s),
        Err(e) => {
            warn!("Failed to build {} stream: {}", source_name, e);
            None
        }
    }
}

/// Convert audio samples from i16 to f32 format.
pub fn i16_to_f32(samples: &[i16]) -> Vec<f32> {
    samples.iter().map(|&s| s as f32 / 32768.0).collect()
}

/// Convert audio samples from f32 to i16 format.
pub fn f32_to_i16(samples: &[f32]) -> Vec<i16> {
    samples
        .iter()
        .map(|&s| (s * 32767.0).clamp(-32768.0, 32767.0) as i16)
        .collect()
}

/// Resample audio to the target sample rate (simple linear interpolation).
/// For production, consider using a proper resampling library like rubato.
pub fn resample(samples: &[f32], from_rate: u32, to_rate: u32) -> Vec<f32> {
    if from_rate == to_rate {
        return samples.to_vec();
    }

    let ratio = to_rate as f64 / from_rate as f64;
    let new_len = (samples.len() as f64 * ratio) as usize;
    let mut output = Vec::with_capacity(new_len);

    for i in 0..new_len {
        let src_idx = i as f64 / ratio;
        let src_idx_floor = src_idx.floor() as usize;
        let src_idx_ceil = (src_idx_floor + 1).min(samples.len() - 1);
        let frac = src_idx - src_idx_floor as f64;

        let sample = samples[src_idx_floor] as f64 * (1.0 - frac)
            + samples[src_idx_ceil] as f64 * frac;
        output.push(sample as f32);
    }

    output
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_i16_to_f32_conversion() {
        let i16_samples = vec![0i16, 16384, -16384, 32767, -32768];
        let f32_samples = i16_to_f32(&i16_samples);

        assert!((f32_samples[0] - 0.0).abs() < 0.001);
        assert!((f32_samples[1] - 0.5).abs() < 0.001);
        assert!((f32_samples[2] - (-0.5)).abs() < 0.001);
    }

    #[test]
    fn test_resample_same_rate() {
        let samples = vec![1.0, 2.0, 3.0, 4.0];
        let resampled = resample(&samples, 16000, 16000);
        assert_eq!(samples, resampled);
    }
}
