//! Module de génération de playlists HLS (HTTP Live Streaming)
//!
//! Ce module fournit des fonctionnalités pour générer des playlists HLS (.m3u8)
//! pour le streaming adaptatif avec différentes qualités.

use serde::{Deserialize, Serialize};
use std::path::Path;
use std::time::Duration;

/// Segmente un fichier audio pour HLS via FFmpeg
pub async fn segment_file(
    input_path: &Path,
    output_dir: &Path,
    segment_duration: Duration,
    base_filename: &str,
) -> std::io::Result<()> {
    // S'assurer que le répertoire de sortie existe
    if !output_dir.exists() {
        tokio::fs::create_dir_all(output_dir).await?;
    }

    let segment_filename_pattern = format!("{}_%05d.ts", base_filename);
    let output_pattern = output_dir.join(&segment_filename_pattern);
    // Note: On ne génère pas la playlist ici car HLSGenerator le fait dynamiquement
    // On demande juste à ffmpeg de générer les segments
    
    // Commande: ffmpeg -i input -c copy -f segment -segment_time 10 -segment_format mpegts output_%05d.ts
    // Note: -c copy suppose que l'entrée est déjà dans un format compatible (AAC/MP3).
    // Si ce n'est pas le cas, il faudrait transcoder (ex: -c:a aac -b:a 128k)
    // Pour simplifier, on suppose que l'entrée est déjà transcodée par CompressionEngine
    
    let status = tokio::process::Command::new("ffmpeg")
        .arg("-y")
        .arg("-i")
        .arg(input_path)
        .arg("-c")
        .arg("copy")
        .arg("-f")
        .arg("segment")
        .arg("-segment_time")
        .arg(segment_duration.as_secs().to_string())
        .arg("-segment_format")
        .arg("mpegts")
        .arg(output_pattern)
        .status()
        .await?;

    if !status.success() {
        return Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            "FFmpeg segmentation failed",
        ));
    }

    Ok(())
}

/// Générateur de playlists HLS
#[derive(Debug, Clone)]
pub struct HLSGenerator {
    track_id: String,
    qualities: Vec<HLSQuality>,
    base_url: String,
    segment_duration: Duration,
}

/// Configuration d'une qualité HLS
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HLSQuality {
    pub quality_id: String,
    pub bandwidth: u32,
    pub codecs: String,
    pub resolution: Option<String>,
    pub audio_sample_rate: Option<u32>,
    pub audio_channels: Option<u8>,
}

impl HLSGenerator {
    /// Crée un nouveau générateur HLS
    pub fn new(track_id: String, base_url: String) -> Self {
        Self {
            track_id,
            qualities: vec![],
            base_url,
            segment_duration: Duration::from_secs(10),
        }
    }

    /// Ajoute une qualité au générateur
    pub fn with_quality(mut self, quality: HLSQuality) -> Self {
        self.qualities.push(quality);
        self
    }

    /// Définit la durée des segments
    pub fn with_segment_duration(mut self, duration: Duration) -> Self {
        self.segment_duration = duration;
        self
    }

    /// Génère la master playlist (master.m3u8)
    pub fn generate_master_playlist(&self) -> String {
        let mut playlist = String::from("#EXTM3U\n");
        playlist.push_str("#EXT-X-VERSION:6\n");
        playlist.push_str("#EXT-X-INDEPENDENT-SEGMENTS\n\n");

        for quality in &self.qualities {
            let mut stream_info = format!("#EXT-X-STREAM-INF:BANDWIDTH={}", quality.bandwidth);

            // Ajouter codecs
            stream_info.push_str(&format!(",CODECS=\"{}\"", quality.codecs));

            // Ajouter résolution si disponible
            if let Some(ref resolution) = quality.resolution {
                stream_info.push_str(&format!(",RESOLUTION={}", resolution));
            }

            // Ajouter audio sample rate si disponible
            if quality.audio_sample_rate.is_some() {
                stream_info.push_str(&format!(",AUDIO=\"{}-audio\"", quality.quality_id));
            }

            playlist.push_str(&stream_info);
            playlist.push('\n');

            // URL de la playlist de qualité
            let playlist_url = format!(
                "{}/hls/{}/{}/playlist.m3u8",
                self.base_url, self.track_id, quality.quality_id
            );
            playlist.push_str(&playlist_url);
            playlist.push('\n');
            playlist.push('\n');
        }

        playlist
    }

