<?php

declare(strict_types=1);

namespace Install\Controllers;

use Install\Services\DatabaseChecker;
use Install\Services\EnvGenerator;
use Install\Services\InstallationEngine;
use Install\Services\AdministratorInstaller;
use Install\Services\FinalizationService;
use Install\Services\SystemCheck;
use Throwable;

final class WizardController
{
    /** @var array<string,mixed> */
    private array $config;

    public function __construct()
    {
        $config = require INSTALL_PATH . '/config.php';
        $this->config = is_array($config) ? $config : [];
    }

    public function handle(): void
    {
        if ($this->isInstallationLocked()) {
            http_response_code(403);

            $this->render('locked', [
                'title' => 'Installation verrouillée',
                'step' => 1,
                'progress' => 0,
            ]);

            return;
        }

        $step = $this->resolveStep();

        match ($step) {
            1 => $this->welcome(),
            2 => $this->requirements(),
            3 => $this->database(),
            4 => $this->environment(),
            5 => $this->preparation(),
            6 => $this->databaseInstallation(),
            7 => $this->administrator(),
            8 => $this->finalizationPending(),
            default => $this->redirectToStep(1),
        };
    }

    private function welcome(): void
    {
        $this->render('welcome', [
            'title' => 'Bienvenue',
            'step' => 1,
            'progress' => 12,
        ]);
    }

    private function requirements(): void
    {
        $result = (new SystemCheck())->run(
            $this->config
        );

        $this->render('requirements', [
            'title' => 'Vérification du serveur',
            'step' => 2,
            'progress' => 25,
            'requirements' => $result,
        ]);
    }

    private function database(): void
    {
        $systemResult = (new SystemCheck())->run(
            $this->config
        );

        if (!$systemResult['ready']) {
            $this->redirectToStep(2);
        }

        $form = $this->databaseFormDefaults();
        $result = null;
        $error = null;

        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $form = [
                'db_host' => trim(
                    (string)($_POST['db_host'] ?? 'localhost')
                ),
                'db_port' => (int)($_POST['db_port'] ?? 3306),
                'db_database' => trim(
                    (string)($_POST['db_database'] ?? '')
                ),
                'db_username' => trim(
                    (string)($_POST['db_username'] ?? '')
                ),
                'db_password' => (string)(
                    $_POST['db_password'] ?? ''
                ),
            ];

            try {
                $result = (new DatabaseChecker())->check(
                    $form,
                    $this->config
                );

                if ($result['success']) {
                    $_SESSION['install_database'] =
                        $result['connection'];

                    $_SESSION['install_database_validated_at'] =
                        time();

                    // Une base peut avoir été supprimée puis recréée avec le
                    // même nom pendant un test d'installation. Ne jamais
                    // réutiliser l'état d'exécution conservé dans la session.
                    unset(
                        $_SESSION['install_preparation'],
                        $_SESSION['install_pending_migrations'],
                        $_SESSION['install_total_migrations'],
                        $_SESSION['install_completed_migrations'],
                        $_SESSION['install_execution_complete'],
                        $_SESSION['install_execution_summary'],
                        $_SESSION['install_administrator'],
                        $_SESSION['install_finalization']
                    );
                } else {
                    unset(
                        $_SESSION['install_database'],
                        $_SESSION['install_database_validated_at']
                    );
                }
            } catch (Throwable $exception) {
                $error = $exception->getMessage();

                unset(
                    $_SESSION['install_database'],
                    $_SESSION['install_database_validated_at']
                );
            }
        } elseif (isset($_SESSION['install_database'])) {
            $saved = $_SESSION['install_database'];

            if (is_array($saved)) {
                $form = array_replace(
                    $form,
                    [
                        'db_host' => (string)(
                            $saved['db_host'] ?? 'localhost'
                        ),
                        'db_port' => (int)(
                            $saved['db_port'] ?? 3306
                        ),
                        'db_database' => (string)(
                            $saved['db_database'] ?? ''
                        ),
                        'db_username' => (string)(
                            $saved['db_username'] ?? ''
                        ),
                        'db_password' => (string)(
                            $saved['db_password'] ?? ''
                        ),
                    ]
                );
            }
        }

        $validated = $this->isDatabaseSessionValidated();

