<?php
/**
 * MediaController
 *
 * Serves uploaded files stored outside the webroot (STORAGE_ROOT).
 * This is the sole access point for thesis files, covers, and annexes — they
 * are never exposed as direct filesystem paths from the web server.
 *
 * Security:
 *   - Strict character whitelist on the path parameter (no path traversal)
 *   - realpath() jail: resolved path must stay inside STORAGE_ROOT
 *   - MIME type verified against an allow-list before serving
 *   - Access-type gate for thesis files (blocks 'Interdit' access_type_id=3)
 */
class MediaController
{
    /**
     * Handle a media request. Reads $_GET['path'], validates, and streams the file.
     * Sends appropriate headers and exit() — no return value.
     */
    public function handle(): void
    {
        $requestedPath = $_GET['path'] ?? '';

        // 1. Validate path characters
        if (!preg_match('#^[a-zA-Z0-9/_\-.]+$#', $requestedPath) || $requestedPath === '') {
            http_response_code(400);
            exit;
        }

        // 2. Resolve path + storage jail
        $storageRoot = defined('STORAGE_ROOT') ? STORAGE_ROOT : '/var/www/posterg/storage';
        $fullPath    = $storageRoot . '/' . $requestedPath;

        $realStorage = realpath($storageRoot);
        $realFull    = realpath($fullPath);

        if (
            $realFull === false
            || $realStorage === false
            || strpos($realFull, $realStorage . '/') !== 0
        ) {
            http_response_code(404);
            exit;
        }

        if (!is_file($realFull)) {
            http_response_code(404);
            exit;
        }

        // 3. Visibility gate for thesis files
        if (preg_match('#^theses/#', $requestedPath)) {
            require_once APP_ROOT . '/src/Database.php';
            try {
                $mediaDb     = Database::getInstance();
                $accessTypeId = $mediaDb->getFileVisibility($requestedPath);
                if ($accessTypeId !== null && $accessTypeId === 3) {
                    http_response_code(403);
                    exit;
                }
            } catch (\Throwable $e) {
                error_log("MediaController visibility check error: " . $e->getMessage());
            }
        }

        // 4. Verify MIME type
        $finfo    = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->file($realFull);

        $allowedMimes = [
            'image/jpeg',
            'image/png',
            'image/gif',
            'application/pdf',
            'video/mp4',
            'application/zip',
            'text/vtt',       // WebVTT caption sidecar files
        ];

        // finfo may return 'text/plain' for WebVTT files on some systems;
        // re-classify by extension so we don't block them.
        if ($mimeType === 'text/plain' && strtolower(pathinfo($realFull, PATHINFO_EXTENSION)) === 'vtt') {
            $mimeType = 'text/vtt';
        }

        if (!in_array($mimeType, $allowedMimes, true)) {
            http_response_code(403);
            exit;
        }

        // 5. Send response headers
        header('Content-Type: ' . $mimeType);
        header('Content-Length: ' . filesize($realFull));
        header('X-Content-Type-Options: nosniff');

        $ext = strtolower(pathinfo($realFull, PATHINFO_EXTENSION));

        if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'], true)) {
            header('Cache-Control: public, max-age=604800');
        } elseif ($ext === 'pdf') {
            header('Cache-Control: public, max-age=86400');
            header('Content-Disposition: inline');
        } elseif ($ext === 'vtt') {
            header('Content-Type: text/vtt; charset=utf-8');
            header('Cache-Control: public, max-age=86400');
        } else {
            header('Cache-Control: private, no-store');
        }

        // 6. Stream file
        readfile($realFull);
    }
}
