//! Whisper transcription using whisper-rs.

use std::path::Path;
use thiserror::Error;
use tracing::{debug, info};
use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters};

#[derive(Error, Debug)]
pub enum TranscriberError {
    #[error("Failed to load Whisper model: {0}")]
    ModelLoadError(String),
    #[error("Transcription failed: {0}")]
    TranscriptionError(String),
    #[error("Model not loaded")]
    ModelNotLoaded,
}

/// A transcription segment with timing information.
#[derive(Debug, Clone, serde::Serialize)]
pub struct TranscriptSegment {
    pub start: f64,
    pub end: f64,
    pub text: String,
    pub speaker: String,
}

/// Whisper transcriber using whisper-rs bindings.
pub struct WhisperTranscriber {
    context: Option<WhisperContext>,
    model_path: Option<String>,
}

impl WhisperTranscriber {
    /// Create a new transcriber instance (without loading a model).
    pub fn new() -> Self {
        Self {
            context: None,
            model_path: None,
        }
    }

    /// Load a Whisper model from the given path.
    pub fn load_model(&mut self, model_path: &Path) -> Result<(), TranscriberError> {
        info!("Loading Whisper model from: {}", model_path.display());

        let params = WhisperContextParameters::default();

        let context = WhisperContext::new_with_params(
            model_path.to_str().unwrap_or_default(),
            params,
        )
        .map_err(|e| TranscriberError::ModelLoadError(e.to_string()))?;

        self.context = Some(context);
        self.model_path = Some(model_path.to_string_lossy().to_string());

        info!("Whisper model loaded successfully");
        Ok(())
    }

    /// Check if a model is loaded.
    pub fn is_loaded(&self) -> bool {
        self.context.is_some()
    }

    /// Transcribe audio samples (expected to be 16kHz mono f32).
    pub fn transcribe(&self, audio: &[f32]) -> Result<Vec<TranscriptSegment>, TranscriberError> {
        self.transcribe_with_progress(audio, |_| {})
    }

    /// Transcribe audio samples with progress callback.
    pub fn transcribe_with_progress<F>(&self, audio: &[f32], mut progress_fn: F) -> Result<Vec<TranscriptSegment>, TranscriberError>
    where
        F: FnMut(f64),
    {
        let context = self
            .context
            .as_ref()
            .ok_or(TranscriberError::ModelNotLoaded)?;

        debug!("Transcribing {} samples ({:.2}s of audio)",
               audio.len(),
               audio.len() as f64 / 16000.0);

        // Report initial progress
        progress_fn(0.0);

        // Create a state for this transcription
        let mut state = context
            .create_state()
            .map_err(|e| TranscriberError::TranscriptionError(e.to_string()))?;

        // Report progress after state creation
        progress_fn(0.2);

        // Set up transcription parameters
        let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 });

        // Configure for English transcription
        params.set_language(Some("en"));
        params.set_print_special(false);
        params.set_print_progress(false);
        params.set_print_realtime(false);
        params.set_print_timestamps(false);
        params.set_token_timestamps(true);
        params.set_translate(false);

        // Run transcription
        state
            .full(params, audio)
            .map_err(|e| TranscriberError::TranscriptionError(e.to_string()))?;

        // Report progress after transcription
        progress_fn(0.8);

        // Extract segments
        let num_segments = state.full_n_segments()
            .map_err(|e| TranscriberError::TranscriptionError(e.to_string()))?;

        let mut segments = Vec::new();

        for i in 0..num_segments {
            let start = state
                .full_get_segment_t0(i)
                .map_err(|e| TranscriberError::TranscriptionError(e.to_string()))?;
            let end = state
                .full_get_segment_t1(i)
                .map_err(|e| TranscriberError::TranscriptionError(e.to_string()))?;
            let text = state
                .full_get_segment_text(i)
                .map_err(|e| TranscriberError::TranscriptionError(e.to_string()))?;

            // Convert centiseconds to seconds
            let start_secs = start as f64 / 100.0;
            let end_secs = end as f64 / 100.0;

            segments.push(TranscriptSegment {
                start: start_secs,
                end: end_secs,
                text: text.trim().to_string(),
                speaker: "Unknown".to_string(), // Will be filled by VAD
            });
        }

        // Report completion
        progress_fn(1.0);

        info!("Transcription complete: {} segments", segments.len());
        Ok(segments)
    }
}

impl Default for WhisperTranscriber {
    fn default() -> Self {
        Self::new()
    }
}

/// Download the Whisper model if not present.
/// Returns the path to the model file.
pub async fn ensure_whisper_model(models_dir: &Path, model_size: &str) -> Result<std::path::PathBuf, TranscriberError> {
    let model_filename = format!("ggml-{}.bin", model_size);
    let model_path = models_dir.join(&model_filename);

    if model_path.exists() {
        info!("Whisper model already exists: {}", model_path.display());
        return Ok(model_path);
    }

    // Model URLs from Hugging Face
    let model_url = format!(
        "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/{}",
        model_filename
    );

    info!("Downloading Whisper model from: {}", model_url);

    // Create models directory if it doesn't exist
    std::fs::create_dir_all(models_dir)
        .map_err(|e| TranscriberError::ModelLoadError(format!("Failed to create models directory: {}", e)))?;

    // Download the model using reqwest or similar
    // For now, we'll return an error indicating manual download is needed
    Err(TranscriberError::ModelLoadError(format!(
        "Model not found. Please download {} to {}",
        model_filename,
        model_path.display()
    )))
}

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

    #[test]
    fn test_transcriber_not_loaded() {
        let transcriber = WhisperTranscriber::new();
        assert!(!transcriber.is_loaded());
    }
}