        $this->render('database', [
            'title' => 'Connexion à la base de données',
            'step' => 3,
            'progress' => 37,
            'form' => $form,
            'databaseResult' => $result,
            'databaseError' => $error,
            'databaseValidated' => $validated,
        ]);
    }

    private function environment(): void
    {
        if (!$this->isDatabaseSessionValidated()) {
            $this->redirectToStep(3);
        }

        $form = $this->environmentDefaults();
        $result = null;
        $error = null;

        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $form = [
                'app_name' => trim(
                    (string)($_POST['app_name'] ?? 'Darlink ERP')
                ),
                'app_url' => trim(
                    (string)($_POST['app_url'] ?? '')
                ),
                'app_locale' => trim(
                    (string)($_POST['app_locale'] ?? 'fr')
                ),
                'app_timezone' => trim(
                    (string)(
                        $_POST['app_timezone']
                        ?? 'Africa/Casablanca'
                    )
                ),
            ];

            try {
                $this->validateEnvironmentForm(
                    $form
                );

                $result = (new EnvGenerator())->generate(
                    (array)$_SESSION['install_database'],
                    $form
                );

                if ($result['success']) {
                    $_SESSION['install_environment'] = [
                        'app_name' => $form['app_name'],
                        'app_url' => $form['app_url'],
                        'app_locale' => $form['app_locale'],
                        'app_timezone' => $form['app_timezone'],
                        'env_path' => $result['env_path'],
                        'backup_path' => $result['backup_path'],
                    ];

                    $_SESSION['install_environment_validated_at'] =
                        time();
                } else {
                    unset(
                        $_SESSION['install_environment'],
                        $_SESSION['install_environment_validated_at']
                    );
                }
            } catch (Throwable $exception) {
                $error = $exception->getMessage();

                unset(
                    $_SESSION['install_environment'],
                    $_SESSION['install_environment_validated_at']
                );
            }
        } elseif (isset($_SESSION['install_environment'])) {
            $saved = $_SESSION['install_environment'];

            if (is_array($saved)) {
                $form = array_replace(
                    $form,
                    [
                        'app_name' => (string)(
                            $saved['app_name'] ?? 'Darlink ERP'
                        ),
                        'app_url' => (string)(
                            $saved['app_url'] ?? ''
                        ),
                        'app_locale' => (string)(
                            $saved['app_locale'] ?? 'fr'
                        ),
                        'app_timezone' => (string)(
                            $saved['app_timezone']
                            ?? 'Africa/Casablanca'
                        ),
                    ]
                );
            }
        }

        $validated = $this->isEnvironmentSessionValidated();

        $this->render('environment', [
            'title' => 'Configuration de l’environnement',
            'step' => 4,
            'progress' => 50,
            'form' => $form,
            'environmentResult' => $result,
            'environmentError' => $error,
            'environmentValidated' => $validated,
        ]);
    }

    private function preparation(): void
    {
        if (!$this->isEnvironmentSessionValidated()) {
            $this->redirectToStep(4);
        }

        $database = (array)(
            $_SESSION['install_database'] ?? []
        );

        $environment = (array)(
            $_SESSION['install_environment'] ?? []
        );

        $report = (new InstallationEngine())->prepare(
            $this->config,
            $database,
            $environment
        );

        $confirmed = false;
        $error = null;

        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $confirmedInformation =
                isset($_POST['confirm_information']);

            $confirmedInstallation =
                isset($_POST['confirm_installation']);

            if (!$report['ready']) {
                $error =
                    'L’installation ne peut pas démarrer tant que '
                    . 'des contrôles obligatoires échouent.';
            } elseif (
                !$confirmedInformation
                || !$confirmedInstallation
            ) {
                $error =
                    'Vous devez confirmer les deux déclarations '
                    . 'avant de poursuivre.';
            } elseif (
                !empty(
                    $report['database_state']
                    ['existing_installation']
                )
                && !isset($_POST['confirm_existing'])
            ) {
                $error =
                    'Une installation existante a été détectée. '
                    . 'Confirmez que vous souhaitez poursuivre.';
            } else {
                // Toute nouvelle confirmation doit déclencher un contrôle
                // réel des migrations, même après un essai précédent.
                unset(
                    $_SESSION['install_pending_migrations'],
                    $_SESSION['install_total_migrations'],
                    $_SESSION['install_completed_migrations'],
                    $_SESSION['install_execution_complete'],
                    $_SESSION['install_execution_summary'],
                    $_SESSION['install_administrator'],
                    $_SESSION['install_finalization']
                );

                $_SESSION['install_preparation'] = [
                    'confirmed_at' => time(),
                    'report' => $report,
                ];

                $confirmed = true;
            }
        }

        if ($confirmed) {
            $this->redirectToStep(6);
        }

        $this->render('preparation', [
            'title' => 'Préparation de l’installation',
            'step' => 5,
            'progress' => 62,
            'installationReport' => $report,
            'preparationError' => $error,
        ]);
    }

    private function databaseInstallation(): void
    {
        $preparation =
            $_SESSION['install_preparation'] ?? null;

        if (!is_array($preparation)) {
            $this->redirectToStep(5);
        }

        $confirmedAt = (int)(
            $preparation['confirmed_at'] ?? 0
        );

        if (
            $confirmedAt <= 0
            || (time() - $confirmedAt) > 1800
        ) {
            unset($_SESSION['install_preparation']);
            $this->redirectToStep(5);
        }

        if (
            empty($_SESSION['install_csrf_token'])
            || !is_string(
                $_SESSION['install_csrf_token']
            )
        ) {
            $_SESSION['install_csrf_token'] =
                bin2hex(random_bytes(32));
        }

        $this->render(
            'database_installation',
            [
                'title' =>
                    'Installation de la base de données',
                'step' => 6,
                'progress' => 75,
                'preparation' => $preparation,
                'csrfToken' =>
                    $_SESSION['install_csrf_token'],
                'executionComplete' => !empty(
                    $_SESSION['install_execution_complete']
                ),
                'executionSummary' => is_array(
                    $_SESSION['install_execution_summary']
                    ?? null
                )
                    ? $_SESSION['install_execution_summary']
                    : [],
            ]
        );
    }

    private function administrator(): void
    {
        if (empty($_SESSION['install_execution_complete'])) {
            $this->redirectToStep(6);
        }

        $database = $_SESSION['install_database'] ?? null;

        if (!is_array($database)) {
            $this->redirectToStep(3);
        }

        $form = [
            'nom' => '',
            'prenom' => '',
            'login' => '',
            'email' => '',
        ];

        $result = null;
        $error = null;
        $schema = [];

        try {
            $installer = new AdministratorInstaller(
                $database
            );

            $schema = $installer->schemaInformation();

            if ($_SERVER['REQUEST_METHOD'] === 'POST') {
                $form = [
                    'nom' => trim(
                        (string)($_POST['nom'] ?? '')
                    ),
                    'prenom' => trim(
                        (string)($_POST['prenom'] ?? '')
                    ),
                    'login' => trim(
                        (string)($_POST['login'] ?? '')
                    ),
                    'email' => trim(
                        (string)($_POST['email'] ?? '')
                    ),
                ];

                $result = $installer->create([
                    ...$form,
                    'password' => (string)(
                        $_POST['password'] ?? ''
                    ),
                    'password_confirmation' =>
                        (string)(
                            $_POST['password_confirmation']
                            ?? ''
                        ),
                ]);

                $_SESSION['install_administrator'] =
                    $result;

                $_SESSION['install_administrator_created_at'] =
                    time();

                $this->redirectToStep(8);
            }
        } catch (Throwable $exception) {
            $error = $exception->getMessage();
        }

        $this->render(
            'administrator',
            [
                'title' =>
                    'Création du premier administrateur',
                'step' => 7,
                'progress' => 87,
                'form' => $form,
                'administratorResult' => $result,
                'administratorError' => $error,
                'administratorSchema' => $schema,
            ]
        );
    }

    private function finalizationPending(): void
    {
        $administrator = $_SESSION['install_administrator'] ?? null;

        if (!is_array($administrator)) {
            $this->redirectToStep(7);
        }

        $database = is_array($_SESSION['install_database'] ?? null)
            ? $_SESSION['install_database']
            : [];

        $environment = is_array($_SESSION['install_environment'] ?? null)
            ? $_SESSION['install_environment']
            : [];

        $summary = is_array($_SESSION['install_execution_summary'] ?? null)
            ? $_SESSION['install_execution_summary']
            : [];

        $error = null;

        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            try {
                $result = (new FinalizationService())->finalize(
                    $this->config,
                    $database,
                    $environment,
                    $administrator,
                    $summary
                );

                $this->clearInstallationSession();

                $this->render('installation_success', [
                    'title' => 'Installation terminée',
                    'step' => 8,
                    'progress' => 100,
                    'finalizationResult' => $result,
                ]);

                return;
            } catch (Throwable $exception) {
                $error = $exception->getMessage();
            }
        }

        $this->render('finalization', [
            'title' => 'Finalisation de l’installation',
            'step' => 8,
            'progress' => 100,
            'administrator' => $administrator,
            'summary' => $summary,
            'environment' => $environment,
            'finalizationError' => $error,
        ]);
    }

    private function clearInstallationSession(): void
    {
        foreach (array_keys($_SESSION) as $key) {
            if (str_starts_with((string)$key, 'install_')) {
                unset($_SESSION[$key]);
            }
        }

        session_regenerate_id(true);
    }

    /**
     * @return array<string,mixed>
     */
    private function databaseFormDefaults(): array
    {
        return [
            'db_host' => 'localhost',
            'db_port' => 3306,
            'db_database' => '',
            'db_username' => '',
            'db_password' => '',
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function environmentDefaults(): array
    {
        $defaultUrl = $this->detectApplicationUrl();

        return [
            'app_name' => 'Darlink ERP',
            'app_url' => $defaultUrl,
            'app_locale' => (string)(
                $this->config['default_locale'] ?? 'fr'
            ),
            'app_timezone' => (string)(
                $this->config['default_timezone']
                ?? 'Africa/Casablanca'
            ),
        ];
    }

    /**
     * @param array<string,mixed> $form
     */
    private function validateEnvironmentForm(
        array $form
    ): void {
        if (
            trim(
                (string)($form['app_name'] ?? '')
            ) === ''
        ) {
            throw new \InvalidArgumentException(
                'Le nom de l’application est obligatoire.'
            );
        }

        $appUrl = trim(
            (string)($form['app_url'] ?? '')
        );

        if (
            $appUrl === ''
            || filter_var(
                $appUrl,
                FILTER_VALIDATE_URL
            ) === false
        ) {
            throw new \InvalidArgumentException(
                'L’adresse de l’application est invalide.'
            );
        }

        if (
            trim(
                (string)($form['app_locale'] ?? '')
            ) === ''
        ) {
            throw new \InvalidArgumentException(
                'La langue est obligatoire.'
            );
        }

        $timezone = trim(
            (string)($form['app_timezone'] ?? '')
        );

        if (
            $timezone === ''
            || !in_array(
                $timezone,
                timezone_identifiers_list(),
                true
            )
        ) {
            throw new \InvalidArgumentException(
                'Le fuseau horaire est invalide.'
            );
        }
    }

    private function detectApplicationUrl(): string
    {
        $https =
            !empty($_SERVER['HTTPS'])
            && $_SERVER['HTTPS'] !== 'off';

        $scheme = $https
            ? 'https'
            : 'http';

        $host = trim(
            (string)($_SERVER['HTTP_HOST'] ?? 'localhost')
        );

        $scriptName = str_replace(
            '\\',
            '/',
            (string)($_SERVER['SCRIPT_NAME'] ?? '/install/index.php')
        );

        $installPosition = strrpos(
            $scriptName,
            '/install/'
        );

        $basePath = $installPosition !== false
            ? substr(
                $scriptName,
                0,
                $installPosition
            )
            : dirname(
                dirname($scriptName)
            );

        $basePath = rtrim(
            str_replace('\\', '/', $basePath),
            '/'
        );

        return $scheme
            . '://'
            . $host
            . $basePath
            . '/public';
    }

    private function isDatabaseSessionValidated(): bool
    {
        $database = $_SESSION['install_database'] ?? null;
        $validatedAt = (int)(
            $_SESSION['install_database_validated_at'] ?? 0
        );

        if (!is_array($database) || $validatedAt <= 0) {
            return false;
        }

        return (time() - $validatedAt) <= 1800;
    }

    private function isEnvironmentSessionValidated(): bool
    {
        $environment =
            $_SESSION['install_environment'] ?? null;

        $validatedAt = (int)(
            $_SESSION['install_environment_validated_at'] ?? 0
        );

        if (
            !is_array($environment)
            || $validatedAt <= 0
        ) {
            return false;
        }

        return (time() - $validatedAt) <= 1800;
    }

    private function resolveStep(): int
    {
        $step = (int)($_GET['step'] ?? 1);

        return $step >= 1
            && $step <= (int)($this->config['total_steps'] ?? 8)
                ? $step
                : 1;
    }

    /**
     * @param array<string,mixed> $data
     */
    private function render(
        string $view,
        array $data = []
    ): void {
        $viewFile = INSTALL_PATH
            . DIRECTORY_SEPARATOR
            . 'Views'
            . DIRECTORY_SEPARATOR
            . $view
            . '.php';

        if (!is_file($viewFile)) {
            throw new \RuntimeException(
                'Vue d’installation introuvable : '
                . $view
            );
        }

        $config = $this->config;
        extract($data, EXTR_SKIP);

        require INSTALL_PATH
            . DIRECTORY_SEPARATOR
            . 'Views'
            . DIRECTORY_SEPARATOR
            . 'layout.php';
    }

    private function redirectToStep(
        int $step
    ): never {
        header(
            'Location: index.php?step=' . $step
        );
        exit;
    }

    private function isInstallationLocked(): bool
    {
        return is_file(
            PROJECT_PATH
            . DIRECTORY_SEPARATOR
            . 'storage'
            . DIRECTORY_SEPARATOR
            . 'install.lock'
        );
    }
}
