<?php

namespace App\Services\Install;

/**
 * Step 1 of the installer. Every check returns ['label','required','actual','passed'].
 * The controller/view render green/red rows from this array; "Next" is only
 * enabled client-side (and re-validated server-side) if allPassed() is true.
 */
class ServerRequirementChecker
{
    private const REQUIRED_PHP_VERSION = '8.0.0';

    private const REQUIRED_EXTENSIONS = [
        'intl', 'mbstring', 'json', 'curl', 'mysqli', 'zip', 'openssl', 'fileinfo',
    ];

    public function check(): array
    {
        $results = [];

        $results[] = $this->checkPhpVersion();
        $results[] = $this->checkImageLibrary();

        foreach (self::REQUIRED_EXTENSIONS as $ext) {
            $results[] = $this->checkExtension($ext);
        }

        $results[] = $this->checkIniValue('upload_max_filesize', '10M', fn ($v) => $this->toBytes($v) >= $this->toBytes('10M'));
        $results[] = $this->checkIniValue('post_max_size', '12M', fn ($v) => $this->toBytes($v) >= $this->toBytes('12M'));
        $results[] = $this->checkIniValue('memory_limit', '128M', fn ($v) => $v === '-1' || $this->toBytes($v) >= $this->toBytes('128M'));

        // Only relevant for Composer-based deployments (identified by the
        // presence of composer.json at the project root). The Composer-less
        // shared-hosting build of this package ships no composer.json at
        // all and vendors the framework directly into system/, so this
        // check would otherwise permanently and incorrectly fail there.
        $usesComposer = file_exists(ROOTPATH . 'composer.json');
        $results[] = [
            'label'    => 'Composer dependencies (vendor/)',
            'required' => $usesComposer ? 'vendor/autoload.php present' : 'Not applicable (Composer-less build)',
            'actual'   => ! $usesComposer ? 'N/A' : (file_exists(ROOTPATH . 'vendor/autoload.php') ? 'Found' : 'Missing'),
            'passed'   => ! $usesComposer || file_exists(ROOTPATH . 'vendor/autoload.php'),
        ];

        return $results;
    }

    public function allPassed(): bool
    {
        foreach ($this->check() as $row) {
            if (! $row['passed']) {
                return false;
            }
        }
        return true;
    }

    private function checkPhpVersion(): array
    {
        $actual = PHP_VERSION;
        return [
            'label'    => 'PHP Version',
            'required' => '>= ' . self::REQUIRED_PHP_VERSION,
            'actual'   => $actual,
            'passed'   => version_compare($actual, self::REQUIRED_PHP_VERSION, '>='),
        ];
    }

    private function checkExtension(string $ext): array
    {
        $loaded = extension_loaded($ext);
        return [
            'label'    => "PHP extension: {$ext}",
            'required' => 'Enabled',
            'actual'   => $loaded ? 'Enabled' : 'Not found',
            'passed'   => $loaded,
        ];
    }

    private function checkImageLibrary(): array
    {
        $ok = extension_loaded('gd') || extension_loaded('imagick');
        return [
            'label'    => 'Image library (gd or imagick)',
            'required' => 'At least one enabled',
            'actual'   => extension_loaded('gd') ? 'gd' : (extension_loaded('imagick') ? 'imagick' : 'None'),
            'passed'   => $ok,
        ];
    }

    private function checkIniValue(string $key, string $minimum, callable $comparator): array
    {
        $actual = ini_get($key);
        return [
            'label'    => "php.ini: {$key}",
            'required' => ">= {$minimum}",
            'actual'   => $actual,
            'passed'   => $comparator($actual),
        ];
    }

    private function toBytes(string $value): int
    {
        $value = trim($value);
        $last = strtolower($value[strlen($value) - 1] ?? '');
        $num = (int) $value;
        return match ($last) {
            'g' => $num * 1024 * 1024 * 1024,
            'm' => $num * 1024 * 1024,
            'k' => $num * 1024,
            default => $num,
        };
    }
}
