<?php

declare(strict_types=1);

class SecureMedia
{
    public static function signUrl(string $path, int $storageId): string
    {
        $storage = MediaStorage::get($storageId);
        if (!$storage || !$storage['secure_urls']) {
            $base = MediaStorage::mediaUrl($storageId);
            return $base . '/' . ltrim($path, '/');
        }

        $secret = $storage['secure_secret'];
        $ttl = (int)($storage['secure_ttl'] ?: 21600);
        $expires = time() + $ttl;
         
         
         
        $ip = '';
        $token = self::generateToken($secret, $path, $expires, $ip);

        return '/media/' . $token . '/' . $expires . '/' . ltrim($path, '/');
    }

    public static function generateToken(string $secret, string $path, int $expires, string $ip = ''): string
    {
        return substr(hash_hmac('sha256', $path . ':' . $expires . ':' . $ip, $secret), 0, 16);
    }

    public static function verify(string $token, string $path, int $expires, string $secret): bool
    {
        if ($expires < time()) return false;
         
         
        $ip = '';
        $expected = self::generateToken($secret, $path, $expires, $ip);
        return hash_equals($expected, $token);
    }

    public static function generateSecret(): string
    {
        return bin2hex(random_bytes(32));
    }

    public static function routes(): void
    {
         
         
        $uri = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH);
        if (is_string($uri) && str_starts_with($uri, '/media/')) {
            self::serve();
        }
    }

    public static function serve(): void
    {
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        if (!preg_match('#^/media/([a-f0-9]{16})/(\d+)/(.+)$#', $uri, $m)) {
            http_response_code(404);
            exit;
        }

        $token = $m[1];
        $expires = (int)$m[2];
        $path = $m[3];

        if ($expires < time()) {
            http_response_code(403);
            header('Cache-Control: no-store');
            echo 'Link expired.';
            exit;
        }

        $storageId = self::findStorageForPath($path);
        $storage = $storageId ? MediaStorage::get($storageId) : null;

        if (!$storage || !$storage['secure_urls']) {
            http_response_code(404);
            exit;
        }

        if (!self::verify($token, $path, $expires, $storage['secure_secret'])) {
            http_response_code(403);
            echo 'Invalid token.';
            exit;
        }

        if ($storage['driver'] === 'local') {
            $localPath = APP_ROOT . '/public/' . $path;
            if (!file_exists($localPath)) {
                http_response_code(404);
                exit;
            }

            $realPath = realpath($localPath);
            $basePath = realpath(APP_ROOT . '/public/uploads');
            if (!$realPath || !$basePath || !str_starts_with($realPath, $basePath . DIRECTORY_SEPARATOR)) {
                http_response_code(403);
                exit;
            }

            $mime = match(strtolower(pathinfo($localPath, PATHINFO_EXTENSION))) {
                'jpg', 'jpeg' => 'image/jpeg',
                'png' => 'image/png',
                'webp' => 'image/webp',
                'gif' => 'image/gif',
                'mp4' => 'video/mp4',
                'webm' => 'video/webm',
                default => 'application/octet-stream',
            };

            header('Content-Type: ' . $mime);
            header('Content-Length: ' . filesize($localPath));
            header('Cache-Control: private, max-age=' . max(0, $expires - time()));
            header('Accept-Ranges: bytes');

            if (isset($_SERVER['HTTP_RANGE']) && str_starts_with($mime, 'video/')) {
                self::serveRange($localPath, $mime);
            } else {
                readfile($localPath);
            }
        } else {
            $base = MediaStorage::mediaUrl($storageId);
            $targetUrl = $base . '/' . $path;
            header('Location: ' . $targetUrl, true, 302);
            header('Cache-Control: private, max-age=' . min(300, max(0, $expires - time())));
        }

        exit;
    }

    private static function serveRange(string $file, string $mime): void
    {
        $size = filesize($file);
        $range = $_SERVER['HTTP_RANGE'];

        if (preg_match('/bytes=-(\d+)$/', $range, $sm)) {
             
            $n = (int)$sm[1];
            $start = max(0, $size - $n);
            $end = $size - 1;
        } elseif (preg_match('/bytes=(\d+)-(\d*)/', $range, $m)) {
            $start = (int)$m[1];
            $end = $m[2] !== '' ? (int)$m[2] : $size - 1;

            if ($start > $end || $start >= $size) {
                http_response_code(416);
                header("Content-Range: bytes */{$size}");
                return;
            }
        } else {
             
            readfile($file);
            return;
        }

            $length = $end - $start + 1;
            http_response_code(206);
            header("Content-Range: bytes {$start}-{$end}/{$size}");
            header("Content-Length: {$length}");
            header("Content-Type: {$mime}");

            $fh = fopen($file, 'rb');
            fseek($fh, $start);
            $sent = 0;
            while ($sent < $length && !feof($fh)) {
                $chunk = min(8192, $length - $sent);
                echo fread($fh, $chunk);
                $sent += $chunk;
                flush();
            }
            fclose($fh);
    }

    private static function findStorageForPath(string $path): ?int
    {
        if (str_starts_with($path, 'uploads/albums/')) {
            $parts = explode('/', $path);
            if (count($parts) < 4) return null;
            $album = Database::fetchOne('SELECT storage_id FROM albums WHERE album_folder = ? LIMIT 1', [$parts[2]]);
            if ($album && $album['storage_id']) {
                return (int) $album['storage_id'];
            }
            foreach (MediaStorage::all() as $s) {
                if ($s['secure_urls'] && $s['is_default']) return (int) $s['id'];
            }
            return null;
        }

        if (!str_starts_with($path, 'uploads/videos/')) return null;

        $parts = explode('/', $path);
        if (count($parts) < 4) return null;
        $folder = $parts[2];

        $video = Database::fetchOne('SELECT storage_id FROM videos WHERE video_folder = ? LIMIT 1', [$folder]);
        if ($video && $video['storage_id']) {
            return (int)$video['storage_id'];
        }

        foreach (MediaStorage::all() as $s) {
            if ($s['secure_urls'] && $s['is_default']) return (int)$s['id'];
        }

        return null;
    }
}
