<?php

declare(strict_types=1);



class VideoAsset
{
    private const BASE_DIR = '/public/uploads/videos';
    private const THUMB_ORDER_DEFAULT = 'v.views DESC';
    private const THUMB_CANDIDATES = 10;
    private const THUMB_SLICE_STEPS = [500, 4000, 32000];
    private const THUMB_SLICE_GAIN = 3;

    private static function mediaBase(?int $storageId = null): string
    {
        return MediaStorage::mediaUrl($storageId);
    }

    public static function createFolder(): string
    {
        return bin2hex(random_bytes(8));
    }

    public static function dir(string $folder): string
    {
        return APP_ROOT . self::BASE_DIR . '/' . $folder;
    }

    public static function url(string $folder, string $type, ?int $storageId = null): string
    {
        $map = [
            'thumb'       => 'thumb.jpg',
            'thumb_small' => 'thumb_small.jpg',
            'preview'     => 'preview.mp4',
        ];
        $file = $map[$type] ?? $type;
        $path = 'uploads/videos/' . $folder . '/' . $file;

        if ($storageId) {
            $storage = MediaStorage::get($storageId);
            if ($storage && $storage['secure_urls']) {
                return SecureMedia::signUrl($path, $storageId);
            }
        }

        return self::mediaBase($storageId) . '/' . $path;
    }

    

    public static function videoUrl(string $folder, ?int $storageId = null): string
    {
        $sources = self::getVideoSources($folder, $storageId);
        return !empty($sources) ? $sources[0]['url'] : '';
    }

    public static function video480Url(string $folder, ?int $storageId = null): string
    {
        foreach (self::getVideoSources($folder, $storageId) as $s) {
            if ($s['label'] === '480p') return $s['url'];
        }
        return '';
    }

    public static function getVideoSources(string $folder, ?int $storageId = null, ?int $sourceHeight = null): array
    {
        $dir = self::dir($folder);
        $isRemote = $storageId && MediaStorage::isRemote($storageId);

        $formats = [];
        try {
            $formats = TranscodeManager::getFormats();
        } catch (\Throwable) {}

        $sources = [];
        $completedTasks = [];

         
        $existingHeights = [];
        foreach (glob($dir . '/video_*p.mp4') as $file) {
            $base = basename($file);
            if (preg_match('/^video_(\d+)p\.mp4$/', $base, $m) && filesize($file) > 1000) {
                $h = (int)$m[1];
                $sources[] = ['file' => $base, 'label' => $h . 'p', 'height' => $h, 'url' => self::buildFileUrl($folder, $base, $storageId)];
                $existingHeights[] = $h;
            }
        }

         
        if ($isRemote) {
             
             
             
             
            $vid = $vid ?? Database::fetchOne('SELECT id, source_height FROM videos WHERE video_folder = ? LIMIT 1', [$folder]);
            if ($sourceHeight === null) {
                $sourceHeight = (int)($vid['source_height'] ?? 0);
            }

            $completedTasks = [];
            try {
                if ($vid) {
                    $videoId = (int)($vid['id'] ?? 0);
                    $rows = Database::fetchAll("SELECT task FROM transcode_jobs WHERE video_id = ? AND status = 'completed'", [$videoId]);
                    $completedTasks = array_column($rows, 'task');
                }
            } catch (\Throwable) {}

             
            if ($sourceHeight > 0 && !in_array($sourceHeight, $existingHeights, true) && in_array('source', $completedTasks, true)) {
                $srcFile = "video_{$sourceHeight}p.mp4";
                $sources[] = ['file' => $srcFile, 'label' => $sourceHeight . 'p', 'height' => $sourceHeight, 'url' => self::buildFileUrl($folder, $srcFile, $storageId)];
                $existingHeights[] = $sourceHeight;
            }

             
            $sorted = $formats;
            uasort($sorted, fn($a, $b) => $b['h'] - $a['h']);
            foreach ($sorted as $task => $fmt) {
                if (!in_array($fmt['h'], $existingHeights, true) && in_array($task, $completedTasks, true)) {
                    $sources[] = ['file' => $fmt['file'], 'label' => $fmt['h'] . 'p', 'height' => $fmt['h'], 'url' => self::buildFileUrl($folder, $fmt['file'], $storageId)];
                    $existingHeights[] = $fmt['h'];
                }
            }
        }

         
         
         
        if (!$isRemote) {
            $srcH = $sourceHeight ?? 0;
            if ($srcH > 0 && (empty($existingHeights) || max($existingHeights) < $srcH)) {
                foreach (['video.mp4', 'video.webm', 'video.ogg'] as $srcFile) {
                    if (is_file($dir . '/' . $srcFile) && filesize($dir . '/' . $srcFile) > 1000) {
                        $sources[] = ['file' => $srcFile, 'label' => $srcH . 'p', 'height' => $srcH, 'url' => self::buildFileUrl($folder, $srcFile, $storageId)];
                        break;
                    }
                }
            }
        }

         
         
        if (empty($sources)) {
            $hasCompletedJobs = !empty($completedTasks);
            foreach (['video.mp4', 'video.webm', 'video.ogg'] as $srcFile) {
                $srcPath = $dir . '/' . $srcFile;
                $existsLocally = file_exists($srcPath) && filesize($srcPath) > 1000;
                 
                $existsRemote = $isRemote && !$hasCompletedJobs;
                if ($existsLocally || $existsRemote) {
                    $srcH = $sourceHeight ?: 1080;
                    $sources[] = ['file' => $srcFile, 'label' => $srcH . 'p', 'height' => $srcH, 'url' => self::buildFileUrl($folder, $srcFile, $storageId)];
                    break;
                }
            }
        }

         
        usort($sources, fn($a, $b) => $b['height'] - $a['height']);

        return $sources;
    }

