<?php
declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\Controller;
use App\Core\Csrf;
use App\Core\Permission;
use App\Core\Database;
use App\Repositories\UpdatePackageRegistryRepository;
use App\Services\UpdateLockService;
use App\Services\UpdatePackageService;
use App\Security\SecureFileService;
use RuntimeException;
use Throwable;

final class UpdateCenterController extends Controller
{
    public function index(): void
    {
        Auth::requireLogin();
        Permission::require('updates.view');

        $lockService = new UpdateLockService();
        $lockState = $lockService->status();

        if ($lockState['locked'] && $lockState['stale']) {
            $lockService->forceRelease();
            $this->markInterruptedPackages(
                'Installation interrompue : verrou expiré et libéré automatiquement.'
            );
            $lockState = $lockService->status();
        } elseif (!$lockState['locked']) {
            $this->markInterruptedPackages(
                'Installation interrompue : aucun processus actif détecté.'
            );
        }

        $version = is_file(BASE_PATH . '/VERSION')
            ? trim((string) file_get_contents(BASE_PATH . '/VERSION'))
            : 'inconnue';

        $packages = (new UpdatePackageRegistryRepository())->all();

        $this->view('updates/center', [
            'pageTitle' => 'Mises à jour',
            'version' => $version,
            'packages' => $packages,
            'lockState' => $lockState,
        ]);
    }

    public function import(): void
    {
        Auth::requireLogin();
        Permission::require('updates.import');
        Permission::requireSuperAdmin();
        Auth::requireRecentAuthentication();
        Csrf::verify($_POST['_csrf'] ?? null);

        try {
            $storage = BASE_PATH . '/storage/updates/packages';
            $uploaded = SecureFileService::storeUploaded(
                $_FILES['package'] ?? [],
                $storage,
                [
                    'application/zip' => ['zip'],
                    'application/x-zip-compressed' => ['zip'],
                ],
                100 * 1024 * 1024,
                'update'
            );

            $name = $uploaded['original'];
            $stored = $uploaded['name'];
            $target = $uploaded['path'];

            $service = new UpdatePackageService();
            $manifest = $service->inspect($target);
            $preflight = $service->preflight([
                'fichier_original' => $stored,
            ]);

            if (!$preflight['ok']) {
                @unlink($target);

                $messages = array_map(
                    static fn(array $check): string =>
                        $check['name'] . ' : ' . $check['message'],
                    array_filter(
                        $preflight['checks'],
                        static fn(array $check): bool =>
                            $check['critical'] && !$check['ok']
                    )
                );

                throw new RuntimeException(
                    'Préflight bloquant : ' . implode(' | ', $messages)
                );
            }

            $checksum = hash_file('sha256', $target);

            $duplicate = Database::connection()->prepare(
                "SELECT id, statut
                 FROM cda_update_packages
                 WHERE checksum_sha256 = :checksum
                 ORDER BY id DESC
                 LIMIT 1"
            );
            $duplicate->execute(['checksum' => $checksum]);
            $previous = $duplicate->fetch();
            $duplicate->closeCursor();

            if ($previous) {
                @unlink($target);
                throw new RuntimeException(
                    'Ce fichier exact a déjà été importé (package #'
                    . (int) $previous['id']
                    . ', état '
                    . $previous['statut']
                    . ').'
                );
            }

            $summary = count(array_filter(
                $preflight['checks'],
                static fn(array $check): bool => $check['ok']
            )) . '/' . count($preflight['checks']) . ' contrôles réussis.';

            $insert = Database::connection()->prepare(
                "INSERT INTO cda_update_packages
                 (
                    nom_package,
                    fichier_original,
                    statut,
                    taille_octets,
                    checksum_sha256,
                    importe_par,
                    version_package,
                    message,
                    result_json
                 )
                 VALUES
                 (
                    :nom,
                    :fichier,
                    'pret',
                    :taille,
                    :checksum,
                    :user_id,
                    :version,
                    :message,
                    :result_json
                 )"
            );

            $insert->execute([
                'nom' => $name,
                'fichier' => $stored,
                'taille' => filesize($target) ?: 0,
                'checksum' => $checksum,
                'user_id' => (int) (Auth::user()['id'] ?? 0) ?: null,
                'version' => $manifest['version'],
                'message' => 'Package ' . (string) $manifest['package']
                    . ' contrôlé et prêt à installer. ' . $summary,
                'result_json' => json_encode(
                    [
                        'manifest' => $manifest,
                        'preflight' => $preflight,
                    ],
                    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
                ),
            ]);

            $_SESSION['success'] =
                'Package contrôlé. Vous pouvez maintenant lancer son installation.';
        } catch (Throwable $error) {
            $_SESSION['error'] = $error->getMessage();
        }

        $this->redirect('/studio/mises-a-jour');
    }

    public function install(): void
    {
        Auth::requireLogin();
        Permission::require('updates.install');
        Permission::requireSuperAdmin();
        Auth::requireRecentAuthentication();
        Csrf::verify($_POST['_csrf'] ?? null);

        try {
            $package = (new UpdatePackageRegistryRepository())->find(
                (int) ($_POST['id'] ?? 0)
            );

            if ($package === null) {
                throw new RuntimeException('Package introuvable.');
            }

            if (!in_array($package['statut'], ['pret', 'echec'], true)) {
                throw new RuntimeException(
                    'Ce package ne peut pas être installé dans son état actuel.'
                );
            }

            $manifest = (new UpdatePackageService())->install(
                $package,
                (int) (Auth::user()['id'] ?? 0)
            );

            $_SESSION['success'] =
                'Mise à jour ' . $manifest['version'] . ' installée avec succès.';
        } catch (Throwable $error) {
            $_SESSION['error'] =
                'Installation interrompue : ' . $error->getMessage();
        }

        $this->redirect('/studio/mises-a-jour');
    }

    public function unlock(): void
    {
        Auth::requireLogin();
        Permission::require('updates.unlock');
        Permission::requireSuperAdmin();
        Auth::requireRecentAuthentication();
        Csrf::verify($_POST['_csrf'] ?? null);

        try {
            $lock = new UpdateLockService();

            if (!$lock->forceRelease()) {
                throw new RuntimeException(
                    'Le verrou n’a pas pu être supprimé.'
                );
            }

            $this->markInterruptedPackages(
                'Installation interrompue puis déverrouillée manuellement.'
            );

            $_SESSION['success'] = 'Installation débloquée.';
        } catch (Throwable $error) {
            $_SESSION['error'] =
                'Déverrouillage impossible : ' . $error->getMessage();
        }

        $this->redirect('/studio/mises-a-jour');
    }

    private function markInterruptedPackages(string $message): void
    {
        Database::connection()->prepare(
            "UPDATE cda_update_packages
             SET
                statut = 'echec',
                installation_fin = COALESCE(installation_fin, NOW()),
                message = :message,
                lock_token = NULL
             WHERE statut = 'installation'"
        )->execute(['message' => $message]);
    }
}
