<?php

declare(strict_types=1);

namespace Install\Services;

final class EnvGenerator
{
    /**
     * @param array<string,mixed> $database
     * @param array<string,mixed> $settings
     *
     * @return array{
     *     success:bool,
     *     message:string,
     *     env_path:string,
     *     backup_path:?string,
     *     checks:array<int,array<string,mixed>>
     * }
     */
    public function generate(
        array $database,
        array $settings
    ): array {
        $envPath = PROJECT_PATH
            . DIRECTORY_SEPARATOR
            . '.env';

        $examplePath = PROJECT_PATH
            . DIRECTORY_SEPARATOR
            . '.env.example';

        $backupPath = null;

        if (is_file($envPath)) {
            $backupPath = $this->createBackup($envPath);
        }

        $content = is_file($examplePath)
            ? (string)file_get_contents($examplePath)
            : $this->defaultTemplate();

        if ($content === '') {
            $content = $this->defaultTemplate();
        }

        $appKey = $this->generateAppKey();

        $values = [
            'APP_NAME' => (string)($settings['app_name'] ?? 'Darlink ERP'),
            'APP_ENV' => 'production',
            'APP_DEBUG' => 'false',
            'APP_URL' => (string)($settings['app_url'] ?? ''),
            'APP_KEY' => $appKey,
            'APP_LOCALE' => (string)($settings['app_locale'] ?? 'fr'),
            'APP_TIMEZONE' => (string)($settings['app_timezone'] ?? 'Africa/Casablanca'),

            'DB_CONNECTION' => 'mysql',
            'DB_HOST' => (string)($database['db_host'] ?? 'localhost'),
            'DB_PORT' => (string)($database['db_port'] ?? 3306),

            // Format historique encore utilisé par le cœur de Darlink ERP.
            // Ces valeurs doivent rester strictement synchronisées avec
            // les variables modernes ci-dessous.
            'DB_NAME' => (string)($database['db_database'] ?? ''),
            'DB_USER' => (string)($database['db_username'] ?? ''),
            'DB_PASS' => (string)($database['db_password'] ?? ''),

            // Format moderne conservé pour la compatibilité future.
            'DB_DATABASE' => (string)($database['db_database'] ?? ''),
            'DB_USERNAME' => (string)($database['db_username'] ?? ''),
            'DB_PASSWORD' => (string)($database['db_password'] ?? ''),

            'MAIL_MAILER' => 'smtp',
            'MAIL_HOST' => '',
            'MAIL_PORT' => '587',
            'MAIL_USERNAME' => '',
            'MAIL_PASSWORD' => '',
            'MAIL_ENCRYPTION' => 'tls',
            'MAIL_FROM_ADDRESS' => '',
            'MAIL_FROM_NAME' => (string)($settings['app_name'] ?? 'Darlink ERP'),

            'SESSION_DRIVER' => 'file',
            'CACHE_DRIVER' => 'file',
        ];

        foreach ($values as $key => $value) {
            $content = $this->setEnvValue(
                $content,
                $key,
                (string)$value
            );
        }

        if (
            file_put_contents(
                $envPath,
                $content,
                LOCK_EX
            ) === false
        ) {
            throw new \RuntimeException(
                'Impossible d’écrire le fichier .env.'
            );
        }

        $checks = $this->validate(
            $envPath,
            array_keys($values)
        );

        $success = true;

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

        return [
            'success' => $success,
            'message' => $success
                ? 'Le fichier .env a été créé et validé.'
                : 'Le fichier .env a été créé, mais certains contrôles ont échoué.',
            'env_path' => $envPath,
            'backup_path' => $backupPath,
            'checks' => $checks,
        ];
    }

    private function createBackup(
        string $envPath
    ): string {
        $backupPath = PROJECT_PATH
            . DIRECTORY_SEPARATOR
            . '.env.backup-'
            . date('Ymd-His');

        if (!copy($envPath, $backupPath)) {
            throw new \RuntimeException(
                'Impossible de créer la sauvegarde du fichier .env.'
            );
        }

        return $backupPath;
    }

    private function generateAppKey(): string
    {
        return 'base64:' . base64_encode(
            random_bytes(32)
        );
    }

    private function setEnvValue(
        string $content,
        string $key,
        string $value
    ): string {
        $formattedValue = $this->quoteValue(
            $value
        );

        $pattern = '/^'
            . preg_quote($key, '/')
            . '\s*=.*$/m';

        if (preg_match($pattern, $content) === 1) {
            return (string)preg_replace(
                $pattern,
                $key . '=' . $formattedValue,
                $content,
                1
            );
        }

        $content = rtrim($content);

        if ($content !== '') {
            $content .= PHP_EOL;
        }

        return $content
            . $key
            . '='
            . $formattedValue
            . PHP_EOL;
    }

