// src/services/videoService.js
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const config = require('../config');
const Video = require('../models/Video');

/**
 * Service de gestion des opérations vidéo
 */
class VideoService {
  /**
   * Crée une vidéo à partir d'une liste d'images
   * @param {number} projectId - ID du projet
   * @param {Array<string>} pathList - Liste des chemins d'images
   * @param {number} duration - Durée souhaitée en secondes
   * @param {number} videoId - ID de la vidéo
   * @param {number} resWidth - Largeur de la résolution
   * @param {number} resHeight - Hauteur de la résolution
   * @returns {Promise<string>} Chemin du fichier vidéo créé
   */
  static async createVideoFromImages(projectId, pathList, duration, videoId, resWidth, resHeight) {
    const tempFile = path.join('temp.txt');
    let ffmpegProcess;
    let cleanupDone = false;

    try {
      // Configuration des chemins
      const workdir = path.join(config.paths.storage, projectId.toString());
      if (!fs.existsSync(workdir)) {
        fs.mkdirSync(workdir, { recursive: true });
      }
      
      // Vérifie que des images ont été fournies
      if (!Array.isArray(pathList) || pathList.length === 0) {
        throw new Error('Liste d\'images vide ou invalide');
      }

      // Tri des images par ordre numérique
      const sortedImages = pathList.sort((a, b) => {
        const numA = parseInt(path.basename(a).match(/\d+/)[0], 10);
        const numB = parseInt(path.basename(b).match(/\d+/)[0], 10);
        return numA - numB;
      });

      // Création du fichier temporaire pour FFmpeg
      fs.writeFileSync(tempFile, sortedImages.map(image => `file '${image}'`).join('\n'));

      // Calcul des paramètres vidéo
      const totalFrames = sortedImages.length;
      const frameRate = Math.ceil(totalFrames / parseInt(duration));
      const timestamp = Date.now();
      const firstImageId = path.basename(sortedImages[0]).match(/\d+/)[0];
      const lastImageId = path.basename(sortedImages[sortedImages.length - 1]).match(/\d+/)[0];
      const outputVideo = path.join(
        workdir,
        `${projectId}_${firstImageId}_${lastImageId}-${timestamp}.mp4`
      );

      // Mise à jour initiale du statut vidéo
      await Video.updateVideo(videoId, {
        status: config.videoStatus.rendering,
        progress: 0,
        started_at: new Date(),
        updated_at: new Date(),
        eta: null
      });

      // Configuration du scaling vidéo
      const scale = resWidth && resHeight ? `scale=${resWidth}:${resHeight}` : 'scale=854:480';

      // Configuration des arguments FFmpeg
      const ffmpegArgs = [
        '-y',                  // Écrase les fichiers existants sans demander
        '-r', frameRate.toString(), // Framerate
        '-f', 'concat',        // Format de concaténation
        '-safe', '0',          // Autorise les chemins non sécurisés
        '-i', tempFile,        // Fichier d'entrée
        '-vsync', 'vfr',       // Synchronisation vidéo
        '-pix_fmt', 'yuv420p', // Format de pixels
        '-vf', scale,          // Filter vidéo pour le scaling
        '-b:v', '1500k',       // Bitrate vidéo
        outputVideo            // Fichier de sortie
      ];

      // Lancement du processus FFmpeg
      ffmpegProcess = spawn('ffmpeg', ffmpegArgs, { 
        stdio: ['ignore', 'ignore', 'pipe'] 
      });

      let lastUpdate = 0;
      const startTime = Date.now();

      // Capture de la progression du rendu
      ffmpegProcess.stderr.on('data', (data) => {
        const output = data.toString();
        const frameMatch = output.match(/frame=\s*(\d+)/);
        
        if (frameMatch) {
          const currentFrame = parseInt(frameMatch[1], 10);
          const progress = Math.min((currentFrame / totalFrames) * 100, 99.99);
          const now = Date.now();
          
          // Calcul du temps restant estimé
          const elapsedSeconds = (now - startTime) / 1000;
          const eta = elapsedSeconds / (currentFrame / totalFrames) - elapsedSeconds;

          // Mise à jour périodique (max toutes les 500ms)
          if (now - lastUpdate > 500) {
            Video.updateVideo(videoId, {
              progress: progress,
              eta: Math.round(eta),
              updated_at: new Date()
            });
            
            console.log(`[VIDEO] Progression: ${progress.toFixed(2)}%, ETA: ${eta.toFixed(0)}s`);
            lastUpdate = now;
          }
        }
      });

      // Attente de la fin du rendu vidéo
      await new Promise((resolve, reject) => {
        ffmpegProcess.on('close', async (code) => {
          if (code === 0) {
            try {
              // Mise à jour finale de la vidéo
              await Video.updateVideo(videoId, {
                status: config.videoStatus.completed,
                progress: 100,
                eta: 0,
                video_file: outputVideo,
                updated_at: new Date()
              });
              resolve();
            } catch (e) {
              reject(e);
            }
          } else {
            reject(new Error(`FFmpeg s'est terminé avec le code ${code}`));
          }
        });

        ffmpegProcess.on('error', reject);
      });

      return outputVideo;

    } catch (error) {
      // Gestion des erreurs
      console.error('[VIDEO] Erreur lors de la création de la vidéo:', error);
      
      try {
        // Mise à jour du statut vidéo en cas d'erreur
        await Video.updateVideo(videoId, {
          status: config.videoStatus.error,
          progress: 0,
          eta: null,
          updated_at: new Date()
        });
      } catch (dbError) {
        console.error('[VIDEO] Erreur lors de la mise à jour de la base de données:', dbError);
      }
      
      throw error;

    } finally {
      // Nettoyage des ressources
      if (!cleanupDone) {
        if (tempFile && fs.existsSync(tempFile)) {
          fs.unlinkSync(tempFile);
        }
        if (ffmpegProcess) {
          ffmpegProcess.kill();
        }
        cleanupDone = true;
      }
    }
  }

  /**
   * Récupère les informations de progression d'une vidéo
   * @param {number} videoId - ID de la vidéo
   * @returns {Promise<Object>} Informations de progression
   */
  static async getVideoProgress(videoId) {
    const video = await Video.getVideoById(videoId);
    
    if (!video) {
      throw new Error('Vidéo non trouvée');
    }
    
    return {
      progress: video.progress,
      elapsed: video.started_at ? Math.floor((new Date() - new Date(video.started_at)) / 1000) : 0,
      eta: video.eta,
      status: video.status
    };
  }

  /**
   * Convertit un code de statut en libellé
   * @param {number} status - Code de statut
   * @returns {string} Libellé du statut
   */
  static getStatusLabel(status) {
    const statusMap = {
      [config.videoStatus.rendering]: 'En cours',
      [config.videoStatus.completed]: 'Terminé',
      [config.videoStatus.error]: 'Échec',
      0: 'En attente'
    };
    
    return statusMap[status] || 'Inconnu';
  }
}

module.exports = VideoService;