<?php
namespace App;

/**
 * بررسی سلامت محیط اجرا — برای دستور /diag در تلگرام.
 *
 * روی هاست اشتراکی بدون SSH هیچ راهی برای دیدن این‌ها نیست، و چند موردشان
 * (نبود افزونه، غیرفعال بودن exec، پر شدن صف) باعث می‌شوند بات بی‌صدا کار
 * نکند بدون اینکه خطایی به کاربر برسد.
 */
class Health {

    /** افزونه‌های لازم: composer.json پروژه + نیازمندی‌های PhpSpreadsheet. */
    private const REQUIRED_EXT = [
        'json'      => 'خواندن خروجی JSON تلگرام',
        'curl'      => 'تماس با تلگرام و زحل',
        'mbstring'  => 'پردازش متن فارسی',
        'gd'        => 'ساخت تصویر گزارش نهایی',
        'zip'       => 'خواندن/نوشتن فایل اکسل',
        'zlib'      => 'فشرده‌سازی اکسل',
        'dom'       => 'PhpSpreadsheet',
        'libxml'    => 'PhpSpreadsheet',
        'simplexml' => 'PhpSpreadsheet',
        'xml'       => 'PhpSpreadsheet',
        'xmlreader' => 'PhpSpreadsheet',
        'xmlwriter' => 'PhpSpreadsheet',
        'iconv'     => 'PhpSpreadsheet',
        'ctype'     => 'PhpSpreadsheet',
        'fileinfo'  => 'تشخیص نوع فایل اکسل (بدون آن IOFactory می‌تواند fatal بدهد)',
    ];

    public static function report(): array {
        return [
            'php'    => self::php(),
            'ext'    => self::extensions(),
            'funcs'  => self::functions(),
            'dirs'   => self::dirs(),
            'queue'  => self::queue(),
            'assets' => self::assets(),
        ];
    }

    private static function php(): array {
        return [
            'version'     => PHP_VERSION,
            'version_ok'  => version_compare(PHP_VERSION, '8.0.0', '>='),
            'sapi'        => PHP_SAPI,
            'memory'      => ini_get('memory_limit'),
            'max_exec'    => ini_get('max_execution_time'),
            'upload_max'  => ini_get('upload_max_filesize'),
        ];
    }

    private static function extensions(): array {
        $missing = [];
        foreach (self::REQUIRED_EXT as $ext => $why) {
            if (!extension_loaded($ext)) $missing[$ext] = $why;
        }
        return ['missing' => $missing, 'total' => count(self::REQUIRED_EXT)];
    }

    /** یک تابع هم موجود باشد و هم در disable_functions هاست نباشد. */
    private static function usable(string $f): bool {
        static $disabled = null;
        if ($disabled === null) {
            $disabled = array_map('trim', explode(',', (string)ini_get('disable_functions')));
        }
        return function_exists($f) && !in_array($f, $disabled, true);
    }

    /** آیا بات می‌تواند خودش worker را در پس‌زمینه اجرا کند؟ */
    public static function canSpawnWorker(): bool {
        return self::usable('exec') || self::usable('shell_exec');
    }

    /** آیا می‌توان پاسخ را بست و کار را در پس‌زمینه ادامه داد؟ */
    public static function canDetachRequest(): bool {
        return self::usable('litespeed_finish_request') || self::usable('fastcgi_finish_request');
    }

    /** exec/shell_exec ممکن است در disable_functions هاست بسته باشند. */
    private static function functions(): array {
        return [
            'exec'       => self::usable('exec'),
            'shell_exec' => self::usable('shell_exec'),
            'can_spawn'  => self::canSpawnWorker(),
            'can_detach' => self::canDetachRequest(),
        ];
    }

    private static function dirs(): array {
        $out = [];
        foreach (['DATA_DIR' => DATA_DIR, 'QUEUE_DIR' => QUEUE_DIR, 'CACHE_DIR' => CACHE_DIR] as $name => $p) {
            $out[$name] = ['path' => $p, 'exists' => is_dir($p), 'writable' => is_dir($p) && is_writable($p)];
        }
        $ht = rtrim(DATA_DIR, '/') . '/.htaccess';
        $out['htaccess'] = ['path' => $ht, 'exists' => is_file($ht), 'writable' => false];
        return $out;
    }

    private static function queue(): array {
        $dir     = rtrim(QUEUE_DIR, '/');
        $pending = glob($dir . '/*.json') ?: [];
        $stuck   = glob($dir . '/*.json.processing') ?: [];
        $errs    = glob($dir . '/_errors/*') ?: [];

        $oldest = null;
        foreach ($pending as $f) {
            $t = @filemtime($f);
            if ($t && ($oldest === null || $t < $oldest)) $oldest = $t;
        }

        $lastErr = '';
        if ($errs) {
            usort($errs, fn($a, $b) => (@filemtime($b) ?: 0) <=> (@filemtime($a) ?: 0));
            $lastErr = trim((string)@file_get_contents($errs[0]));
            $lastErr = mb_substr(preg_replace('/\s+/u', ' ', $lastErr), 0, 220);
        }

        return [
            'pending'      => count($pending),
            'stuck'        => count($stuck),   // rename شده ولی تمام نشده = کرش وسط کار
            'errors'       => count($errs),
            'oldest_min'   => $oldest ? (int)round((time() - $oldest) / 60) : 0,
            'last_error'   => $lastErr,
        ];
    }

    private static function assets(): array {
        $font = __DIR__ . '/../assets/fonts/DejaVuSans.ttf';
        return [
            'font_exists' => is_file($font),
            'font_bytes'  => is_file($font) ? (int)filesize($font) : 0,
        ];
    }
}
