<?php
declare(strict_types=1);

namespace App\Services;

use RuntimeException;
use ZipArchive;

final class SecurityRegressionService
{
    /** @return array{ok:bool,passed:int,total:int,critical:list<string>,warnings:list<string>,checks:list<array{name:string,ok:bool,critical:bool,message:string}>} */
    public function auditZip(string $zipPath): array
    {
        if (!is_file($zipPath)) throw new RuntimeException('Package introuvable pour les tests de non-régression.');
        $zip = new ZipArchive();
        if ($zip->open($zipPath) !== true) throw new RuntimeException('Package illisible pour les tests de non-régression.');
        $overlay = [];
        for ($i=0; $i<$zip->numFiles; $i++) {
            $name = str_replace('\\','/',(string)$zip->getNameIndex($i));
            if (!str_starts_with($name,'payload/') || str_ends_with($name,'/')) continue;
            $content = $zip->getFromIndex($i);
            if ($content !== false) $overlay[substr($name,8)] = $content;
        }
        $zip->close();
        return $this->auditOverlay($overlay, defined('BASE_PATH') ? BASE_PATH : dirname(__DIR__,2));
    }

    /** @param array<string,string> $overlay */
    public function auditOverlay(array $overlay, string $basePath): array
    {
        $checks=[]; $critical=[]; $warnings=[];
        $read = static function(string $path) use ($overlay,$basePath): string {
            if (array_key_exists($path,$overlay)) return $overlay[$path];
            $full=rtrim($basePath,'/\\').DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$path);
            return is_file($full) ? (string)file_get_contents($full) : '';
        };
        $add = static function(string $name,bool $ok,bool $isCritical,string $message) use (&$checks,&$critical,&$warnings): void {
            $checks[]=['name'=>$name,'ok'=>$ok,'critical'=>$isCritical,'message'=>$message];
            if (!$ok) { if ($isCritical) $critical[]=$message; else $warnings[]=$message; }
        };

        $router=$read('app/Core/Router.php');
        $add('Routeur fermé par défaut', str_contains($router,'__route_policy_missing__') && str_contains($router,'RouteAuthorization::policy'), true, 'Le routeur doit refuser toute route privée sans politique native.');

        $auth=$read('app/Core/Auth.php');
        $add('Rotation de session', str_contains($auth,'session_regenerate_id(true)'), true, 'La connexion doit renouveler l’identifiant de session.');

        $upload=$read('app/Security/SecureFileService.php');
        $add('Téléversements sécurisés', str_contains($upload,'finfo') && str_contains($upload,'realpath') && str_contains($upload,'hash_file'), true, 'SecureFileService doit vérifier MIME, confinement et empreinte.');

        $backup=$read('app/Security/BackupArchiveCrypto.php');
        $add('Sauvegardes chiffrées', str_contains($backup,'sodium_crypto_secretstream_xchacha20poly1305'), true, 'Les sauvegardes doivent rester chiffrées et authentifiées.');

        $updates=$read('app/Services/UpdatePackageService.php');
        $add('Packages signés', str_contains($updates,'sodium_crypto_sign_verify_detached') && str_contains($updates,'UpdatePreflightService'), true, 'Le Centre de mises à jour doit vérifier Ed25519 et exécuter le préflight.');

        $preflight=$read('app/Services/UpdatePreflightService.php');
        $add('Conformité avant installation', str_contains($preflight,'SecurityComplianceService'), true, 'Le préflight doit exécuter la conformité native.');
        $add('Non-régression avant installation', str_contains($preflight,'SecurityRegressionService'), true, 'Le préflight doit exécuter les tests automatiques de sécurité.');

        $builder=$read('tools/build_signed_update.php');
        foreach (['audit_secrets.php','audit_sql_security.php','audit_offensive_security.php','audit_package_compliance.php','run_security_regression.php'] as $tool) {
            $add('Signature · '.$tool, str_contains($builder,$tool), true, 'Le générateur signé doit exécuter '.$tool.'.');
        }

        $routeSource=$read('routes/web.php');
        $policy=$read('app/Security/RouteAuthorization.php');
        if ($routeSource !== '' && $policy !== '') {
            preg_match_all('/\$router->(?:get|post|put|patch|delete)\s*\(\s*[\'\"]([^\'\"]+)/i',$routeSource,$m);
            $uncovered=[];
            foreach(array_unique($m[1]??[]) as $path) {
                if (str_contains($path,'{')) continue;
                if (!str_contains($policy,$path)) $uncovered[]=$path;
            }
            $add('Couverture déclarative des routes', count($uncovered)===0, false, $uncovered ? 'Routes à vérifier : '.implode(', ',array_slice($uncovered,0,8)) : 'Toutes les routes statiques sont référencées par la politique.');
        }

        $passed=count(array_filter($checks,static fn(array $c):bool=>$c['ok']));
        return ['ok'=>$critical===[],'passed'=>$passed,'total'=>count($checks),'critical'=>$critical,'warnings'=>$warnings,'checks'=>$checks];
    }
}
