<?php

declare(strict_types=1);

namespace Install\Services;

final class SystemCheck
{
    /**
     * @param array<string,mixed> $config
     *
     * @return array{
     *     ready:bool,
     *     required_failures:int,
     *     recommended_warnings:int,
     *     checks:array<int,array<string,mixed>>
     * }
     */
    public function run(array $config): array
    {
        $checks = [];

        $checks[] = $this->versionCheck(
            'Version PHP',
            PHP_VERSION,
            (string)($config['minimum_php_version'] ?? '8.3.0'),
            true,
            'PHP 8.3 ou supérieur est obligatoire.'
        );

        $requiredExtensions = [
            'pdo' => 'PDO',
            'pdo_mysql' => 'PDO MySQL',
            'zip' => 'ZIP',
            'openssl' => 'OpenSSL',
            'mbstring' => 'Mbstring',
            'fileinfo' => 'Fileinfo',
            'json' => 'JSON',
        ];

        foreach ($requiredExtensions as $extension => $label) {
            $checks[] = $this->extensionCheck(
                $extension,
                $label,
                true
            );
        }

        $recommendedExtensions = [
            'gd' => 'GD',
            'curl' => 'cURL',
        ];

        foreach ($recommendedExtensions as $extension => $label) {
            $checks[] = $this->extensionCheck(
                $extension,
                $label,
                false
            );
        }

        foreach ($this->requiredDirectories() as $relativePath) {
            $checks[] = $this->directoryCheck(
                $relativePath,
                true
            );
        }

        $checks[] = $this->projectRootCheck();
        $checks[] = $this->diskSpaceCheck(
            (int)($config['minimum_free_space_bytes'] ?? 104857600)
        );

        $requiredFailures = 0;
        $recommendedWarnings = 0;

        foreach ($checks as $check) {
            if (($check['status'] ?? '') === 'success') {
                continue;
            }

            if (!empty($check['required'])) {
                $requiredFailures++;
            } else {
                $recommendedWarnings++;
            }
        }

        return [
            'ready' => $requiredFailures === 0,
            'required_failures' => $requiredFailures,
            'recommended_warnings' => $recommendedWarnings,
            'checks' => $checks,
        ];
    }

    /**
     * @return array<int,string>
     */
    private function requiredDirectories(): array
    {
        return [
            'storage',
            'storage/backups',
            'storage/documents',
            'storage/imports',
            'storage/install',
            'public/uploads',
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function versionCheck(
        string $label,
        string $currentVersion,
        string $minimumVersion,
        bool $required,
        string $help
    ): array {
        $success = version_compare(
            $currentVersion,
            $minimumVersion,
            '>='
        );

        return [
            'group' => 'Serveur',
            'label' => $label,
            'status' => $success ? 'success' : 'error',
            'value' => $currentVersion,
            'required' => $required,
            'message' => $success
                ? 'Version compatible.'
                : $help,
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function extensionCheck(
        string $extension,
        string $label,
        bool $required
    ): array {
        $loaded = extension_loaded($extension);

        return [
            'group' => 'Extensions PHP',
            'label' => $label,
            'status' => $loaded
                ? 'success'
                : ($required ? 'error' : 'warning'),
            'value' => $loaded ? 'Active' : 'Absente',
            'required' => $required,
            'message' => $loaded
                ? 'Extension disponible.'
                : (
                    $required
                        ? 'Cette extension est obligatoire.'
                        : 'Extension recommandée pour certaines fonctions.'
                ),
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function directoryCheck(
        string $relativePath,
        bool $required
    ): array {
        $absolutePath = PROJECT_PATH
            . DIRECTORY_SEPARATOR
            . str_replace(
                '/',
                DIRECTORY_SEPARATOR,
                $relativePath
            );

        $created = false;

        if (!is_dir($absolutePath)) {
            $parent = dirname($absolutePath);

            if (is_dir($parent) && is_writable($parent)) {
                $created = @mkdir(
                    $absolutePath,
                    0775,
                    true
                );
            }
        }

        $exists = is_dir($absolutePath);
        $writable = $exists && is_writable($absolutePath);
        $success = $exists && $writable;

        $message = match (true) {
            $success && $created =>
                'Dossier créé automatiquement et accessible en écriture.',
            $success =>
                'Dossier accessible en écriture.',
            !$exists =>
                'Dossier absent et impossible à créer automatiquement.',
            default =>
                'Dossier présent mais non accessible en écriture.',
        };

        return [
            'group' => 'Dossiers et permissions',
            'label' => $relativePath,
            'status' => $success ? 'success' : 'error',
            'value' => $success ? 'Écriture autorisée' : 'À corriger',
            'required' => $required,
            'message' => $message,
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function projectRootCheck(): array
    {
        $envPath = PROJECT_PATH
            . DIRECTORY_SEPARATOR
            . '.env';

        if (is_file($envPath)) {
            $success = is_writable($envPath);
            $value = $success
                ? '.env modifiable'
                : '.env non modifiable';
            $message = $success
                ? 'Le fichier .env pourra être configuré.'
                : 'Autorisez temporairement l’écriture sur le fichier .env.';
        } else {
            $success = is_writable(PROJECT_PATH);
            $value = $success
                ? 'Création .env possible'
                : 'Création .env impossible';
            $message = $success
                ? 'Le fichier .env pourra être créé.'
                : 'La racine du projet doit être accessible en écriture pendant l’installation.';
        }

        return [
            'group' => 'Dossiers et permissions',
            'label' => 'Configuration .env',
            'status' => $success ? 'success' : 'error',
            'value' => $value,
            'required' => true,
            'message' => $message,
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function diskSpaceCheck(
        int $minimumBytes
    ): array {
        $freeBytes = @disk_free_space(PROJECT_PATH);
        $known = is_float($freeBytes) || is_int($freeBytes);
        $success = !$known || $freeBytes >= $minimumBytes;

        return [
            'group' => 'Serveur',
            'label' => 'Espace disque disponible',
            'status' => $success ? 'success' : 'error',
            'value' => $known
                ? $this->formatBytes((int)$freeBytes)
                : 'Non déterminé',
            'required' => true,
            'message' => $success
                ? (
                    $known
                        ? 'Espace disponible suffisant.'
                        : 'Le serveur ne permet pas de mesurer l’espace disque.'
                )
                : 'Au moins '
                    . $this->formatBytes($minimumBytes)
                    . ' sont nécessaires.',
        ];
    }

    private function formatBytes(int $bytes): string
    {
        if ($bytes >= 1073741824) {
            return number_format(
                $bytes / 1073741824,
                2,
                ',',
                ' '
            ) . ' Go';
        }

        if ($bytes >= 1048576) {
            return number_format(
                $bytes / 1048576,
                2,
                ',',
                ' '
            ) . ' Mo';
        }

        return number_format(
            $bytes / 1024,
            2,
            ',',
            ' '
        ) . ' Ko';
    }
}