    private function quoteValue(
        string $value
    ): string {
        if ($value === '') {
            return '""';
        }

        if (
            preg_match(
                '/^[A-Za-z0-9_\/\.\-\:]+$/',
                $value
            ) === 1
        ) {
            return $value;
        }

        return '"'
            . addcslashes(
                $value,
                "\\\""
            )
            . '"';
    }

    /**
     * @param array<int,string> $requiredKeys
     *
     * @return array<int,array<string,mixed>>
     */
    private function validate(
        string $envPath,
        array $requiredKeys
    ): array {
        $checks = [];

        $exists = is_file($envPath);

        $checks[] = $this->check(
            'Fichier .env',
            $exists,
            $exists ? 'Créé' : 'Absent',
            $exists
                ? 'Le fichier de configuration existe.'
                : 'Le fichier .env est introuvable.'
        );

        $readable = $exists && is_readable($envPath);

        $checks[] = $this->check(
            'Lecture du fichier',
            $readable,
            $readable ? 'Possible' : 'Impossible',
            $readable
                ? 'Le fichier .env est lisible.'
                : 'Le fichier .env n’est pas lisible.'
        );

        if (!$readable) {
            return $checks;
        }

        $content = (string)file_get_contents(
            $envPath
        );

        foreach ($requiredKeys as $key) {
            $value = $this->readEnvValue(
                $content,
                $key
            );

            $emptyValueAllowed = in_array(
                $key,
                [
                    'DB_PASSWORD',
                    'DB_PASS',
                    'MAIL_HOST',
                    'MAIL_USERNAME',
                    'MAIL_PASSWORD',
                    'MAIL_FROM_ADDRESS',
                ],
                true
            );

            $valid = $value !== null
                && (
                    $emptyValueAllowed
                    || trim($value) !== ''
                );

            $valueLabel = match (true) {
                !$valid => 'À corriger',
                in_array($key, ['DB_PASSWORD', 'DB_PASS'], true)
                    && trim((string)$value) === '' =>
                    'Vide autorisé',
                trim((string)$value) === '' =>
                    'Non configuré',
                default =>
                    'Configuré',
            };

            $message = match (true) {
                !$valid =>
                    'Variable absente ou vide.',
                in_array($key, ['DB_PASSWORD', 'DB_PASS'], true)
                    && trim((string)$value) === '' =>
                    'Variable présente. Aucun mot de passe est une configuration valide.',
                trim((string)$value) === '' =>
                    'Variable présente et facultative.',
                default =>
                    'Variable présente.',
            };

            $checks[] = $this->check(
                $key,
                $valid,
                $valueLabel,
                $message
            );
        }

        return $checks;
    }

    private function readEnvValue(
        string $content,
        string $key
    ): ?string {
        $pattern = '/^'
            . preg_quote($key, '/')
            . '\s*=\s*(.*)$/m';

        if (
            preg_match(
                $pattern,
                $content,
                $matches
            ) !== 1
        ) {
            return null;
        }

        $value = trim(
            (string)($matches[1] ?? '')
        );

        if (
            strlen($value) >= 2
            && (
                (
                    str_starts_with($value, '"')
                    && str_ends_with($value, '"')
                )
                || (
                    str_starts_with($value, "'")
                    && str_ends_with($value, "'")
                )
            )
        ) {
            $value = substr(
                $value,
                1,
                -1
            );
        }

        return $value;
    }

    /**
     * @return array<string,mixed>
     */
    private function check(
        string $label,
        bool $success,
        string $value,
        string $message
    ): array {
        return [
            'label' => $label,
            'status' => $success
                ? 'success'
                : 'error',
            'value' => $value,
            'message' => $message,
        ];
    }

    private function defaultTemplate(): string
    {
        return <<<'ENV'
APP_NAME="Darlink ERP"
APP_ENV=production
APP_DEBUG=false
APP_URL=
APP_KEY=
APP_LOCALE=fr
APP_TIMEZONE=Africa/Casablanca

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306

DB_NAME=
DB_USER=
DB_PASS=

DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=

MAIL_MAILER=smtp
MAIL_HOST=
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=
MAIL_FROM_NAME="Darlink ERP"

SESSION_DRIVER=file
CACHE_DRIVER=file
ENV;
    }
}