    private static function buildFileUrl(string $folder, string $file, ?int $storageId): string
    {
        $path = 'uploads/videos/' . $folder . '/' . $file;
        if ($storageId) {
            $storage = MediaStorage::get($storageId);
            if ($storage && $storage['secure_urls']) return SecureMedia::signUrl($path, $storageId);
        }
        return self::mediaBase($storageId) . '/' . $path;
    }

    

    public static function saveThumb(string $folder, string $tmpFile, string $type = 'thumb', ?int $storageId = null): bool
    {
        $dir = self::dir($folder);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        $filename = $type === 'thumb_small' ? 'thumb_small.jpg' : 'thumb.jpg';
        $dest = $dir . '/' . $filename;
        $ok = move_uploaded_file($tmpFile, $dest) || copy($tmpFile, $dest);
        $sid = $storageId ?? MediaStorage::defaultId();
        if ($ok && $sid && MediaStorage::isRemote($sid)) {
            if (MediaStorage::put($dest, 'uploads/videos/' . $folder . '/' . $filename, $sid)) {
                @unlink($dest);  
            }
        }
        return $ok;
    }

    

    public static function saveVideo(string $folder, string $tmpFile, string $mime = 'video/mp4', ?int $storageId = null): bool
    {
        $dir = self::dir($folder);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        $extMap = ['video/mp4' => 'mp4', 'video/webm' => 'webm', 'video/ogg' => 'ogg'];
        $ext = $extMap[$mime] ?? 'mp4';
        $filename = 'video.' . $ext;
        $dest = $dir . '/' . $filename;
        $ok = move_uploaded_file($tmpFile, $dest) || copy($tmpFile, $dest);
        if (!$ok) return false;

        $sid = $storageId ?? MediaStorage::defaultId();
        if ($sid && MediaStorage::isRemote($sid)) {
            if (!MediaStorage::put($dest, 'uploads/videos/' . $folder . '/' . $filename, $sid)) {
                error_log("VideoAsset::saveVideo failed to upload {$filename} to storage #{$sid}");
                return false;
            }
        }
        return true;
    }

    

    public static function saveRaw(string $folder, string $filename, string $data, ?int $storageId = null): bool
    {
        if (!preg_match('/^[a-zA-Z0-9_.-]+$/', $filename) || str_contains($filename, '..')) {
            return false;
        }
        $dir = self::dir($folder);
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        $localPath = $dir . '/' . $filename;
        if (file_put_contents($localPath, $data) === false) {
            return false;
        }

        $sid = $storageId ?? MediaStorage::defaultId();
        if ($sid && MediaStorage::isRemote($sid)) {
            if (!MediaStorage::put($localPath, 'uploads/videos/' . $folder . '/' . $filename, $sid)) {
                error_log("VideoAsset::saveRaw failed to upload {$filename} to storage #{$sid}");
                return false;
            }
             
             
             
             
            if (!preg_match('/^video(_\d+p)?\.(mp4|webm|ogg)$/', $filename)) {
                @unlink($localPath);
            }
        }

        return true;
    }

    

    public static function purgeLocalFolder(string $folder): void
    {
        if ($folder === '' || !preg_match('/^[a-f0-9]+$/i', $folder)) {
            return;
        }
        $dir = self::dir($folder);
        if (!is_dir($dir)) return;
        $realDir = realpath($dir);
        $baseDir = realpath(APP_ROOT . self::BASE_DIR);
        if ($realDir === false || $baseDir === false || !str_starts_with($realDir, $baseDir . '/')) return;
        foreach (glob($dir . '/*') ?: [] as $f) {
            if (is_file($f)) @unlink($f);
        }
        @rmdir($dir);
    }

    

