<?php
/*******************************************************************************************************************
| Software Name        : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author      : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
| 
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/

defined('_ISVALID') or header('Location: /error');

/**
 * Advanced Video Processing System with FFmpeg Integration
 */
class VVideoProcessor
{
    private $ffmpegPath;
    private $ffprobePath;
    private $tempDir;
    private $outputDir;
    private $logger;
    private $queue;
    
    public function __construct()
    {
        $this->ffmpegPath = $this->findFFmpegPath();
        $this->ffprobePath = $this->findFFprobePath();
        $this->tempDir = _FPATH . 'f_data/temp/';
        $this->outputDir = _FPATH . 'f_data/processed/';
        $this->logger = VLogger::getInstance();
        $this->queue = new VQueue();
        
        // Create directories if they don't exist
        $this->ensureDirectories();
    }
    
    /**
     * Process video file with multiple formats and HLS streaming
     * @param string $inputFile Input video file path
     * @param string $videoKey Video unique key
     * @param array $options Processing options
     * @return array Processing result
     */
    public function processVideo($inputFile, $videoKey, $options = [])
    {
        $this->logger->info('Starting video processing', [
            'input_file' => $inputFile,
            'video_key' => $videoKey,
            'options' => $options
        ]);
        
        try {
            // Validate input file
            if (!file_exists($inputFile)) {
                throw new Exception("Input file not found: {$inputFile}");
            }
            
            // Get video information
            $videoInfo = $this->getVideoInfo($inputFile);
            if (!$videoInfo) {
                throw new Exception("Unable to read video information");
            }
            
            // Create output directory for this video
            $videoOutputDir = $this->outputDir . $videoKey . '/';
            if (!is_dir($videoOutputDir)) {
                mkdir($videoOutputDir, 0755, true);
            }
            
            // Process different formats
            $formats = $options['formats'] ?? ['1080p', '720p', '480p', '360p'];
            $results = [];
            
            foreach ($formats as $format) {
                $result = $this->processFormat($inputFile, $videoOutputDir, $format, $videoKey, $videoInfo);
                $results[$format] = $result;
            }
            
            // Generate HLS playlist
            $hlsResult = $this->generateHLS($videoOutputDir, $videoKey, $results);
            $results['hls'] = $hlsResult;
            
            // Generate thumbnails
            $thumbnailResult = $this->generateThumbnails($inputFile, $videoOutputDir, $videoKey);
            $results['thumbnails'] = $thumbnailResult;
            
            // Generate preview/trailer
            if ($options['generate_preview'] ?? true) {
                $previewResult = $this->generatePreview($inputFile, $videoOutputDir, $videoKey);
                $results['preview'] = $previewResult;
            }
            
            // Update database with processing results
            $this->updateVideoRecord($videoKey, $results, $videoInfo);
            
            $this->logger->info('Video processing completed', [
                'video_key' => $videoKey,
                'results' => array_keys($results)
            ]);
            
            return [
                'success' => true,
                'video_key' => $videoKey,
                'results' => $results,
                'video_info' => $videoInfo
            ];
            
        } catch (Exception $e) {
            $this->logger->error('Video processing failed', [
                'video_key' => $videoKey,
                'input_file' => $inputFile,
                'error' => $e->getMessage()
            ]);
            
            // Update database with failed status
            $this->updateVideoStatus($videoKey, 'failed', $e->getMessage());
            
            return [
                'success' => false,
                'error' => $e->getMessage(),
                'video_key' => $videoKey
            ];
        }
    }
    
