<?php

declare(strict_types=1);

namespace Install\Services;

use PDO;
use Throwable;

final class InstallationChecker
{
    /**
     * @param array<string,mixed> $config
     * @param array<string,mixed> $database
     * @param array<string,mixed> $environment
     *
     * @return array<string,mixed>
     */
    public function audit(
        array $config,
        array $database,
        array $environment
    ): array {
        $checks = [];

        $system = (new SystemCheck())->run($config);

        foreach ($system['checks'] as $check) {
            $checks[] = [
                'group' => 'Serveur',
                'label' => (string)($check['label'] ?? ''),
                'status' => (string)($check['status'] ?? 'error'),
                'value' => (string)($check['value'] ?? ''),
                'message' => (string)($check['message'] ?? ''),
                'required' => (bool)($check['required'] ?? true),
            ];
        }

        $envPath = PROJECT_PATH . DIRECTORY_SEPARATOR . '.env';

        $checks[] = $this->check(
            'Environnement',
            'Fichier .env',
            is_file($envPath) && is_readable($envPath),
            is_file($envPath) ? 'Présent' : 'Absent',
            is_file($envPath)
                ? 'Le fichier de configuration est disponible.'
                : 'Le fichier .env doit être généré avant l’installation.'
        );

        $sql = $this->inspectSqlFiles();

        $checks[] = $this->check(
            'Fichiers d’installation',
            'Scripts SQL',
            $sql['ready'],
            $sql['summary'],
            $sql['message']
        );

        $databaseState = $this->inspectDatabase(
            $database
        );

        $checks[] = $this->check(
            'Base de données',
            'État de la base',
            !$databaseState['error'],
            $databaseState['display'],
            $databaseState['message']
        );

        $blockingFailures = 0;
        $warnings = 0;

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

            if (!empty($check['required'])) {
                $blockingFailures++;
            } else {
                $warnings++;
            }
        }

        return [
            'ready' => $blockingFailures === 0,
            'blocking_failures' => $blockingFailures,
            'warnings' => $warnings,
            'checks' => $checks,
            'sql' => $sql,
            'database_state' => $databaseState,
            'summary' => [
                'application' => (string)($environment['app_name'] ?? 'Darlink ERP'),
                'edition' => (string)($config['edition'] ?? 'Phoenix'),
                'version' => (string)($config['version'] ?? ''),
                'url' => (string)($environment['app_url'] ?? ''),
                'locale' => (string)($environment['app_locale'] ?? 'fr'),
                'timezone' => (string)($environment['app_timezone'] ?? ''),
                'db_host' => (string)($database['db_host'] ?? ''),
                'db_port' => (int)($database['db_port'] ?? 3306),
                'db_database' => (string)($database['db_database'] ?? ''),
                'db_product' => (string)($database['server_product'] ?? ''),
                'db_version' => (string)($database['server_version'] ?? ''),
                'db_charset' => (string)($database['database_character_set'] ?? ''),
                'db_collation' => (string)($database['database_collation'] ?? ''),
                'project_path' => PROJECT_PATH,
            ],
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function inspectSqlFiles(): array
    {
        $candidates = [
            INSTALL_PATH . DIRECTORY_SEPARATOR . 'sql',
            PROJECT_PATH . DIRECTORY_SEPARATOR . 'database'
                . DIRECTORY_SEPARATOR . 'migrations',
        ];

        $files = [];

        foreach ($candidates as $directory) {
            if (!is_dir($directory)) {
                continue;
            }

            foreach (glob($directory . DIRECTORY_SEPARATOR . '*.sql') ?: [] as $file) {
                if (is_file($file)) {
                    $files[] = $file;
                }
            }
        }

        sort($files);

        $readableFiles = [];
        $totalBytes = 0;
        $tableCount = 0;
        $indexCount = 0;
        $constraintCount = 0;
        $insertCount = 0;

        foreach ($files as $file) {
            if (!is_readable($file) || filesize($file) === 0) {
                continue;
            }

            $content = (string)file_get_contents($file);
            $readableFiles[] = $file;
            $totalBytes += (int)filesize($file);

            $tableCount += preg_match_all(
                '/\bCREATE\s+TABLE\b/i',
                $content
            );

            $indexCount += preg_match_all(
                '/\bCREATE\s+(?:UNIQUE\s+)?INDEX\b/i',
                $content
            );

            $constraintCount += preg_match_all(
                '/\bCONSTRAINT\b|\bFOREIGN\s+KEY\b/i',
                $content
            );

            $insertCount += preg_match_all(
                '/\bINSERT\s+INTO\b/i',
                $content
            );
        }

        $ready = count($readableFiles) > 0;

        return [
            'ready' => $ready,
            'files' => $readableFiles,
            'files_count' => count($readableFiles),
            'total_bytes' => $totalBytes,
            'tables' => $tableCount,
            'indexes' => $indexCount,
            'constraints' => $constraintCount,
            'inserts' => $insertCount,
            'estimated_seconds' => max(
                5,
                min(
                    120,
                    (int)ceil(
                        max(1, $totalBytes) / 200000
                    ) * 5
                )
            ),
            'summary' => $ready
                ? count($readableFiles) . ' fichier(s) SQL'
                : 'Aucun fichier SQL',
            'message' => $ready
                ? 'Les scripts SQL sont présents et lisibles.'
                : 'Aucun script SQL exploitable n’a été trouvé.',
        ];
    }

    /**
     * @param array<string,mixed> $database
     *
     * @return array<string,mixed>
     */
    private function inspectDatabase(
        array $database
    ): array {
        try {
            $pdo = $this->connect($database);

            $tables = (int)$pdo->query(
                'SELECT COUNT(*) '
                . 'FROM information_schema.tables '
                . 'WHERE table_schema = DATABASE()'
            )->fetchColumn();

            $darlinkTables = (int)$pdo->query(
                "SELECT COUNT(*) "
                . "FROM information_schema.tables "
                . "WHERE table_schema = DATABASE() "
                . "AND table_name IN ("
                . "'utilisateurs',"
                . "'clients',"
                . "'articles',"
                . "'documents',"
                . "'parametres_societe'"
                . ")"
            )->fetchColumn();

            $existing = $darlinkTables >= 2;

            return [
                'error' => false,
                'tables' => $tables,
                'darlink_tables' => $darlinkTables,
                'existing_installation' => $existing,
                'empty' => $tables === 0,
                'display' => $tables === 0
                    ? 'Base vide'
                    : $tables . ' table(s)',
                'message' => $existing
                    ? 'Une installation Darlink ERP semble déjà présente.'
                    : (
                        $tables === 0
                            ? 'La base est vide et prête pour une nouvelle installation.'
                            : 'La base contient déjà des tables. Vérifiez qu’elles peuvent être remplacées.'
                    ),
            ];
        } catch (Throwable $exception) {
            return [
                'error' => true,
                'tables' => 0,
                'darlink_tables' => 0,
                'existing_installation' => false,
                'empty' => false,
                'display' => 'Contrôle impossible',
                'message' => $exception->getMessage(),
            ];
        }
    }

    /**
     * @param array<string,mixed> $database
     */
    private function connect(
        array $database
    ): PDO {
        $dsn = 'mysql:host='
            . (string)($database['db_host'] ?? 'localhost')
            . ';port='
            . (int)($database['db_port'] ?? 3306)
            . ';dbname='
            . (string)($database['db_database'] ?? '')
            . ';charset=utf8mb4';

        return new PDO(
            $dsn,
            (string)($database['db_username'] ?? ''),
            (string)($database['db_password'] ?? ''),
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_TIMEOUT => 8,
            ]
        );
    }

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