    public static function deleteFolder(string $folder, ?int $storageId = null): void
    {
        if ($folder === '' || !preg_match('/^[a-f0-9]+$/i', $folder)) {
            return;
        }

        $sid = $storageId ?? MediaStorage::defaultId();
        if ($sid && MediaStorage::isRemote($sid)) {
            MediaStorage::deleteRemoteFolder($folder, $sid);
        }

        self::purgeLocalFolder($folder);
    }
    public static function thumbUrl(array $video, bool $preferSmall = false): string
    {
        $folder = $video['video_folder'] ?? '';
        $sid = isset($video['storage_id']) ? (int)$video['storage_id'] : null;

        if ($folder !== '') {
            if ($preferSmall && file_exists(self::dir($folder) . '/thumb_small.jpg')) {
                return self::url($folder, 'thumb_small', $sid);
            }
            if (file_exists(self::dir($folder) . '/thumb.jpg')) {
                return self::url($folder, 'thumb', $sid);
            }
            if ($sid && MediaStorage::isRemote($sid)) {
                return self::url($folder, $preferSmall ? 'thumb_small' : 'thumb', $sid);
            }
        }

        $thumb = $video['thumbnail'] ?? '';
        if ($thumb !== '' && (str_starts_with($thumb, 'http://') || str_starts_with($thumb, 'https://'))) {
            return $thumb;
        }

        return '';
    }

    

    public static function previewUrl(array $video): string
    {
        $folder = $video['video_folder'] ?? '';
        $sid = isset($video['storage_id']) ? (int)$video['storage_id'] : null;
        if ($folder !== '') {
            if (file_exists(self::dir($folder) . '/preview.mp4')) {
                return self::url($folder, 'preview', $sid);
            }
             
             
             
             
             
            if ($sid && MediaStorage::isRemote($sid)) {
                return self::url($folder, 'preview', $sid);
            }
        }
        return '';
    }

    public static function timelineVttUrl(array $video): string
    {
        $folder = $video['video_folder'] ?? '';
        $sid = isset($video['storage_id']) ? (int)$video['storage_id'] : null;
        if ($folder !== '') {
            if (file_exists(self::dir($folder) . '/timeline.vtt')) {
                return self::url($folder, 'timeline.vtt', $sid);
            }
             
             
            if ($sid && MediaStorage::isRemote($sid) && !empty($video['timeline_ready'])) {
                return self::url($folder, 'timeline.vtt', $sid);
            }
        }
        return '';
    }

    

    public static function assignUniqueThumbFolders(
        array &$items,
        string $joinTable,
        string $fkColumn,
        string $manualField,
        string $orderBy = 'v.views DESC'
    ): void {
         
         
        if (class_exists('HookSystem')) {
            $orderBy = HookSystem::applyFilter('entity.thumb_order', $orderBy);
        }
        $needsFolder = [];
        $libraryShare = [];
        foreach ($items as $item) {
            if (empty($item[$manualField])) {
                $id = (int) $item['id'];
                $needsFolder[$id] = true;
                $libraryShare[$id] = (int) ($item['video_count'] ?? 0);
            }
        }
        $needsFolder = array_keys($needsFolder);

        $candidates = [];
        if (!empty($needsFolder)) {
            if ($orderBy === self::THUMB_ORDER_DEFAULT) {
                $pending = $needsFolder;
                foreach (self::THUMB_SLICE_STEPS as $sliceSize) {
                    $worthIt = [];
                    foreach ($pending as $id) {
                        if ($libraryShare[$id] > $sliceSize * self::THUMB_SLICE_GAIN) {
                            $worthIt[] = $id;
                        }
                    }
                    if (empty($worthIt)) {
                        break;
                    }
                    $ranked = self::thumbRowsInTopSlice($joinTable, $fkColumn, $orderBy, $worthIt, $sliceSize);
                    foreach ($worthIt as $id) {
                        if (count($ranked[$id] ?? []) >= self::THUMB_CANDIDATES) {
                            $candidates[$id] = self::thumbCandidates($ranked[$id]);
                        }
                    }
                    $pending = array_values(array_diff($pending, array_keys($candidates)));
                }
            }

            $exact = array_values(array_diff($needsFolder, array_keys($candidates)));
            if (!empty($exact)) {
                foreach (self::thumbRowsForEntities($joinTable, $fkColumn, $orderBy, $exact) as $eid => $rows) {
                    $candidates[$eid] = self::thumbCandidates($rows);
                }
            }
        }

        $usedKeys = [];
        foreach ($items as &$item) {
            $item['best_video_folder'] = null;
            $item['best_video_storage_id'] = null;
            $item['best_video_thumbnail'] = null;
            if (!empty($item[$manualField])) {
                continue;
            }
            $entityId = (int) $item['id'];
            if (!isset($candidates[$entityId])) {
                continue;
            }
            foreach ($candidates[$entityId] as $candidate) {
                if (!isset($usedKeys[$candidate['key']])) {
                    $item['best_video_folder'] = $candidate['folder'] !== '' ? $candidate['folder'] : null;
                    $item['best_video_storage_id'] = $candidate['storage_id'];
                    $item['best_video_thumbnail'] = $candidate['thumbnail'] !== '' ? $candidate['thumbnail'] : null;
                    $usedKeys[$candidate['key']] = true;
                    break;
                }
            }
        }
        unset($item);
    }



