<?php
function db(): PDO {
    static $pdo = null;
    if ($pdo === null) {
        $c = require __DIR__ . '/config.php';
        $pdo = new PDO(
            $c['db_dsn'],
            $c['db_user'],
            $c['db_password'],
            [
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]
        );
        if ($c['db_driver'] === 'sqlite') {
            $pdo->exec('PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;');
        }
        _run_migrations($pdo);
    }
    return $pdo;
}

function _table_exists(PDO $pdo, string $table, string $driver): bool {
    if ($driver === 'sqlite') {
        return (bool) $pdo->query(
            "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=" . $pdo->quote($table)
        )->fetchColumn();
    }
    return (bool) $pdo->query(
        "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name=" . $pdo->quote($table)
    )->fetchColumn();
}

function _run_migrations(PDO $pdo): void {
    $c      = require __DIR__ . '/config.php';
    $driver = $c['db_driver'];

    $migrationTableExisted = _table_exists($pdo, 'schema_migrations', $driver);

    $pdo->exec("
        CREATE TABLE IF NOT EXISTS schema_migrations (
            version    VARCHAR(255) NOT NULL PRIMARY KEY,
            applied_at VARCHAR(32)  NOT NULL
        )
    ");

    $files = glob(__DIR__ . '/../migrations/*.php');
    if (!$files) return;
    sort($files);

    // Existing DB without migration table: stamp all known migrations as applied.
    if (!$migrationTableExisted && _table_exists($pdo, 'users', $driver)) {
        $insertIgnore = $driver === 'sqlite' ? 'INSERT OR IGNORE' : 'INSERT IGNORE';
        $stmt = $pdo->prepare(
            "$insertIgnore INTO schema_migrations (version, applied_at) VALUES (?, ?)"
        );
        foreach ($files as $file) {
            $stmt->execute([basename($file, '.php'), date('c')]);
        }
        return;
    }

    $applied = $pdo->query('SELECT version FROM schema_migrations')
                   ->fetchAll(PDO::FETCH_COLUMN);
    $applied = array_flip($applied);

    $useTransactions = ($driver === 'sqlite');

    foreach ($files as $file) {
        $version = basename($file, '.php');
        if (isset($applied[$version])) continue;

        $migration = require $file;
        if ($useTransactions) $pdo->beginTransaction();
        try {
            foreach ((array) $migration['up'] as $sql) {
                $pdo->exec($sql);
            }
            $pdo->prepare(
                'INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)'
            )->execute([$version, date('c')]);
            if ($useTransactions) $pdo->commit();
        } catch (Throwable $e) {
            if ($useTransactions && $pdo->inTransaction()) $pdo->rollBack();
            throw new RuntimeException(
                "Migration {$version} failed: " . $e->getMessage(), 0, $e
            );
        }
    }
}
