<?php

declare(strict_types=1);

final class Installer
{
    public static function requirementsCheck(string $root): array
    {
        $checks = [];

        // PHP
        $phpOk = version_compare(PHP_VERSION, '8.0.0', '>=');
        $checks[] = [
            'name' => 'PHP >= 8.0',
            'ok'   => $phpOk,
            'hint' => 'Current: ' . PHP_VERSION,
        ];

        // Extensions
        $extensions = ['pdo', 'pdo_mysql', 'mbstring', 'json', 'openssl'];
        foreach ($extensions as $ext) {
            $checks[] = [
                'name' => 'Extension: ' . $ext,
                'ok'   => extension_loaded($ext),
                'hint' => extension_loaded($ext) ? 'Loaded' : 'Missing',
            ];
        }

        // Writable paths
        $writablePaths = [
            $root . '/storage',
            $root . '/uploads',
            $root . '/core/app/Helpers',
        ];

        foreach ($writablePaths as $path) {
            $checks[] = [
                'name' => 'Writable: ' . self::rel($root, $path),
                'ok'   => is_dir($path) ? is_writable($path) : false,
                'hint' => is_dir($path) ? (is_writable($path) ? 'OK' : 'Not writable') : 'Missing directory',
            ];
        }

        // SQL file
        $schema = __DIR__ . '/../schema.sql';
        $checks[] = [
            'name' => 'SQL schema file: /install/schema.sql',
            'ok'   => file_exists($schema) && filesize($schema) > 0,
            'hint' => file_exists($schema) ? ('Size: ' . filesize($schema) . ' bytes') : 'Not found',
        ];

        $allOk = true;
        foreach ($checks as $c) {
            if (!$c['ok']) {
                $allOk = false;
                break;
            }
        }

        return ['all_ok' => $allOk, 'checks' => $checks];
    }

    public static function testDbConnection(array $db): array
    {
        try {
            $dsn = sprintf(
                'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
                $db['host'],
                $db['port'],
                $db['name']
            );
            $pdo = new PDO($dsn, $db['user'], $db['pass'], [
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES   => false,
            ]);

            $version = $pdo->query('SELECT VERSION() as v')->fetch();
            return ['ok' => true, 'message' => 'Connected. Server: ' . ($version['v'] ?? '')];
        } catch (Throwable $e) {
            return ['ok' => false, 'message' => $e->getMessage()];
        }
    }

    public static function importSql(PDO $pdo, string $sqlFile): array
    {
        if (!file_exists($sqlFile)) {
            return ['ok' => false, 'message' => 'SQL file not found: ' . $sqlFile];
        }

        $sql = file_get_contents($sqlFile);
        if ($sql === false || trim($sql) === '') {
            return ['ok' => false, 'message' => 'SQL file is empty'];
        }

        // Remove phpMyAdmin comments and SET statements that can break some hosts
        $sql = preg_replace('/^--.*$/m', '', $sql);

        // Split by semicolon at end of line (best-effort)
        $statements = self::splitSqlStatements($sql);

        $pdo->beginTransaction();
        try {
            $count = 0;
            foreach ($statements as $stmt) {
                $stmt = trim($stmt);
                if ($stmt === '') {
                    continue;
                }
                $pdo->exec($stmt);
                $count++;
            }
            $pdo->commit();
            return ['ok' => true, 'message' => 'Imported SQL statements: ' . $count];
        } catch (Throwable $e) {
            $pdo->rollBack();
            return ['ok' => false, 'message' => $e->getMessage()];
        }
    }

    public static function writeEnv(string $root, array $env): array
    {
        $envPath = $root . '/.env';

        $lines = [];
        foreach ($env as $k => $v) {
            $v = (string)$v;
            // Quote values that contain spaces or special chars
            if (preg_match('/\s|#|"|\'|=/', $v)) {
                $v = '"' . str_replace('"', '\\"', $v) . '"';
            }
            $lines[] = $k . '=' . $v;
        }

        $content = implode("\n", $lines) . "\n";
        $ok = @file_put_contents($envPath, $content);

        if ($ok === false) {
            return ['ok' => false, 'message' => 'Failed writing .env file. Check permissions.'];
        }

        return ['ok' => true, 'message' => 'Created .env successfully'];
    }