    private static function thumbRowsInTopSlice(
        string $joinTable,
        string $fkColumn,
        string $orderBy,
        array $entityIds,
        int $sliceSize
    ): array {
        $placeholders = implode(',', array_fill(0, count($entityIds), '?'));
        return self::groupThumbRows(Database::fetchAll(
            "SELECT entity_id, video_folder, storage_id, thumbnail FROM (
                 SELECT jt.{$fkColumn} AS entity_id, v.video_folder, v.storage_id, v.thumbnail,
                        ROW_NUMBER() OVER (PARTITION BY jt.{$fkColumn} ORDER BY {$orderBy}) AS rn
                 FROM (
                     SELECT v.id FROM videos v
                     WHERE v.status = 'published'
                     ORDER BY {$orderBy}
                     LIMIT " . (int) $sliceSize . "
                 ) slice
                 JOIN {$joinTable} jt ON jt.video_id = slice.id AND jt.{$fkColumn} IN ({$placeholders})
                 JOIN videos v ON v.id = slice.id
                 WHERE ((v.video_folder IS NOT NULL AND v.video_folder != '')
                        OR (v.thumbnail IS NOT NULL AND v.thumbnail != ''))
             ) ranked
             WHERE ranked.rn <= " . self::THUMB_CANDIDATES . "
             ORDER BY ranked.entity_id, ranked.rn",
            $entityIds
        ));
    }

    private static function thumbRowsForEntities(
        string $joinTable,
        string $fkColumn,
        string $orderBy,
        array $entityIds
    ): array {
        $placeholders = implode(',', array_fill(0, count($entityIds), '?'));
        return self::groupThumbRows(Database::fetchAll(
            "SELECT entity_id, video_folder, storage_id, thumbnail FROM (
                 SELECT jt.{$fkColumn} AS entity_id, v.video_folder, v.storage_id, v.thumbnail,
                        ROW_NUMBER() OVER (PARTITION BY jt.{$fkColumn} ORDER BY {$orderBy}) AS rn
                 FROM {$joinTable} jt
                 JOIN videos v ON jt.video_id = v.id
                 WHERE jt.{$fkColumn} IN ({$placeholders})
                   AND v.status = 'published'
                   AND ((v.video_folder IS NOT NULL AND v.video_folder != '')
                        OR (v.thumbnail IS NOT NULL AND v.thumbnail != ''))
             ) ranked
             WHERE ranked.rn <= " . self::THUMB_CANDIDATES . "
             ORDER BY ranked.entity_id, ranked.rn",
            $entityIds
        ));
    }

    private static function groupThumbRows(array $rows): array
    {
        $grouped = [];
        foreach ($rows as $row) {
            $grouped[(int) $row['entity_id']][] = $row;
        }
        return $grouped;
    }

    private static function thumbCandidates(array $rows): array
    {
        $candidates = [];
        $seen = [];
        foreach ($rows as $row) {
            $folder = (string) ($row['video_folder'] ?? '');
            $thumb = (string) ($row['thumbnail'] ?? '');
            $key = $folder !== '' ? 'f:' . $folder : ($thumb !== '' ? 't:' . $thumb : '');
            if ($key === '' || isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $candidates[] = [
                'key' => $key,
                'folder' => $folder,
                'storage_id' => $row['storage_id'] ? (int) $row['storage_id'] : null,
                'thumbnail' => $thumb,
            ];
        }
        return $candidates;
    }



    public static function thumbUrlFromFolder(string $folder, bool $preferSmall = false, ?int $storageId = null): string
    {
        if ($folder === '') {
            return '';
        }
        if ($preferSmall) {
            $smallPath = self::dir($folder) . '/thumb_small.jpg';
            if (file_exists($smallPath)) {
                return self::url($folder, 'thumb_small', $storageId);
            }
        }
        $thumbPath = self::dir($folder) . '/thumb.jpg';
        if (file_exists($thumbPath)) {
            return self::url($folder, 'thumb', $storageId);
        }
        if ($storageId && MediaStorage::isRemote($storageId)) {
            return self::url($folder, $preferSmall ? 'thumb_small' : 'thumb', $storageId);
        }
        return '';
    }
}
