<?php
declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use PDO;

final class UpdatePreflightService
{
    /** @return array{ok:bool,checks:list<array{name:string,ok:bool,critical:bool,message:string}>} */
    public function run(string $zipPath, array $manifest, string $installedVersion): array
    {
        $checks = [];
        $add = static function (string $name, bool $ok, bool $critical, string $message) use (&$checks): void {
            $checks[] = compact('name', 'ok', 'critical', 'message');
        };

        $add('Archive', is_file($zipPath) && is_readable($zipPath), true, is_readable($zipPath) ? 'Archive lisible.' : 'Archive absente ou illisible.');
        $add('PHP', version_compare(PHP_VERSION, (string)($manifest['min_php'] ?? '8.1.0'), '>='), true, 'PHP ' . PHP_VERSION . '.');
        foreach (['pdo', 'pdo_mysql', 'json', 'zip', 'hash'] as $extension) {
            $ok = extension_loaded($extension);
            $add('Extension ' . $extension, $ok, true, $ok ? 'Disponible.' : 'Extension requise absente.');
        }

        try {
            $compatibility=(new UpdateVersionCompatibility())->evaluate($installedVersion,$manifest);
            $add('Compatibilité version',$compatibility['ok'],true,'Installée : '.$compatibility['installed'].' · Acceptée : '.$compatibility['accepted'].' · '.$compatibility['reason']);
        } catch (\Throwable $e) {
            $add('Compatibilité version',false,true,$e->getMessage());
        }

        foreach ([BASE_PATH, BASE_PATH . '/app', BASE_PATH . '/storage'] as $dir) {
            $ok = is_dir($dir) && is_writable($dir);
            $add('Écriture ' . basename($dir), $ok, true, $ok ? 'Répertoire accessible en écriture.' : 'Répertoire non accessible en écriture : ' . $dir);
        }

        $free = @disk_free_space(BASE_PATH);
        $required = max(50 * 1024 * 1024, ((int)@filesize($zipPath)) * 4);
        $spaceOk = $free !== false && $free >= $required;
        $add('Espace disque', $spaceOk, true, $free === false ? 'Espace disque impossible à déterminer.' : number_format($free / 1048576, 1, ',', ' ') . ' Mo disponibles.');

        try {
            $pdo = Database::connection();
            $server = (string)$pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
            $driver = (string)$pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
            $add('Base de données', $driver === 'mysql', true, $driver . ' ' . $server . '.');
        } catch (\Throwable $e) {
            $add('Base de données', false, true, $e->getMessage());
        }

        try {
            $compliance = (new SecurityComplianceService())->auditZip($zipPath);
            $message = 'Score ' . $compliance['score'] . '/100 · ' . $compliance['files_scanned'] . ' fichiers analysés';
            if ($compliance['critical']) {
                $message .= ' · ' . implode(' | ', $compliance['critical']);
            } elseif ($compliance['warnings']) {
                $message .= ' · ' . count($compliance['warnings']) . ' avertissement(s) non bloquant(s).';
            } else {
                $message .= ' · aucune anomalie détectée.';
            }
            $add('Conformité sécurité native', $compliance['ok'], true, $message);
            foreach ($compliance['categories'] as $name => $category) {
                $add('Sécurité · ' . str_replace('_', ' ', $name), $category['status'] !== 'critical', false, $category['score'] . '/100 · ' . $category['status']);
            }
        } catch (\Throwable $e) {
            $add('Conformité sécurité native', false, true, $e->getMessage());
        }

        try {
            $regression = (new SecurityRegressionService())->auditZip($zipPath);
            $message = $regression['passed'] . '/' . $regression['total'] . ' tests réussis.';
            if ($regression['critical']) $message .= ' ' . implode(' | ', $regression['critical']);
            elseif ($regression['warnings']) $message .= ' ' . count($regression['warnings']) . ' avertissement(s) non bloquant(s).';
            $add('Tests automatiques de sécurité', $regression['ok'], true, $message);
            foreach ($regression['checks'] as $check) {
                $add('Régression · ' . $check['name'], $check['ok'], false, $check['message']);
            }
        } catch (\Throwable $e) {
            $add('Tests automatiques de sécurité', false, true, $e->getMessage());
        }

        $ok = !array_filter($checks, static fn(array $c): bool => $c['critical'] && !$c['ok']);
        return ['ok' => $ok, 'checks' => $checks];
    }
}