    /**
     * Queue video for background processing
     * @param string $inputFile Input file path
     * @param string $videoKey Video key
     * @param array $options Processing options
     * @return string|false Job ID or false on failure
     */
    public function queueVideoProcessing($inputFile, $videoKey, $options = [])
    {
        $jobData = [
            'video_key' => $videoKey,
            'input_file' => $inputFile,
            'output_dir' => $this->outputDir . $videoKey . '/',
            'formats' => $options['formats'] ?? ['1080p', '720p', '480p', '360p'],
            'generate_preview' => $options['generate_preview'] ?? true,
            'generate_hls' => $options['generate_hls'] ?? true
        ];
        
        $jobId = $this->queue->enqueue('VideoProcessingJob', $jobData, 'video_processing', 0, 1);
        
        if ($jobId) {
            // Update video status to queued
            $this->updateVideoStatus($videoKey, 'queued');
            
            $this->logger->info('Video queued for processing', [
                'video_key' => $videoKey,
                'job_id' => $jobId
            ]);
        }
        
        return $jobId;
    }
    
    /**
     * Get video information using FFprobe
     * @param string $inputFile Input file path
     * @return array|false Video information or false on failure
     */
    public function getVideoInfo($inputFile)
    {
        $command = sprintf(
            '%s -v quiet -print_format json -show_format -show_streams %s',
            escapeshellarg($this->ffprobePath),
            escapeshellarg($inputFile)
        );
        
        $output = shell_exec($command);
        
        if (!$output) {
            return false;
        }
        
        $info = json_decode($output, true);
        
        if (!$info) {
            return false;
        }
        
        // Extract video stream information
        $videoStream = null;
        $audioStream = null;
        
        foreach ($info['streams'] as $stream) {
            if ($stream['codec_type'] === 'video' && !$videoStream) {
                $videoStream = $stream;
            } elseif ($stream['codec_type'] === 'audio' && !$audioStream) {
                $audioStream = $stream;
            }
        }
        
        return [
            'format' => $info['format'],
            'video_stream' => $videoStream,
            'audio_stream' => $audioStream,
            'duration' => (float)($info['format']['duration'] ?? 0),
            'size' => (int)($info['format']['size'] ?? 0),
            'bitrate' => (int)($info['format']['bit_rate'] ?? 0),
            'width' => (int)($videoStream['width'] ?? 0),
            'height' => (int)($videoStream['height'] ?? 0),
            'fps' => $this->calculateFPS($videoStream),
            'codec' => $videoStream['codec_name'] ?? 'unknown'
        ];
    }
    
    /**
     * Process video to specific format
     * @param string $inputFile Input file path
     * @param string $outputDir Output directory
     * @param string $format Format (1080p, 720p, etc.)
     * @param string $videoKey Video key
     * @param array $videoInfo Video information
     * @return array Processing result
     */
    private function processFormat($inputFile, $outputDir, $format, $videoKey, $videoInfo)
    {
        $outputFile = $outputDir . $videoKey . '_' . $format . '.mp4';
        
        // Define format settings
        $formatSettings = [
            '1080p' => ['width' => 1920, 'height' => 1080, 'bitrate' => '5000k', 'audio_bitrate' => '192k'],
            '720p'  => ['width' => 1280, 'height' => 720,  'bitrate' => '2500k', 'audio_bitrate' => '128k'],
            '480p'  => ['width' => 854,  'height' => 480,  'bitrate' => '1000k', 'audio_bitrate' => '128k'],
            '360p'  => ['width' => 640,  'height' => 360,  'bitrate' => '750k',  'audio_bitrate' => '96k'],
            '240p'  => ['width' => 426,  'height' => 240,  'bitrate' => '400k',  'audio_bitrate' => '64k']
        ];
        
        if (!isset($formatSettings[$format])) {
            return [
                'success' => false,
                'error' => "Unknown format: {$format}",
                'output_file' => null
            ];
        }
        
        $settings = $formatSettings[$format];
        
        // Skip if input resolution is lower than target
        if ($videoInfo['height'] < $settings['height']) {
            $this->logger->info("Skipping {$format} - input resolution too low", [
                'input_height' => $videoInfo['height'],
                'target_height' => $settings['height']
            ]);
            
            return [
                'success' => false,
                'error' => 'Input resolution too low',
                'skipped' => true
            ];
        }
        
        // Build FFmpeg command with optimized settings
        $command = sprintf(
            '%s -i %s -c:v libx264 -preset medium -crf 23 -maxrate %s -bufsize %s -vf "scale=%d:%d:force_original_aspect_ratio=decrease,pad=%d:%d:(ow-iw)/2:(oh-ih)/2:color=black" -c:a aac -b:a %s -movflags +faststart -f mp4 %s 2>&1',
            escapeshellarg($this->ffmpegPath),
            escapeshellarg($inputFile),
            $settings['bitrate'],
            $this->calculateBufferSize($settings['bitrate']),
            $settings['width'],
            $settings['height'],
            $settings['width'],
            $settings['height'],
            $settings['audio_bitrate'],
            escapeshellarg($outputFile)
        );
        
        $this->logger->info("Processing {$format}", [
            'command' => $command,
            'output_file' => $outputFile
        ]);
        
        $startTime = microtime(true);
        $output = [];
        $returnCode = 0;
        
        exec($command, $output, $returnCode);
        
        $processingTime = microtime(true) - $startTime;
        
        if ($returnCode === 0 && file_exists($outputFile)) {
            $fileSize = filesize($outputFile);
            
            return [
                'success' => true,
                'output_file' => $outputFile,
                'file_size' => $fileSize,
                'format' => $format,
                'processing_time' => $processingTime,
                'settings' => $settings
            ];
        } else {
            return [
                'success' => false,
                'error' => 'FFmpeg processing failed',
                'return_code' => $returnCode,
                'output' => implode("\n", $output),
                'processing_time' => $processingTime
            ];
        }
    }
    
