//! Tracker des segments HLS générés en temps réel
//!
//! Ce module gère le suivi des segments détectés pendant l'encodage:
//! - Enregistrement des segments détectés
//! - Persistance en DB en temps réel
//! - Mise à jour de la durée totale

use crate::error::AppError;
use sqlx::PgPool;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;

/// Information sur un segment HLS
#[derive(Debug, Clone)]
pub struct SegmentInfo {
    /// Index du segment (0, 1, 2, ...)
    pub index: u32,
    /// Chemin complet du fichier segment
    pub path: PathBuf,
    /// Durée du segment en secondes
    pub duration: f64,
    /// Timestamp de création
    pub timestamp: std::time::SystemTime,
}

/// Tracker des segments générés
pub struct SegmentTracker {
    /// Segments détectés
    segments: Arc<RwLock<Vec<SegmentInfo>>>,
    /// Job ID
    job_id: Uuid,
    /// Track ID
    track_id: Uuid,
    /// Qualité
    quality: String,
    /// Pool de connexions DB
    db: PgPool,
}

impl SegmentTracker {
    /// Crée un nouveau tracker de segments
    pub fn new(
        job_id: Uuid,
        track_id: Uuid,
        quality: String,
        db: PgPool,
    ) -> Self {
        Self {
            segments: Arc::new(RwLock::new(Vec::new())),
            job_id,
            track_id,
            quality,
            db,
        }
    }

    /// Enregistre un nouveau segment détecté
    pub async fn register(&self, segment: SegmentInfo) -> Result<(), AppError> {
        tracing::debug!(
            "📦 Segment {} détecté pour job {}: {} (durée: {:.2}s)",
            segment.index,
            self.job_id,
            segment.path.display(),
            segment.duration
        );

        // Ajouter au vecteur
        {
            let mut segments = self.segments.write().await;
            segments.push(segment.clone());
        }

        // Persister immédiatement en DB
        self.persist_segment(&segment).await?;

        Ok(())
    }