    /// Génère une playlist de qualité spécifique
    pub fn generate_quality_playlist(
        &self,
        quality_id: &str,
        segment_count: usize,
    ) -> Result<String, HLSGeneratorError> {
        // Trouver la qualité
        let _quality = self
            .qualities
            .iter()
            .find(|q| q.quality_id == quality_id)
            .ok_or_else(|| HLSGeneratorError::QualityNotFound {
                quality_id: quality_id.to_string(),
            })?;

        let mut playlist = String::from("#EXTM3U\n");
        playlist.push_str("#EXT-X-VERSION:6\n");
        playlist.push_str(&format!(
            "#EXT-X-TARGETDURATION:{}\n",
            self.segment_duration.as_secs()
        ));
        playlist.push_str("#EXT-X-MEDIA-SEQUENCE:0\n");
        playlist.push_str("#EXT-X-PLAYLIST-TYPE:VOD\n");
        playlist.push('\n');

        // Générer les segments
        for i in 0..segment_count {
            let segment_duration_secs = self.segment_duration.as_secs_f32();
            playlist.push_str(&format!("#EXTINF:{:.3},\n", segment_duration_secs));

            // URL du segment .ts
            let segment_url = format!(
                "{}/hls/{}/{}/segment_{:05}.ts",
                self.base_url, self.track_id, quality_id, i
            );
            playlist.push_str(&segment_url);
            playlist.push('\n');
        }

        // Marquer la fin de la playlist
        playlist.push_str("#EXT-X-ENDLIST\n");

        Ok(playlist)
    }

    /// Génère une playlist dynamique (live) avec un nombre limité de segments
    pub fn generate_live_playlist(
        &self,
        quality_id: &str,
        segment_count: usize,
        media_sequence: usize,
    ) -> Result<String, HLSGeneratorError> {
        // Trouver la qualité
        let _quality = self
            .qualities
            .iter()
            .find(|q| q.quality_id == quality_id)
            .ok_or_else(|| HLSGeneratorError::QualityNotFound {
                quality_id: quality_id.to_string(),
            })?;

        let mut playlist = String::from("#EXTM3U\n");
        playlist.push_str("#EXT-X-VERSION:6\n");
        playlist.push_str(&format!(
            "#EXT-X-TARGETDURATION:{}\n",
            self.segment_duration.as_secs()
        ));
        playlist.push_str(&format!("#EXT-X-MEDIA-SEQUENCE:{}\n", media_sequence));
        playlist.push('\n');

        // Générer les segments (derniers N segments pour live)
        for i in 0..segment_count {
            let segment_index = media_sequence + i;
            let segment_duration_secs = self.segment_duration.as_secs_f32();
            playlist.push_str(&format!("#EXTINF:{:.3},\n", segment_duration_secs));

            // URL du segment .ts
            let segment_url = format!(
                "{}/hls/{}/{}/segment_{:05}.ts",
                self.base_url, self.track_id, quality_id, segment_index
            );
            playlist.push_str(&segment_url);
            playlist.push('\n');
        }

        // Pas de #EXT-X-ENDLIST pour les playlists live
        Ok(playlist)
    }
}

/// Erreurs possibles lors de la génération HLS
#[derive(Debug, thiserror::Error)]
pub enum HLSGeneratorError {
    #[error("Quality not found: {quality_id}")]
    QualityNotFound { quality_id: String },
}

impl HLSQuality {
    /// Crée une qualité haute définition
    pub fn high() -> Self {
        Self {
            quality_id: "high".to_string(),
            bandwidth: 320000,               // 320 kbps
            codecs: "mp4a.40.2".to_string(), // AAC-LC
            resolution: None,
            audio_sample_rate: Some(44100),
            audio_channels: Some(2),
        }
    }

    /// Crée une qualité moyenne
    pub fn medium() -> Self {
        Self {
            quality_id: "medium".to_string(),
            bandwidth: 192000, // 192 kbps
            codecs: "mp4a.40.2".to_string(),
            resolution: None,
            audio_sample_rate: Some(44100),
            audio_channels: Some(2),
        }
    }

    /// Crée une qualité basse
    pub fn low() -> Self {
        Self {
            quality_id: "low".to_string(),
            bandwidth: 128000, // 128 kbps
            codecs: "mp4a.40.2".to_string(),
            resolution: None,
            audio_sample_rate: Some(22050),
            audio_channels: Some(2),
        }
    }