    /**
     * Generate HLS playlist for adaptive streaming
     * @param string $outputDir Output directory
     * @param string $videoKey Video key
     * @param array $formatResults Format processing results
     * @return array HLS generation result
     */
    private function generateHLS($outputDir, $videoKey, $formatResults)
    {
        $hlsDir = $outputDir . 'hls/';
        if (!is_dir($hlsDir)) {
            mkdir($hlsDir, 0755, true);
        }
        
        $masterPlaylist = "#EXTM3U\n#EXT-X-VERSION:3\n\n";
        $hlsResults = [];
        
        foreach ($formatResults as $format => $result) {
            if (!$result['success'] || !isset($result['output_file'])) {
                continue;
            }
            
            $segmentDir = $hlsDir . $format . '/';
            if (!is_dir($segmentDir)) {
                mkdir($segmentDir, 0755, true);
            }
            
            $playlistFile = $segmentDir . 'playlist.m3u8';
            
            // Generate HLS segments
            $command = sprintf(
                '%s -i %s -c copy -hls_time 10 -hls_list_size 0 -hls_segment_filename %s -f hls %s 2>&1',
                escapeshellarg($this->ffmpegPath),
                escapeshellarg($result['output_file']),
                escapeshellarg($segmentDir . 'segment_%03d.ts'),
                escapeshellarg($playlistFile)
            );
            
            $output = [];
            $returnCode = 0;
            exec($command, $output, $returnCode);
            
            if ($returnCode === 0 && file_exists($playlistFile)) {
                $hlsResults[$format] = [
                    'success' => true,
                    'playlist_file' => $playlistFile,
                    'segment_dir' => $segmentDir
                ];
                
                // Add to master playlist
                $bandwidth = $this->calculateBandwidth($result['settings']['bitrate']);
                $resolution = $result['settings']['width'] . 'x' . $result['settings']['height'];
                
                $masterPlaylist .= "#EXT-X-STREAM-INF:BANDWIDTH={$bandwidth},RESOLUTION={$resolution}\n";
                $masterPlaylist .= "{$format}/playlist.m3u8\n\n";
            } else {
                $hlsResults[$format] = [
                    'success' => false,
                    'error' => 'HLS generation failed',
                    'output' => implode("\n", $output)
                ];
            }
        }
        
        // Write master playlist
        $masterPlaylistFile = $hlsDir . 'master.m3u8';
        file_put_contents($masterPlaylistFile, $masterPlaylist);
        
        return [
            'success' => !empty($hlsResults),
            'master_playlist' => $masterPlaylistFile,
            'formats' => $hlsResults,
            'hls_dir' => $hlsDir
        ];
    }
    
