#!/usr/bin/env php
<?php

/**
 * Applies pending module migrations for all enabled modules.
 *
 * Usage:  php bin/module-migrate.php
 *
 * Each module declares a migrations_path in its manifest. SQL files in that
 * directory are applied in alphabetical order. Applied files are tracked in the
 * module_migrations table to ensure idempotency.
 *
 * Exit codes:
 *   0 — all migrations applied (or nothing to do)
 *   1 — error during migration
 */

declare(strict_types=1);

use MintyPHP\App\Module\ModuleRegistry;
use MintyPHP\DB;

require_once __DIR__ . '/module-cli-bootstrap.php';

function moduleMigrateRun(): int
{
    return moduleCliRunStep('module-migrate', static function (): int {
        $lockFile = __DIR__ . '/../storage/runtime/.module-migrate.lock';

        return moduleCliWithFileLock(
            $lockFile,
            'module-migrate: another migration run is in progress, skipping.',
            static function (): int {
                $totalApplied = 0;
                $registry = app(ModuleRegistry::class);
                $modules = $registry->getModules();
                $hasModules = count($modules) > 0;

                if (!$hasModules) {
                    fwrite(STDOUT, "module-migrate: no modules enabled, nothing to do.\n");
                    return 0;
                }

                // Ensure tracking table exists (safe if already created by db/updates/ script)
                DB::query(
                    'CREATE TABLE IF NOT EXISTS `module_migrations` (
                        `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
                        `module_id` VARCHAR(64) NOT NULL,
                        `filename` VARCHAR(255) NOT NULL,
                        `applied_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                        PRIMARY KEY (`id`),
                        UNIQUE KEY `uq_module_migration` (`module_id`, `filename`)
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
                );

                foreach ($modules as $manifest) {
                    $migrationsPath = $manifest->migrationsPath;
                    if ($migrationsPath === null || !is_dir($migrationsPath)) {
                        continue;
                    }

                    // Discover SQL files
                    $files = glob($migrationsPath . '/*.sql');
                    if ($files === false || count($files) === 0) {
                        continue;
                    }
                    sort($files); // alphabetical order

                    // Get already-applied filenames
                    $applied = [];
                    $rows = DB::select('SELECT filename FROM module_migrations WHERE module_id = ?', (string) $manifest->id);
                    foreach ((array) $rows as $row) {
                        if (!is_array($row)) {
                            continue;
                        }
                        $filename = trim((string) (($row['module_migrations']['filename'] ?? $row['filename'] ?? '')));
                        if ($filename !== '') {
                            $applied[$filename] = true;
                        }
                    }

                    foreach ($files as $file) {
                        $filename = basename($file);
                        if (isset($applied[$filename])) {
                            continue;
                        }

                        $sql = file_get_contents($file);
                        if ($sql === false || trim($sql) === '') {
                            fwrite(STDERR, sprintf("module-migrate: WARNING skipping empty file %s/%s\n", $manifest->id, $filename));
                            continue;
                        }

                        fwrite(STDOUT, sprintf("module-migrate: applying %s/%s ... ", $manifest->id, $filename));

                        // Execute migration inside a transaction so partial failures don't leave the DB inconsistent.
                        DB::query('START TRANSACTION');
                        try {
                            $statements = moduleMigrateSplitSqlStatements($sql);
                            foreach ($statements as $statement) {
                                DB::query($statement);
                            }

                            // Record as applied
                            DB::query(
                                'INSERT INTO module_migrations (module_id, filename) VALUES (?, ?)',
                                (string) $manifest->id,
                                $filename
                            );
                            DB::query('COMMIT');
                        } catch (\Throwable $migrationError) {
                            DB::query('ROLLBACK');
                            throw new \RuntimeException(
                                sprintf("Migration %s/%s failed: %s", $manifest->id, $filename, $migrationError->getMessage()),
                                0,
                                $migrationError
                            );
                        }

                        fwrite(STDOUT, "OK\n");
                        $totalApplied++;
                    }
                }

                if ($totalApplied === 0) {
                    fwrite(STDOUT, "module-migrate: all modules up to date, 0 new migrations.\n");
                } else {
                    fwrite(STDOUT, sprintf("module-migrate: applied %d migration(s).\n", $totalApplied));
                }

                return 0;
            }
        );
    });
}

/**
 * @return list<string>
 */
function moduleMigrateSplitSqlStatements(string $sql): array
{
    if (preg_match('/^\s*DELIMITER\s+/mi', $sql) === 1) {
        throw new RuntimeException('DELIMITER directives are not supported in module migration SQL files.');
    }

    $statements = [];
    $buffer = '';
    $length = strlen($sql);
    $inSingleQuote = false;
    $inDoubleQuote = false;
    $inBacktick = false;
    $inLineComment = false;
    $inBlockComment = false;
    $escapeNext = false;

    for ($i = 0; $i < $length; $i++) {
        $char = $sql[$i];
        $next = $i + 1 < $length ? $sql[$i + 1] : '';

        if ($inLineComment) {
            $buffer .= $char;
            if ($char === "\n") {
                $inLineComment = false;
            }
            continue;
        }

        if ($inBlockComment) {
            $buffer .= $char;
            if ($char === '*' && $next === '/') {
                $buffer .= $next;
                $i++;
                $inBlockComment = false;
            }
            continue;
        }

        if (!$inSingleQuote && !$inDoubleQuote && !$inBacktick) {
            if ($char === '-' && $next === '-' && ($i + 2 >= $length || ctype_space($sql[$i + 2]))) {
                $buffer .= $char . $next;
                $i++;
                $inLineComment = true;
                continue;
            }
            if ($char === '#') {
                $buffer .= $char;
                $inLineComment = true;
                continue;
            }
            if ($char === '/' && $next === '*') {
                $buffer .= $char . $next;
                $i++;
                $inBlockComment = true;
                continue;
            }
        }

        if ($escapeNext) {
            $buffer .= $char;
            $escapeNext = false;
            continue;
        }

        if (($inSingleQuote || $inDoubleQuote) && $char === '\\') {
            $buffer .= $char;
            $escapeNext = true;
            continue;
        }

        if (!$inDoubleQuote && !$inBacktick && $char === '\'') {
            $inSingleQuote = !$inSingleQuote;
            $buffer .= $char;
            continue;
        }

        if (!$inSingleQuote && !$inBacktick && $char === '"') {
            $inDoubleQuote = !$inDoubleQuote;
            $buffer .= $char;
            continue;
        }

        if (!$inSingleQuote && !$inDoubleQuote && $char === '`') {
            $inBacktick = !$inBacktick;
            $buffer .= $char;
            continue;
        }

        if (!$inSingleQuote && !$inDoubleQuote && !$inBacktick && $char === ';') {
            $statement = trim($buffer);
            if ($statement !== '') {
                $statements[] = $statement;
            }
            $buffer = '';
            continue;
        }

        $buffer .= $char;
    }

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

    return $statements;
}

if (PHP_SAPI === 'cli' && realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
    exit(moduleMigrateRun());
}