    public static function patchDbHelper(string $root): array
    {
        $target = $root . '/core/app/Helpers/db.php';
        if (!file_exists($target)) {
            return ['ok' => false, 'message' => 'db.php not found at core/app/Helpers/db.php'];
        }

        $patched = $root . '/install/lib/db.patched.php';
        if (!file_exists($patched)) {
            return ['ok' => false, 'message' => 'Patched template missing: install/lib/db.patched.php'];
        }

        $ok = @copy($patched, $target);
        if (!$ok) {
            return ['ok' => false, 'message' => 'Failed to patch db.php. Check permissions.'];
        }

        return ['ok' => true, 'message' => 'Patched db.php to use .env'];
    }

    public static function upsertAdmin(PDO $pdo, string $username, string $password, ?string $email = null): array
    {
        // Ensure table exists
        $exists = $pdo->query("SHOW TABLES LIKE 'admins'")->fetch();
        if (!$exists) {
            return ['ok' => false, 'message' => "Table 'admins' not found after SQL import."]; 
        }

        $hash = password_hash($password, PASSWORD_DEFAULT);

        // If admin #1 exists, update it; else insert new
        $admin = $pdo->query('SELECT id FROM admins ORDER BY id ASC LIMIT 1')->fetch();
        if ($admin && isset($admin['id'])) {
            $stmt = $pdo->prepare('UPDATE admins SET username = :u, password = :p, email = :e, is_active = 1 WHERE id = :id');
            $stmt->execute([
                'u'  => $username,
                'p'  => $hash,
                'e'  => $email,
                'id' => (int)$admin['id'],
            ]);
            return ['ok' => true, 'message' => 'Admin account updated (ID: ' . (int)$admin['id'] . ')'];
        }

        $stmt = $pdo->prepare('INSERT INTO admins (username, password, email, is_active) VALUES (:u, :p, :e, 1)');
        $stmt->execute([
            'u' => $username,
            'p' => $hash,
            'e' => $email,
        ]);

        return ['ok' => true, 'message' => 'Admin account created'];
    }

    public static function lock(string $root): array
    {
        $dir = $root . '/storage';
        if (!is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        $lockFile = $dir . '/installed.lock';
        $ok = @file_put_contents($lockFile, 'installed_at=' . date('c') . "\n");
        if ($ok === false) {
            return ['ok' => false, 'message' => 'Failed creating lock file.'];
        }
        return ['ok' => true, 'message' => 'Installer locked'];
    }

    public static function rel(string $root, string $path): string
    {
        return str_replace($root, '', $path) ?: '/';
    }

    private static function splitSqlStatements(string $sql): array
    {
        // Minimal SQL splitter safe enough for phpMyAdmin dumps without procedures.
        $statements = [];
        $buffer = '';
        $inString = false;
        $stringChar = '';

        $len = strlen($sql);
        for ($i = 0; $i < $len; $i++) {
            $ch = $sql[$i];

            if ($inString) {
                $buffer .= $ch;
                if ($ch === $stringChar) {
                    // check escape
                    $escaped = ($i > 0 && $sql[$i - 1] === '\\');
                    if (!$escaped) {
                        $inString = false;
                        $stringChar = '';
                    }
                }
                continue;
            }

            if ($ch === '\'' || $ch === '"') {
                $inString = true;
                $stringChar = $ch;
                $buffer .= $ch;
                continue;
            }

            if ($ch === ';') {
                $statements[] = $buffer;
                $buffer = '';
                continue;
            }

            $buffer .= $ch;
        }

        if (trim($buffer) !== '') {
            $statements[] = $buffer;
        }

        return $statements;
    }
}
