//! Monitoring temps réel des logs FFmpeg
//!
//! Ce module lit stderr de FFmpeg ligne par ligne et détecte:
//! - Ouverture de segments
//! - Progression (time, speed)
//! - Erreurs
//! - Fin de muxing

use crate::core::processing::segment_tracker::SegmentInfo;
use crate::error::AppError;
use crate::transcoding::ffmpeg::progress_parser::FfmpegProgress;
use lazy_static::lazy_static;
use regex::Regex;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::sync::mpsc;

lazy_static! {
    // Regex pour détecter l'ouverture d'un segment
    // Exemple: "Opening 'segment_00000.ts' for writing"
    static ref OPENING_SEGMENT_REGEX: Regex = Regex::new(
        r"Opening '([^']+)' for writing"
    ).unwrap();

    // Regex pour détecter les erreurs
    static ref ERROR_REGEX: Regex = Regex::new(
        r"(?i)(error|failed|invalid|cannot|unable)"
    ).unwrap();

    // Regex pour détecter la fin de muxing
    static ref MUXING_OVERHEAD_REGEX: Regex = Regex::new(
        r"muxing overhead: ([\d.]+)%"
    ).unwrap();
}

/// Événement détecté dans les logs FFmpeg
#[derive(Debug, Clone)]
pub enum FFmpegEvent {
    /// Segment ouvert pour écriture
    SegmentOpened {
        path: PathBuf,
        index: u32,
    },
    /// Progression mise à jour
    Progress {
        time: Option<std::time::Duration>,
        speed: Option<f32>,
    },
    /// Erreur détectée
    Error(String),
    /// Muxing terminé
    MuxingComplete {
        overhead: f32,
    },
}

/// Monitor des logs FFmpeg
pub struct FFmpegMonitor {
    /// Répertoire de sortie
    output_dir: PathBuf,
    /// Sender pour les événements détectés
    event_tx: mpsc::Sender<FFmpegEvent>,
    /// Index de segment actuel
    current_segment_index: Arc<tokio::sync::Mutex<u32>>,
}

impl FFmpegMonitor {
    /// Crée un nouveau monitor
    pub fn new(output_dir: PathBuf) -> (Self, mpsc::Receiver<FFmpegEvent>) {
        let (tx, rx) = mpsc::channel(100);
        (
            Self {
                output_dir,
                event_tx: tx,
                current_segment_index: Arc::new(tokio::sync::Mutex::new(0)),
            },
            rx,
        )
    }

    /// Démarre le monitoring d'un stderr FFmpeg
    pub async fn monitor_stderr(
        &self,
        mut stderr: tokio::process::ChildStderr,
    ) -> Result<(), AppError> {
        let reader = BufReader::new(&mut stderr);
        let mut lines = reader.lines();

        while let Ok(Some(line)) = lines.next_line().await {
            self.process_line(&line).await?;
        }

        Ok(())
    }

    /// Traite une ligne de stderr
    async fn process_line(&self, line: &str) -> Result<(), AppError> {
        // Détecter ouverture de segment
        if let Some(caps) = OPENING_SEGMENT_REGEX.captures(line) {
            if let Some(path_str) = caps.get(1) {
                let segment_path = PathBuf::from(path_str.as_str());
                
                // Extraire l'index depuis le nom de fichier
                // Format: segment_00000.ts
                let index = self.extract_segment_index(&segment_path).await;
                
                let event = FFmpegEvent::SegmentOpened {
                    path: segment_path,
                    index,
                };
                
                if let Err(e) = self.event_tx.send(event).await {
                    tracing::warn!("Failed to send segment opened event: {}", e);
                }
            }
        }

        // Détecter progression
        if let Some(progress) = FfmpegProgress::parse(line) {
            let event = FFmpegEvent::Progress {
                time: progress.time,
                speed: progress.speed,
            };
            
            if let Err(e) = self.event_tx.send(event).await {
                tracing::warn!("Failed to send progress event: {}", e);
            }
        }

        // Détecter erreurs
        if ERROR_REGEX.is_match(line) {
            let event = FFmpegEvent::Error(line.to_string());
            
            if let Err(e) = self.event_tx.send(event).await {
                tracing::warn!("Failed to send error event: {}", e);
            }
        }

        // Détecter fin de muxing
        if let Some(caps) = MUXING_OVERHEAD_REGEX.captures(line) {
            if let Some(overhead_str) = caps.get(1) {
                if let Ok(overhead) = overhead_str.as_str().parse::<f32>() {
                    let event = FFmpegEvent::MuxingComplete { overhead };
                    
                    if let Err(e) = self.event_tx.send(event).await {
                        tracing::warn!("Failed to send muxing complete event: {}", e);
                    }
                }
            }
        }

        Ok(())
    }

    /// Extrait l'index d'un segment depuis son nom de fichier
    async fn extract_segment_index(&self, path: &PathBuf) -> u32 {
        let filename = path.file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("");

        // Format: segment_00000.ts
        if let Some(start) = filename.find("segment_") {
            let num_str = &filename[start + 7..];
            if let Some(end) = num_str.find('.') {
                if let Ok(index) = num_str[..end].parse::<u32>() {
                    let mut current = self.current_segment_index.lock().await;
                    *current = index;
                    return index;
                }
            }
        }

        // Fallback: incrémenter
        let mut current = self.current_segment_index.lock().await;
        let index = *current;
        *current += 1;
        index
    }

    /// Détecte les segments depuis le répertoire de sortie
    /// Utile pour détecter les segments déjà générés
    pub async fn detect_existing_segments(
        &self,
    ) -> Result<Vec<SegmentInfo>, AppError> {
        let mut segments = Vec::new();

        let mut entries = tokio::fs::read_dir(&self.output_dir).await
            .map_err(|e| AppError::InternalError {
                message: format!("Failed to read output directory: {}", e),
            })?;

        while let Some(entry) = entries.next_entry().await
            .map_err(|e| AppError::InternalError {
                message: format!("Failed to read directory entry: {}", e),
            })? {
            
            let path = entry.path();
            if path.extension().and_then(|s| s.to_str()) == Some("ts") {
                if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
                    // Extraire l'index
                    if let Some(start) = file_name.find("segment_") {
                        let num_str = &file_name[start + 7..];
                        if let Some(end) = num_str.find('.') {
                            if let Ok(index) = num_str[..end].parse::<u32>() {
                                // Obtenir les métadonnées du fichier
                                let metadata = entry.metadata().await
                                    .map_err(|e| AppError::InternalError {
                                        message: format!("Failed to get file metadata: {}", e),
                                    })?;

                                let segment = SegmentInfo {
                                    index,
                                    path: path.clone(),
                                    duration: 4.0, // Durée par défaut (sera mise à jour depuis le manifest)
                                    timestamp: metadata
                                        .modified()
                                        .or_else(|| metadata.created())
                                        .unwrap_or_else(|| std::time::SystemTime::now()),
                                };

                                segments.push(segment);
                            }
                        }
                    }
                }
            }
        }

        // Trier par index
        segments.sort_by_key(|s| s.index);

        Ok(segments)
    }
}

