<?php

declare(strict_types=1);

class App
{
    private static bool $booted = false;

    public static function boot(): void
    {
        if (self::$booted) {
            return;
        }

         
         
        self::$booted = true;

         
        error_reporting(E_ALL);
        ini_set('display_errors', '0');
        ini_set('log_errors', '1');
        ini_set('error_log', APP_ROOT . '/storage/logs/error.log');

         
        ini_set('session.cookie_httponly', '1');
         
         
         
         
        $isHttps = (!empty($_SERVER['HTTPS']) && strtolower((string) $_SERVER['HTTPS']) !== 'off')
            || (int) ($_SERVER['SERVER_PORT'] ?? 0) === 443
            || strtolower((string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https'
            || strtolower((string) ($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '')) === 'on';
        ini_set('session.cookie_secure', $isHttps ? '1' : '0');
        ini_set('session.cookie_samesite', 'Lax');
        ini_set('session.use_strict_mode', '1');
        ini_set('session.gc_maxlifetime', '7200');

        if (php_sapi_name() !== 'cli' && session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        foreach ($_GET as $qk => $qv) {
            if (is_array($qv)) {
                unset($_GET[$qk]);
            }
        }


        if (php_sapi_name() !== 'cli') {
            $reqPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
            if (!headers_sent()) {
                header('X-Content-Type-Options: nosniff');
                header('Referrer-Policy: strict-origin-when-cross-origin');
                 
                if (!str_contains($reqPath, '/embed/')) {
                    header('X-Frame-Options: SAMEORIGIN');
                }
                 
                 
                 
                 
                 
                 
                 
                 
                $csp = "object-src 'none'; base-uri 'self'; frame-ancestors "
                    . (str_contains($reqPath, '/embed/') ? '*' : "'self'");
                header('Content-Security-Policy: ' . $csp);
                if (str_starts_with($reqPath, '/admin') || str_starts_with($reqPath, '/install')) {
                    header('X-Robots-Tag: noindex, nofollow');
                    header('Cache-Control: no-store, private');
                }
            }

             
             
            $errPage = '<!doctype html><html lang="en"><head><meta charset="utf-8">'
                . '<meta name="viewport" content="width=device-width,initial-scale=1">'
                . '<title>Temporarily unavailable</title><style>body{margin:0;min-height:100vh;display:flex;'
                . 'align-items:center;justify-content:center;font-family:-apple-system,Segoe UI,Roboto,Arial,sans-serif;'
                . 'background:#0f172a;color:#e2e8f0}.b{text-align:center;padding:40px}h1{font-size:22px;margin:0 0 8px}'
                . 'p{color:#94a3b8;margin:0;font-size:14px}</style></head><body><div class="b">'
                . '<h1>We\'ll be right back</h1><p>Something went wrong on our end. Please try again in a moment.</p>'
                . '</div></body></html>';
             
             
             
            $adminCtx = str_starts_with($reqPath, '/admin') || str_starts_with($reqPath, '/install') || !empty($_SESSION['user_id']);
            $wantsJson = str_contains((string) ($_SERVER['HTTP_ACCEPT'] ?? ''), 'application/json')
                || strtolower((string) ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '')) === 'xmlhttprequest'
                || str_contains((string) ($_SERVER['CONTENT_TYPE'] ?? ''), 'application/json');
            $emit = function (?array $d = null) use ($errPage, $adminCtx, $wantsJson) {
                while (ob_get_level() > 0) { @ob_end_clean(); }
                $ref = date('ymd-His') . '-' . substr(bin2hex(random_bytes(3)), 0, 6);
                if (!headers_sent()) { http_response_code(500); }
                if ($wantsJson) {
                    if (!headers_sent()) { header('Content-Type: application/json; charset=utf-8'); }
                    echo ($adminCtx && $d)
                        ? (string) json_encode(['error' => 'Server error: ' . $d['message'], 'location' => str_replace(APP_ROOT, '', (string) ($d['file'] ?? '?')) . ':' . ($d['line'] ?? '?'), 'type' => $d['type'] ?? 'Error', 'reference' => $ref])
                        : (string) json_encode(['error' => 'Something went wrong on our end. Please try again in a moment.', 'reference' => $ref]);
                    return;
                }
                if (!headers_sent()) { header('Content-Type: text/html; charset=utf-8'); }
                if (!($adminCtx && $d)) { echo $errPage; return; }
                $msg = htmlspecialchars((string) $d['message'], ENT_QUOTES, 'UTF-8');
                $loc = htmlspecialchars(str_replace(APP_ROOT, '', (string) ($d['file'] ?? '?')) . ':' . ($d['line'] ?? '?'), ENT_QUOTES, 'UTF-8');
                $type = htmlspecialchars((string) ($d['type'] ?? 'Error'), ENT_QUOTES, 'UTF-8');
                echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">'
                    . '<title>Action could not be completed</title><style>body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;'
                    . 'font-family:-apple-system,Segoe UI,Roboto,Arial,sans-serif;background:#0f172a;color:#e2e8f0;padding:24px}'
                    . '.b{max-width:680px;width:100%;background:#111c33;border:1px solid #24314f;border-radius:14px;padding:30px 32px}'
                    . 'h1{font-size:20px;margin:0 0 6px;color:#fff}.s{color:#94a3b8;font-size:14px;margin:0 0 18px;line-height:1.55}'
                    . '.e{background:#0b1424;border:1px solid #ef4444;border-left:4px solid #ef4444;border-radius:8px;padding:14px 16px;'
                    . 'font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:13px;color:#fecaca;white-space:pre-wrap;word-break:break-word}'
                    . '.e b{color:#fff}.m{color:#64748b;font-size:12px;margin:14px 0 0}.a{display:inline-block;margin-top:18px;background:#3b82f6;'
                    . 'color:#fff;text-decoration:none;font-size:14px;font-weight:600;padding:9px 16px;border-radius:8px}</style></head><body><div class="b">'
                    . '<h1>This action could not be completed</h1>'
                    . '<p class="s">The server hit an error while handling your request. Because you are signed in as an administrator, the technical details are shown so you can fix or report it &mdash; nothing was hidden from you.</p>'
                    . '<div class="e"><b>' . $type . ':</b> ' . $msg . '<br>at ' . $loc . '</div>'
                    . '<p class="m">Reference ' . $ref . ' &middot; also written to storage/logs/error.log.</p>'
                    . '<a class="a" href="javascript:history.back()">&larr; Go back</a></div></body></html>';
            };
            set_exception_handler(function (\Throwable $e) use ($emit) {
                error_log('Uncaught: ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
                $emit(['message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'type' => get_class($e)]);
            });
            register_shutdown_function(function () use ($emit) {
                $err = error_get_last();
                if ($err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
                    error_log('Fatal: ' . $err['message'] . ' @ ' . $err['file'] . ':' . $err['line']);
                    $emit(['message' => $err['message'], 'file' => $err['file'], 'line' => $err['line'], 'type' => 'Fatal error']);
                }
            });
        }

         
        require_once APP_ROOT . '/src/Plugin/HookSystem.php';

         
        require_once APP_ROOT . '/src/Core/Database.php';
        require_once APP_ROOT . '/src/Core/BulkDelete.php';
        require_once APP_ROOT . '/src/Core/Router.php';
        require_once APP_ROOT . '/src/Core/Pagination.php';
        require_once APP_ROOT . '/src/Core/CronManager.php';
        require_once APP_ROOT . '/src/Core/Domain.php';
        require_once APP_ROOT . '/src/Core/LicenseToken.php';
        require_once APP_ROOT . '/src/Core/LicenseGuard.php';
        require_once APP_ROOT . '/src/Core/ImportProcessor.php';
        require_once APP_ROOT . '/src/Core/CatalogueClient.php';
        require_once APP_ROOT . '/src/Core/CatalogueProcessor.php';
        require_once APP_ROOT . '/src/Core/GalleryImportQueue.php';
        require_once APP_ROOT . '/src/Core/TranscodeManager.php';
        require_once APP_ROOT . '/src/Core/TranslateTypes.php';
        require_once APP_ROOT . '/src/Core/EntityTranslateProcessor.php';
        require_once APP_ROOT . '/src/Core/EntityTranslateAdmin.php';
        require_once APP_ROOT . '/src/Helpers/TranscodeWorkerScript.php';
        require_once APP_ROOT . '/src/Helpers/StorageMonitorScript.php';
        require_once APP_ROOT . '/src/Core/UpdateManager.php';
        require_once APP_ROOT . '/src/Core/AdminAssets.php';
        require_once APP_ROOT . '/src/Core/SeoManager.php';
        require_once APP_ROOT . '/src/Core/DashboardManager.php';
        require_once APP_ROOT . '/src/Core/AnnouncementService.php';

         
        require_once APP_ROOT . '/src/Helpers/Env.php';
        require_once APP_ROOT . '/src/Helpers/Mime.php';
        require_once APP_ROOT . '/src/Helpers/Slug.php';
        require_once APP_ROOT . '/src/Helpers/Format.php';
        require_once APP_ROOT . '/src/Helpers/Ads.php';
        require_once APP_ROOT . '/src/Helpers/Sanitizer.php';
        require_once APP_ROOT . '/src/Widgets/Widget.php';
        require_once APP_ROOT . '/src/Helpers/Shortcode.php';
        require_once APP_ROOT . '/src/Helpers/FileUpload.php';
        require_once APP_ROOT . '/src/Helpers/VideoAsset.php';
        require_once APP_ROOT . '/src/Helpers/AlbumAsset.php';
        require_once APP_ROOT . '/src/Helpers/LocalizedTags.php';
        require_once APP_ROOT . '/src/Helpers/FeedParser.php';
        require_once APP_ROOT . '/src/Core/FeedImportProcessor.php';
        require_once APP_ROOT . '/src/Helpers/SEO.php';
        require_once APP_ROOT . '/src/Helpers/Branding.php';
        require_once APP_ROOT . '/src/Helpers/Player.php';
        require_once APP_ROOT . '/src/Helpers/MediaStorage.php';
        require_once APP_ROOT . '/src/Helpers/SecureMedia.php';
        require_once APP_ROOT . '/src/Helpers/RateLimiter.php';
        require_once APP_ROOT . '/src/Helpers/Captcha.php';
        require_once APP_ROOT . '/src/Helpers/SpamGuard.php';
        require_once APP_ROOT . '/src/Helpers/TwoFactor.php';
        require_once APP_ROOT . '/src/Helpers/MailTypes.php';
        require_once APP_ROOT . '/src/Helpers/Mailer.php';
        require_once APP_ROOT . '/src/Helpers/Telegram.php';

         
        require_once APP_ROOT . '/src/Auth/Auth.php';
        require_once APP_ROOT . '/src/Auth/Gate.php';

         
        require_once APP_ROOT . '/src/Models/Setting.php';
        require_once APP_ROOT . '/src/Models/User.php';
        require_once APP_ROOT . '/src/Models/Video.php';
        require_once APP_ROOT . '/src/Models/Category.php';
        require_once APP_ROOT . '/src/Models/Tag.php';
        require_once APP_ROOT . '/src/Models/Performer.php';
        require_once APP_ROOT . '/src/Models/Channel.php';
        require_once APP_ROOT . '/src/Models/Album.php';
        require_once APP_ROOT . '/src/Models/AlbumCategory.php';
        require_once APP_ROOT . '/src/Models/AlbumTag.php';
        require_once APP_ROOT . '/src/Models/AlbumPerformer.php';
        require_once APP_ROOT . '/src/Models/AlbumChannel.php';
        require_once APP_ROOT . '/src/Models/Comment.php';
        require_once APP_ROOT . '/src/Models/Page.php';
        require_once APP_ROOT . '/src/Models/FooterConfig.php';
        require_once APP_ROOT . '/src/Models/MenuConfig.php';
        require_once APP_ROOT . '/src/Models/Favorite.php';
        require_once APP_ROOT . '/src/Models/History.php';
        require_once APP_ROOT . '/src/Models/ContactMessage.php';
        require_once APP_ROOT . '/src/Models/Report.php';

         
        require_once APP_ROOT . '/src/Plugin/PluginInterface.php';
        require_once APP_ROOT . '/src/Plugin/PluginManager.php';
        require_once APP_ROOT . '/src/Theme/ThemeManager.php';
        require_once APP_ROOT . '/src/Theme/ThemeRenderer.php';

         
        require_once APP_ROOT . '/src/Core/Translator.php';
        require_once APP_ROOT . '/src/Core/TranslationRepair.php';

         
        if (file_exists(APP_ROOT . '/config/database.php')) {
            require_once APP_ROOT . '/config/database.php';
        }

         
        if (self::isInstalled()) {
            PluginManager::bootAll();
            SeoManager::boot();
            ThemeManager::boot();
            Translator::boot();
            TranslationRepair::runOnce();
            ImportProcessor::registerCron();
            CatalogueProcessor::registerCron();
            TranscodeManager::registerCron();
            EntityTranslateProcessor::registerCron();
            FeedImportProcessor::registerCron();
            LicenseGuard::registerCron();

             
             
             
            CronManager::register('heartbeat_spawn', 30, function () {
                self::ensureHeartbeat();
            });

             
            CronManager::register('storage_health', 300, function () {
                foreach (MediaStorage::all() as $s) {
                    $result = MediaStorage::checkHealth((int)$s['id']);
                    $update = [
                        'health_status' => $result['status'],
                        'health_message' => $result['message'],
                        'disk_checked_at' => date('Y-m-d H:i:s'),
                    ];
                    if (isset($result['disk_total'])) $update['disk_total'] = $result['disk_total'];
                    if (isset($result['disk_used'])) $update['disk_used'] = $result['disk_used'];
                    Database::update('media_storages', $update, 'id = ?', [(int)$s['id']]);
                }
                MediaStorage::resetCache();
            });

             
             
             
            CronManager::register('media_local_gc', 1800, function () {
                TranscodeManager::gcLocalMedia();
            });

             
            CronManager::register('sitemap_regen', 3600, function () {
                if (!class_exists('SitemapController', false)) {
                    require_once APP_ROOT . '/src/Front/SitemapController.php';
                }
                SitemapController::cronRegenerate();
            });

             
            CronManager::register('entity_counts', 600, function () {
                Video::refreshPublishedTotals();
                Category::recalcAllCounts();
                Performer::recalcAllCounts();
                Channel::recalcAllCounts();
                Tag::recalcAllCounts();
            });

             
             
             
            CronManager::register('update_check', 90, function () {
                UpdateManager::refresh();
            });

             
             
             
            CronManager::register('gallery_import_kick', 120, function () {
                if (!class_exists('GalleryImportQueue')) return;
                $stats = GalleryImportQueue::stats();
                if (($stats['active'] ?? 0) < 1) return;
                GalleryImportQueue::recoverStalled();
                if (GalleryImportQueue::anyWorkerAlive()) return;

                $script = APP_ROOT . '/bin/gallery-import-worker.php';
                $max = GalleryImportQueue::maxWorkers();
                $want = max(1, min($max, (int) $stats['active']));

                $tried = false;
                if (is_file($script) && Env::canSpawn()) {
                    $phpBin = Env::phpCli();
                    if ($phpBin !== '' && $phpBin !== 'php') {
                        for ($slot = 0; $slot < $want; $slot++) {
                            @exec(escapeshellarg($phpBin) . ' ' . escapeshellarg($script) . ' ' . $slot . ' > /dev/null 2>&1 &');
                        }
                        $tried = true;
                    }
                }

                if ($tried && GalleryImportQueue::awaitWorker()) return;
                if ($tried) {
                    error_log('GalleryImportQueue: no CLI gallery worker could be started, falling back to loopback workers');
                }
                GalleryImportQueue::fireLoopbackWorkers($want);
            });

            CronManager::register('auto_update', 1800, function () {
                if (Setting::get('auto_update_enabled', '0') !== '1') return;
                $tzName = Setting::get('timezone', 'UTC');
                $tz = in_array($tzName, \DateTimeZone::listIdentifiers(), true) ? $tzName : 'UTC';
                $now = new \DateTime('now', new \DateTimeZone($tz));
                if ((int) $now->format('G') !== 1) return;                                    
                if (Setting::get('auto_update_last_day', '') === $now->format('Y-m-d')) return;  
                Setting::set('auto_update_last_day', $now->format('Y-m-d'));
                UpdateManager::autoApply();
            });

             
            CronManager::register('rate_limits_cleanup', 1800, function () {
                Database::query('DELETE FROM rate_limits WHERE window_start < DATE_SUB(NOW(), INTERVAL 48 HOUR) LIMIT 50000');
            });

             
            CronManager::register('bulk_delete', 5, function () {
                $activeJob = Database::fetchOne("SELECT id FROM bulk_delete_jobs WHERE status IN ('pending','processing') LIMIT 1");
                if (!$activeJob) return;
                if (!class_exists('AdminVideoController', false)) {
                    require_once APP_ROOT . '/src/Admin/VideoController.php';
                }
                AdminVideoController::spawnDeleteWorker();
            });

             
            $tz = Setting::get('timezone', 'UTC');
            if (in_array($tz, \DateTimeZone::listIdentifiers(), true)) {
                date_default_timezone_set($tz);
            }
        }
    }

    public static function isInstalled(): bool
    {
        return file_exists(APP_ROOT . '/config/installed.php');
    }

    public static function setting(string $key, mixed $default = null): mixed
    {
        if (!self::isInstalled()) {
            return $default;
        }
        return Setting::get($key, $default);
    }

    

    

    public static function ensureHeartbeat(): void
    {
        $pidFile = APP_ROOT . '/storage/tmp/heartbeat.pid';
        if (file_exists($pidFile)) {
            $pid = (int) @file_get_contents($pidFile);
            if (Env::processAlive($pid)) return;  
            @unlink($pidFile);
        }
        $script = APP_ROOT . '/bin/heartbeat.php';
        if (!is_file($script) || !function_exists('exec')) return;

        $phpBin = Env::phpCli();
        $tmpDir = APP_ROOT . '/storage/tmp';
        if (!is_dir($tmpDir)) @mkdir($tmpDir, 0755, true);
        @exec(escapeshellarg($phpBin) . ' ' . escapeshellarg($script) . ' > /dev/null 2>&1 &');
    }

    public static function selfPing(): void
    {
         
         
         
         
        $siteUrl = '';
        try { $siteUrl = rtrim((string) Setting::get('site_url', ''), '/'); } catch (\Throwable $e) {}
        if ($siteUrl !== '' && preg_match('#^https?://#i', $siteUrl)) {
            $url = $siteUrl . '/';
        } else {
            $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';
            if (!preg_match('/^[a-zA-Z0-9._-]+(:\d+)?$/', (string) $host)) { $host = 'localhost'; }
            $ssl = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (int) ($_SERVER['SERVER_PORT'] ?? 80) === 443;
            $url = ($ssl ? 'https' : 'http') . '://' . $host . '/';
        }

         
        if (function_exists('curl_init')) {
            $ch = curl_init($url);
            curl_setopt_array($ch, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => 1,
                CURLOPT_CONNECTTIMEOUT => 1,
                CURLOPT_NOBODY => true,
                CURLOPT_SSL_VERIFYPEER => true,
                CURLOPT_USERAGENT => 'TubePress-CronPing/1.0',
            ]);
            @curl_exec($ch);
            curl_close($ch);
        }
    }
}