    /**
     * Generate video thumbnails
     * @param string $inputFile Input file path
     * @param string $outputDir Output directory
     * @param string $videoKey Video key
     * @return array Thumbnail generation result
     */
    private function generateThumbnails($inputFile, $outputDir, $videoKey)
    {
        $thumbnailDir = $outputDir . 'thumbnails/';
        if (!is_dir($thumbnailDir)) {
            mkdir($thumbnailDir, 0755, true);
        }
        
        $thumbnails = [];
        
        // Generate main thumbnail (at 10% of duration)
        $mainThumb = $thumbnailDir . $videoKey . '_thumb.jpg';
        $command = sprintf(
            '%s -i %s -ss 00:00:10 -vframes 1 -vf "scale=320:240:force_original_aspect_ratio=decrease,pad=320:240:(ow-iw)/2:(oh-ih)/2:color=black" -q:v 2 %s 2>&1',
            escapeshellarg($this->ffmpegPath),
            escapeshellarg($inputFile),
            escapeshellarg($mainThumb)
        );
        
        exec($command, $output, $returnCode);
        
        if ($returnCode === 0 && file_exists($mainThumb)) {
            $thumbnails['main'] = [
                'success' => true,
                'file' => $mainThumb,
                'size' => filesize($mainThumb)
            ];
        }
        
        // Generate multiple thumbnails for preview
        $previewThumbs = [];
        for ($i = 1; $i <= 5; $i++) {
            $time = $i * 20; // Every 20 seconds
            $thumbFile = $thumbnailDir . $videoKey . "_preview_{$i}.jpg";
            
            $command = sprintf(
                '%s -i %s -ss %d -vframes 1 -vf "scale=160:120:force_original_aspect_ratio=decrease,pad=160:120:(ow-iw)/2:(oh-ih)/2:color=black" -q:v 2 %s 2>&1',
                escapeshellarg($this->ffmpegPath),
                escapeshellarg($inputFile),
                $time,
                escapeshellarg($thumbFile)
            );
            
            exec($command, $output, $returnCode);
            
            if ($returnCode === 0 && file_exists($thumbFile)) {
                $previewThumbs[] = $thumbFile;
            }
        }
        
        $thumbnails['preview'] = $previewThumbs;
        
        return [
            'success' => !empty($thumbnails),
            'thumbnails' => $thumbnails,
            'thumbnail_dir' => $thumbnailDir
        ];
    }
    
    /**
     * Generate video preview/trailer
     * @param string $inputFile Input file path
     * @param string $outputDir Output directory
     * @param string $videoKey Video key
     * @return array Preview generation result
     */
    private function generatePreview($inputFile, $outputDir, $videoKey)
    {
        $previewFile = $outputDir . $videoKey . '_preview.mp4';
        
        // Generate 30-second preview from multiple segments
        $command = sprintf(
            '%s -i %s -vf "select=\'between(t,10,20)+between(t,60,70)+between(t,120,130)\',setpts=N/FRAME_RATE/TB" -af "aselect=\'between(t,10,20)+between(t,60,70)+between(t,120,130)\',asetpts=N/SR/TB" -c:v libx264 -preset fast -crf 28 -c:a aac -b:a 128k -movflags +faststart %s 2>&1',
            escapeshellarg($this->ffmpegPath),
            escapeshellarg($inputFile),
            escapeshellarg($previewFile)
        );
        
        $output = [];
        $returnCode = 0;
        exec($command, $output, $returnCode);
        
        if ($returnCode === 0 && file_exists($previewFile)) {
            return [
                'success' => true,
                'preview_file' => $previewFile,
                'file_size' => filesize($previewFile)
            ];
        } else {
            return [
                'success' => false,
                'error' => 'Preview generation failed',
                'output' => implode("\n", $output)
            ];
        }
    }
    