    /// Crée une qualité mobile
    pub fn mobile() -> Self {
        Self {
            quality_id: "mobile".to_string(),
            bandwidth: 96000, // 96 kbps
            codecs: "mp4a.40.2".to_string(),
            resolution: None,
            audio_sample_rate: Some(22050),
            audio_channels: Some(1),
        }
    }
}

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

    #[test]
    fn test_hls_generator_new() {
        let generator =
            HLSGenerator::new("track123".to_string(), "http://localhost:8080".to_string());

        assert_eq!(generator.track_id, "track123");
        assert_eq!(generator.base_url, "http://localhost:8080");
        assert!(generator.qualities.is_empty());
    }

    #[test]
    fn test_hls_generator_with_qualities() {
        let generator =
            HLSGenerator::new("track123".to_string(), "http://localhost:8080".to_string())
                .with_quality(HLSQuality::high())
                .with_quality(HLSQuality::medium());

        assert_eq!(generator.qualities.len(), 2);
    }

    #[test]
    fn test_generate_master_playlist() {
        let generator =
            HLSGenerator::new("track123".to_string(), "http://localhost:8080".to_string())
                .with_quality(HLSQuality::high())
                .with_quality(HLSQuality::medium());

        let playlist = generator.generate_master_playlist();

        // Vérifier les éléments de base
        assert!(playlist.contains("#EXTM3U"));
        assert!(playlist.contains("#EXT-X-VERSION:6"));
        assert!(playlist.contains("#EXT-X-INDEPENDENT-SEGMENTS"));

        // Vérifier la présence des qualités
        assert!(playlist.contains("high"));
        assert!(playlist.contains("medium"));
        assert!(playlist.contains("BANDWIDTH"));

        // Vérifier les URLs
        assert!(playlist.contains("/hls/track123/high/playlist.m3u8"));
        assert!(playlist.contains("/hls/track123/medium/playlist.m3u8"));
    }

    #[test]
    fn test_generate_quality_playlist() {
        let generator =
            HLSGenerator::new("track123".to_string(), "http://localhost:8080".to_string())
                .with_quality(HLSQuality::high())
                .with_segment_duration(Duration::from_secs(10));

        let playlist = generator
            .generate_quality_playlist("high", 5)
            .expect("Should generate playlist");

        // Vérifier les éléments de base
        assert!(playlist.contains("#EXTM3U"));
        assert!(playlist.contains("#EXT-X-VERSION:6"));
        assert!(playlist.contains("#EXT-X-TARGETDURATION:10"));
        assert!(playlist.contains("#EXT-X-MEDIA-SEQUENCE:0"));
        assert!(playlist.contains("#EXT-X-PLAYLIST-TYPE:VOD"));
        assert!(playlist.contains("#EXT-X-ENDLIST"));

        // Vérifier les segments
        for i in 0..5 {
            assert!(playlist.contains(&format!("segment_{:05}.ts", i)));
            assert!(playlist.contains("#EXTINF:"));
        }
    }

    #[test]
    fn test_generate_live_playlist() {
        let generator =
            HLSGenerator::new("track123".to_string(), "http://localhost:8080".to_string())
                .with_quality(HLSQuality::high())
                .with_segment_duration(Duration::from_secs(10));

        let playlist = generator
            .generate_live_playlist("high", 5, 10)
            .expect("Should generate playlist");

        // Vérifier les éléments de base
        assert!(playlist.contains("#EXTM3U"));
        assert!(playlist.contains("#EXT-X-VERSION:6"));
        assert!(playlist.contains("#EXT-X-TARGETDURATION:10"));
        assert!(playlist.contains("#EXT-X-MEDIA-SEQUENCE:10"));

        // Ne doit pas contenir #EXT-X-ENDLIST pour live
        assert!(!playlist.contains("#EXT-X-ENDLIST"));

        // Vérifier les segments avec media_sequence
        for i in 0..5 {
            assert!(playlist.contains(&format!("segment_{:05}.ts", 10 + i)));
        }
    }

    #[test]
    fn test_generate_quality_playlist_not_found() {
        let generator =
            HLSGenerator::new("track123".to_string(), "http://localhost:8080".to_string())
                .with_quality(HLSQuality::high());

        let result = generator.generate_quality_playlist("nonexistent", 5);

        assert!(result.is_err());
        if let Err(HLSGeneratorError::QualityNotFound { quality_id }) = result {
            assert_eq!(quality_id, "nonexistent");
        } else {
            panic!("Expected QualityNotFound error");
        }
    }

    #[test]
    fn test_hls_quality_defaults() {
        let high = HLSQuality::high();
        assert_eq!(high.quality_id, "high");
        assert_eq!(high.bandwidth, 320000);
        assert_eq!(high.codecs, "mp4a.40.2");
        assert_eq!(high.audio_sample_rate, Some(44100));
        assert_eq!(high.audio_channels, Some(2));

        let mobile = HLSQuality::mobile();
        assert_eq!(mobile.quality_id, "mobile");
        assert_eq!(mobile.bandwidth, 96000);
        assert_eq!(mobile.audio_channels, Some(1));
    }

    #[test]
    fn test_master_playlist_format() {
        let generator = HLSGenerator::new(
            "test-track".to_string(),
            "https://stream.example.com".to_string(),
        )
        .with_quality(HLSQuality::high())
        .with_quality(HLSQuality::low());

        let playlist = generator.generate_master_playlist();

        // Vérifier le format de chaque stream info
        assert!(playlist.contains("#EXT-X-STREAM-INF:BANDWIDTH="));
        assert!(playlist.contains("CODECS="));
        assert!(playlist.contains("playlist.m3u8"));

        // Vérifier que chaque qualité a son entrée
        let lines: Vec<&str> = playlist.lines().collect();
        let stream_inf_count = lines
            .iter()
            .filter(|line| line.starts_with("#EXT-X-STREAM-INF"))
            .count();

        assert_eq!(stream_inf_count, 2);
    }

    #[test]
    fn test_quality_playlist_segment_count() {
        let generator =
            HLSGenerator::new("track123".to_string(), "http://localhost:8080".to_string())
                .with_quality(HLSQuality::medium());

        let playlist = generator
            .generate_quality_playlist("medium", 10)
            .expect("Should generate playlist");

        // Compter les segments
        let segment_count = playlist.matches("segment_").count();
        assert_eq!(segment_count, 10);

        // Compter les EXTINF
        let extinf_count = playlist.matches("#EXTINF").count();
        assert_eq!(extinf_count, 10);
    }
}