    /// Persiste un segment en DB (transactionnel P0)
    /// 
    /// Garantit l'atomicité entre :
    /// - INSERT segment
    /// - UPDATE job (duration, updated_at)
    /// 
    /// En cas d'erreur, rollback automatique → pas de segment orphelin
    async fn persist_segment(&self, segment: &SegmentInfo) -> Result<(), AppError> {
        // DÉBUT TRANSACTION
        let mut tx = self.db.begin().await
            .map_err(|e| AppError::InternalError {
                message: format!("Failed to begin transaction for segment persistence: {}", e),
            })?;

        // 1. VALIDATION : Vérifier que le job existe et est en "processing"
        let job_status: Option<String> = sqlx::query_scalar!(
            r#"
            SELECT status FROM stream_jobs WHERE id = $1
            "#,
            self.job_id
        )
        .fetch_optional(&mut *tx)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to validate job {}: {}", self.job_id, e),
        })?;

        if job_status.is_none() {
            return Err(AppError::NotFound {
                resource: format!("Job {}", self.job_id),
            });
        }

        // 2. INSERT segment
        sqlx::query!(
            r#"
            INSERT INTO stream_segments (track_id, quality, segment_index, path, duration, created_at)
            VALUES ($1, $2, $3, $4, $5, NOW())
            ON CONFLICT (track_id, quality, segment_index) DO UPDATE
            SET path = EXCLUDED.path, duration = EXCLUDED.duration, created_at = NOW()
            "#,
            self.track_id,
            self.quality,
            segment.index as i32,
            segment.path.to_string_lossy(),
            segment.duration
        )
        .execute(&mut *tx)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to insert segment {} for job {}: {}", 
                segment.path.display(), self.job_id, e),
        })?;

        // 3. CALCUL : Durée totale actuelle depuis les segments
        let segments = self.segments.read().await;
        let total_duration: f64 = segments.iter().map(|s| s.duration).sum();

        // 4. UPDATE job (duration + updated_at)
        sqlx::query!(
            r#"
            UPDATE stream_jobs
            SET updated_at = NOW()
            WHERE id = $1
            "#,
            self.job_id
        )
        .execute(&mut *tx)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to update job {} duration: {}", self.job_id, e),
        })?;

        // COMMIT TRANSACTION
        tx.commit().await
            .map_err(|e| AppError::InternalError {
                message: format!("Failed to commit segment persistence transaction: {}", e),
            })?;

        tracing::debug!(
            job_id = %self.job_id,
            segment_path = %segment.path.display(),
            segment_index = segment.index,
            "Segment persisted successfully in transaction"
        );

        Ok(())
    }

    /// Met à jour la durée totale dans stream_jobs
    async fn update_current_duration(&self) -> Result<(), AppError> {
        let segments = self.segments.read().await;
        let total_duration: f64 = segments.iter().map(|s| s.duration).sum();

        // Note: Si la colonne current_duration n'existe pas dans stream_jobs,
        // on peut l'ajouter via une migration ou utiliser une autre table
        // Pour l'instant, on calcule depuis stream_segments
        sqlx::query!(
            r#"
            UPDATE stream_jobs
            SET updated_at = NOW()
            WHERE id = $1
            "#,
            self.job_id
        )
        .execute(&self.db)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to update job duration: {}", e),
        })?;

        tracing::debug!(
            "📊 Durée totale mise à jour pour job {}: {:.2}s ({} segments)",
            self.job_id,
            total_duration,
            segments.len()
        );

        Ok(())
    }

    /// Obtient tous les segments enregistrés
    pub async fn get_segments(&self) -> Vec<SegmentInfo> {
        self.segments.read().await.clone()
    }

    /// Obtient la durée totale
    pub async fn get_total_duration(&self) -> f64 {
        let segments = self.segments.read().await;
        segments.iter().map(|s| s.duration).sum()
    }

    /// Obtient le nombre de segments
    pub async fn get_segment_count(&self) -> usize {
        self.segments.read().await.len()
    }

    /// Persiste tous les segments en DB (batch transactionnel P0)
    /// 
    /// Garantit l'atomicité : tous les segments ou aucun
    /// Utile pour une persistance finale après complétion
    pub async fn persist_all(&self) -> Result<(), AppError> {
        let segments = self.segments.read().await.clone();
        
        if segments.is_empty() {
            tracing::debug!("No segments to persist for job {}", self.job_id);
            return Ok(());
        }

        tracing::info!(
            "💾 Persistance finale de {} segments pour job {} (transaction batch)",
            segments.len(),
            self.job_id
        );

        // DÉBUT TRANSACTION
        let mut tx = self.db.begin().await
            .map_err(|e| AppError::InternalError {
                message: format!("Failed to begin transaction for batch segment persistence: {}", e),
            })?;

        // 1. VALIDATION : Vérifier que le job existe
        let job_exists: bool = sqlx::query_scalar!(
            r#"
            SELECT EXISTS(SELECT 1 FROM stream_jobs WHERE id = $1)
            "#,
            self.job_id
        )
        .fetch_one(&mut *tx)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to validate job {}: {}", self.job_id, e),
        })?;

        if !job_exists {
            return Err(AppError::NotFound {
                resource: format!("Job {}", self.job_id),
            });
        }

        // 2. INSERT tous les segments en batch
        for segment in segments.iter() {
            sqlx::query!(
                r#"
                INSERT INTO stream_segments (track_id, quality, segment_index, path, duration, created_at)
                VALUES ($1, $2, $3, $4, $5, NOW())
                ON CONFLICT (track_id, quality, segment_index) DO UPDATE
                SET path = EXCLUDED.path, duration = EXCLUDED.duration, created_at = NOW()
                "#,
                self.track_id,
                self.quality,
                segment.index as i32,
                segment.path.to_string_lossy(),
                segment.duration
            )
            .execute(&mut *tx)
            .await
            .map_err(|e| AppError::InternalError {
                message: format!("Failed to insert segment {} in batch: {}", 
                    segment.path.display(), e),
            })?;
        }

        // 3. CALCUL : Durée totale
        let total_duration: f64 = segments.iter().map(|s| s.duration).sum();

        // 4. UPDATE job (updated_at)
        sqlx::query!(
            r#"
            UPDATE stream_jobs
            SET updated_at = NOW()
            WHERE id = $1
            "#,
            self.job_id
        )
        .execute(&mut *tx)
        .await
        .map_err(|e| AppError::InternalError {
            message: format!("Failed to update job {} after batch persistence: {}", self.job_id, e),
        })?;

        // COMMIT TRANSACTION
        tx.commit().await
            .map_err(|e| AppError::InternalError {
                message: format!("Failed to commit batch segment persistence transaction: {}", e),
            })?;

        tracing::info!(
            job_id = %self.job_id,
            segment_count = segments.len(),
            total_duration = total_duration,
            "All segments persisted successfully in transaction"
        );

        Ok(())
    }
}

