//! Service d'encodage audio
//!
//! Ce module fournit un service de haut niveau pour lancer des encodages
//! audio, vérifier les statuts, et gérer les répertoires de sortie.

use crate::core::FfmpegEncoderPool;
use crate::core::job::{EncodeJob, EncodeJobStatus};
use crate::error::AppError;
use sqlx::PgPool;
use std::path::{Path, PathBuf};
use uuid::Uuid;

/// Service d'encodage audio
pub struct EncodingService {
    encoder_pool: FfmpegEncoderPool,
    db_pool: PgPool,
    base_output_dir: PathBuf,
}

impl EncodingService {
    /// Crée un nouveau service d'encodage
    ///
    /// # Arguments
    ///
    /// * `encoder_pool` - Pool d'encodeurs FFmpeg
    /// * `db_pool` - Pool de connexions PostgreSQL
    /// * `base_output_dir` - Répertoire de base pour les segments (ex: /data/streams)
    pub fn new(
        encoder_pool: FfmpegEncoderPool,
        db_pool: PgPool,
        base_output_dir: impl AsRef<Path>,
    ) -> Self {
        Self {
            encoder_pool,
            db_pool,
            base_output_dir: base_output_dir.as_ref().to_path_buf(),
        }
    }

    /// Lance un encodage pour un track avec une qualité spécifique
    ///
    /// # Arguments
    ///
    /// * `track_id` - ID du track à encoder
    /// * `quality` - Qualité (low, medium, high, hi_res)
    ///
    /// # Returns
    ///
    /// `Ok(())` si le job a été soumis avec succès
    pub async fn encode_track(&self, track_id: Uuid, quality: &str) -> Result<(), AppError> {
        use sqlx::Row;
        let track_row = sqlx::query(
            r#"
            SELECT id, file_path
            FROM tracks
            WHERE id = $1
            "#,
        )
        .bind(track_id)
        .fetch_optional(&self.db_pool)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to query track: {}", e),
        })?;

        let track_row = track_row.ok_or_else(|| AppError::NotFound {
            resource: format!("Track {}", track_id),
        })?;

        let source_path_str: String = track_row.try_get("file_path").map_err(|e| AppError::InternalError {
            message: format!("Failed to get file_path from row: {}", e),
        })?;
        
        let source_path = PathBuf::from(source_path_str);
        if !source_path.exists() {
            return Err(AppError::NotFound {
                resource: format!("Source file for track {}: {}", track_id, source_path.display()),
            });
        }

        // 2. Créer le répertoire de sortie
        let output_dir = self.base_output_dir
            .join(track_id.to_string())
            .join(quality);
        
        tokio::fs::create_dir_all(&output_dir)
            .await
            .map_err(|e| AppError::InternalError {
                message: format!("Failed to create output directory: {}", e),
            })?;

        // 3. Créer le job d'encodage
        let job = EncodeJob::from_quality_profile(
            track_id,
            source_path,
            output_dir,
            quality.to_string(),
        );

        // 4. Soumettre le job au pool
        self.encoder_pool.submit_job(job).await?;

        tracing::info!(
            "Encodage lancé pour track {} (qualité: {})",
            track_id,
            quality
        );

        Ok(())
    }

    /// Lance l'encodage pour toutes les qualités d'un track
    ///
    /// # Arguments
    ///
    /// * `track_id` - ID du track à encoder
    pub async fn encode_track_all_qualities(&self, track_id: Uuid) -> Result<(), AppError> {
        let qualities = vec!["low", "medium", "high", "hi_res"];
        
        for quality in qualities {
            if let Err(e) = self.encode_track(track_id, quality).await {
                tracing::error!(
                    "Failed to encode track {} with quality {}: {}",
                    track_id,
                    quality,
                    e
                );
                // Continue avec les autres qualités
            }
        }

        Ok(())
    }

    /// Récupère le statut d'encodage pour un track
    ///
    /// # Arguments
    ///
    /// * `track_id` - ID du track
    ///
    /// # Returns
    ///
    /// Statut d'encodage par qualité
    pub async fn get_encoding_status(
        &self,
        track_id: Uuid,
    ) -> Result<Vec<QualityStatus>, AppError> {
        use sqlx::Row;
        let jobs_rows = sqlx::query(
            r#"
            SELECT status, error_message, created_at, updated_at
            FROM stream_jobs
            WHERE track_id = $1
            ORDER BY created_at DESC
            LIMIT 10
            "#,
        )
        .bind(track_id)
        .fetch_all(&self.db_pool)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to query encoding status: {}", e),
        })?;

        // Récupérer les segments par qualité
        let segments_rows = sqlx::query(
            r#"
            SELECT quality, COUNT(*) as segment_count, MAX(segment_index) as max_index
            FROM stream_segments
            WHERE track_id = $1
            GROUP BY quality
            "#,
        )
        .bind(track_id)
        .fetch_all(&self.db_pool)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to query segments: {}", e),
        })?;

        // Construire le statut par qualité
        let mut statuses = Vec::new();
        let qualities = vec!["low", "medium", "high", "hi_res"];

        for quality in qualities {
            let job_row = jobs_rows.first(); // Prendre le job le plus récent
            let segment_info = segments_rows.iter().find(|s| {
                let s_quality: String = s.get("quality");
                s_quality == quality
            });

            let status = if let Some(row) = job_row {
                let status_str: String = row.get("status");
                EncodeJobStatus::from_str(&status_str).unwrap_or(EncodeJobStatus::Pending)
            } else {
                EncodeJobStatus::Pending
            };

            statuses.push(QualityStatus {
                quality: quality.to_string(),
                status,
                segment_count: segment_info.map(|s| s.get::<Option<i64>, _>("segment_count").unwrap_or(0) as u32).unwrap_or(0),
                error_message: job_row.and_then(|j| j.get("error_message")),
            });
        }

        Ok(statuses)
    }

    /// Vérifie si un track est complètement encodé (toutes les qualités)
    pub async fn is_track_fully_encoded(&self, track_id: Uuid) -> Result<bool, AppError> {
        let segments_row = sqlx::query(
            r#"
            SELECT COUNT(DISTINCT quality) as quality_count
            FROM stream_segments
            WHERE track_id = $1
            "#,
        )
        .bind(track_id)
        .fetch_one(&self.db_pool)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to check encoding status: {}", e),
        })?;

        use sqlx::Row;
        let quality_count: i64 = segments_row.get(0);

        // Vérifier qu'on a au moins 3 qualités (low, medium, high)
        Ok(quality_count >= 3)
    }
}

/// Statut d'encodage pour une qualité spécifique
#[derive(Debug, Clone, serde::Serialize)]
pub struct QualityStatus {
    pub quality: String,
    pub status: EncodeJobStatus,
    pub segment_count: u32,
    pub error_message: Option<String>,
}