    /**
     * Update video record in database
     * @param string $videoKey Video key
     * @param array $results Processing results
     * @param array $videoInfo Video information
     */
    private function updateVideoRecord($videoKey, $results, $videoInfo)
    {
        try {
            $db = VDatabase::getInstance();
            
            $updateData = [
                'processing_status' => 'completed',
                'processed_at' => date('Y-m-d H:i:s'),
                'duration' => $videoInfo['duration'],
                'file_size' => $videoInfo['size'],
                'video_width' => $videoInfo['width'],
                'video_height' => $videoInfo['height'],
                'video_fps' => $videoInfo['fps'],
                'video_codec' => $videoInfo['codec'],
                'available_formats' => json_encode(array_keys($results)),
                'hls_available' => isset($results['hls']) && $results['hls']['success'] ? 1 : 0,
                'thumbnails_generated' => isset($results['thumbnails']) && $results['thumbnails']['success'] ? 1 : 0
            ];
            
            $db->doUpdate('db_videofiles', 'file_key', $updateData, $videoKey);
            
            $this->logger->info('Video record updated', [
                'video_key' => $videoKey,
                'status' => 'completed'
            ]);
            
        } catch (Exception $e) {
            $this->logger->error('Failed to update video record', [
                'video_key' => $videoKey,
                'error' => $e->getMessage()
            ]);
        }
    }
    
    /**
     * Update video processing status
     * @param string $videoKey Video key
     * @param string $status Status
     * @param string $error Error message (optional)
     */
    private function updateVideoStatus($videoKey, $status, $error = null)
    {
        try {
            $db = VDatabase::getInstance();
            
            $updateData = [
                'processing_status' => $status,
                'processed_at' => date('Y-m-d H:i:s')
            ];
            
            if ($error) {
                $updateData['processing_error'] = $error;
            }
            
            $db->doUpdate('db_videofiles', 'file_key', $updateData, $videoKey);
            
        } catch (Exception $e) {
            $this->logger->error('Failed to update video status', [
                'video_key' => $videoKey,
                'status' => $status,
                'error' => $e->getMessage()
            ]);
        }
    }
    
    /**
     * Helper methods
     */
    private function findFFmpegPath()
    {
        $paths = ['/usr/bin/ffmpeg', '/usr/local/bin/ffmpeg', 'ffmpeg'];
        
        foreach ($paths as $path) {
            if (shell_exec("which {$path}") || file_exists($path)) {
                return $path;
            }
        }
        
        return 'ffmpeg'; // Fallback to PATH
    }
    
    private function findFFprobePath()
    {
        $paths = ['/usr/bin/ffprobe', '/usr/local/bin/ffprobe', 'ffprobe'];
        
        foreach ($paths as $path) {
            if (shell_exec("which {$path}") || file_exists($path)) {
                return $path;
            }
        }
        
        return 'ffprobe'; // Fallback to PATH
    }
    
    private function ensureDirectories()
    {
        $dirs = [$this->tempDir, $this->outputDir];
        
        foreach ($dirs as $dir) {
            if (!is_dir($dir)) {
                mkdir($dir, 0755, true);
            }
        }
    }
    
    private function calculateFPS($videoStream)
    {
        if (isset($videoStream['r_frame_rate'])) {
            $parts = explode('/', $videoStream['r_frame_rate']);
            if (count($parts) === 2 && $parts[1] > 0) {
                return round($parts[0] / $parts[1], 2);
            }
        }
        
        return 30; // Default FPS
    }
    
    private function calculateBufferSize($bitrate)
    {
        $bitrateNum = (int)str_replace('k', '', $bitrate);
        return ($bitrateNum * 2) . 'k'; // 2x bitrate for buffer
    }
    
    private function calculateBandwidth($bitrate)
    {
        return (int)str_replace('k', '', $bitrate) * 1000;
    }
}