<?php
declare(strict_types=1);

require_once dirname(__DIR__) . '/includes/functions.php';

const MR_APP_VERSION = '9.11.0';

function installIsCli(): bool
{
    return PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg';
}

function installArgv(): array
{
    if (isset($GLOBALS['argv']) && is_array($GLOBALS['argv'])) {
        return $GLOBALS['argv'];
    }

    if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) {
        return $_SERVER['argv'];
    }

    return [];
}

function installStartSession(): void
{
    if (installIsCli()) {
        return;
    }

    if (session_status() === PHP_SESSION_NONE) {
        session_start();
    }
}

function installRootPath(): string
{
    return dirname(__DIR__);
}

function installConfigPath(): string
{
    return installRootPath() . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'config.php';
}

function installSchemaDefaultPath(): string
{
    return installRootPath() . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'mr.sql';
}

function installLockPath(): string
{
    return installRootPath() . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'install.lock';
}

function installIsLocked(): bool
{
    return file_exists(installLockPath());
}

function installReadLock(): array
{
    if (!installIsLocked()) {
        return [];
    }

    $json = file_get_contents(installLockPath());
    if (!is_string($json) || trim($json) === '') {
        return [];
    }

    $data = json_decode($json, true);
    return is_array($data) ? $data : [];
}

function installWriteLock(array $data = []): void
{
    $payload = array_merge([
        'version' => MR_APP_VERSION,
        'written_at' => date('c'),
        'host' => $_SERVER['HTTP_HOST'] ?? php_uname('n'),
    ], $data);

    file_put_contents(
        installLockPath(),
        json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
    );
}

function installLoadConfig(): array
{
    $configPath = installConfigPath();
    if (!file_exists($configPath)) {
        throw new RuntimeException('Konfigurationsdatei fehlt: ' . $configPath);
    }

    $config = require $configPath;
    if (!is_array($config) || empty($config['db'])) {
        throw new RuntimeException('DB-Konfiguration in includes/config.php fehlt.');
    }

    return $config;
}

function installConnectPdo(array $dbConfig): PDO
{
    $dsn = sprintf(
        'mysql:host=%s;dbname=%s;charset=%s',
        $dbConfig['host'] ?? 'localhost',
        $dbConfig['name'] ?? '',
        $dbConfig['charset'] ?? 'utf8mb4'
    );

    return new PDO($dsn, (string) ($dbConfig['user'] ?? ''), (string) ($dbConfig['pass'] ?? ''), [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
}

function installPrint(string $message): void
{
    if (installIsCli()) {
        echo $message . PHP_EOL;
        return;
    }

    echo installH($message) . "<br>\n";
}

function installWriteError(string $message): void
{
    if (installIsCli() && defined('STDERR')) {
        fwrite(STDERR, $message . PHP_EOL);
        return;
    }

    echo '<p style="color:#b42318;font:14px/1.5 Arial,sans-serif;margin:16px 0;">' . installH($message) . "</p>\n";
}

function installMigrationTableName(): string
{
    return 'app_schema_migrations';
}

function installCoreTables(): array
{
    return ['admin_users', 'lists', 'forms', 'settings', 'smtp_accounts', 'sender_identities'];
}

function installEnsureMigrationTable(PDO $pdo): void
{
    $table = installMigrationTableName();
    $pdo->exec(
        "CREATE TABLE IF NOT EXISTS `$table` (
            `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
            `migration_key` VARCHAR(190) NOT NULL,
            `applied_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (`id`),
            UNIQUE KEY `uniq_migration_key` (`migration_key`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
    );
}

function installHasTable(PDO $pdo, string $tableName): bool
{
    $stmt = $pdo->prepare('SHOW TABLES LIKE ?');
    $stmt->execute([$tableName]);
    return (bool) $stmt->fetchColumn();
}

function installHasColumn(PDO $pdo, string $tableName, string $columnName): bool
{
    $stmt = $pdo->prepare("SHOW COLUMNS FROM `$tableName` LIKE ?");
    $stmt->execute([$columnName]);
    return (bool) $stmt->fetchColumn();
}

function installHasIndex(PDO $pdo, string $tableName, string $indexName): bool
{
    $stmt = $pdo->prepare("SHOW INDEX FROM `$tableName` WHERE Key_name = ?");
    $stmt->execute([$indexName]);
    return (bool) $stmt->fetchColumn();
}

function installHasAnyCoreTable(PDO $pdo, ?array $tables = null): bool
{
    foreach ($tables ?? installCoreTables() as $table) {
        if (installHasTable($pdo, $table)) {
            return true;
        }
    }

    return false;
}

function installRequireCoreTables(PDO $pdo, array $tables): void
{
    $missing = [];
    foreach ($tables as $table) {
        if (!installHasTable($pdo, $table)) {
            $missing[] = $table;
        }
    }

    if ($missing !== []) {
        throw new RuntimeException(
            'Pflichttabellen fehlen. Bitte zuerst das Basisschema importieren. Fehlend: ' . implode(', ', $missing)
        );
    }
}

function installAppliedMigrations(PDO $pdo): array
{
    $table = installMigrationTableName();
    $stmt = $pdo->query("SELECT migration_key FROM `$table` ORDER BY migration_key ASC");
    return $stmt->fetchAll(PDO::FETCH_COLUMN) ?: [];
}

function installPendingMigrationKeys(PDO $pdo): array
{
    installEnsureMigrationTable($pdo);
    $applied = array_fill_keys(installAppliedMigrations($pdo), true);
    $pending = [];
    foreach (array_keys(installAvailableMigrations()) as $migrationKey) {
        if (!isset($applied[$migrationKey])) {
            $pending[] = $migrationKey;
        }
    }
    return $pending;
}

function installMarkMigrationApplied(PDO $pdo, string $migrationKey): void
{
    $table = installMigrationTableName();
    $stmt = $pdo->prepare("INSERT INTO `$table` (migration_key) VALUES (?)");
    $stmt->execute([$migrationKey]);
}

function installSplitSqlStatements(string $sql): array
{
    $lines = preg_split('/\r\n|\n|\r/', $sql) ?: [];
    $delimiter = ';';
    $buffer = '';
    $statements = [];

    foreach ($lines as $line) {
        $trimmed = trim($line);
        if ($trimmed === '') {
            $buffer .= "\n";
            continue;
        }

        if (preg_match('/^DELIMITER\s+(.+)$/i', $trimmed, $matches)) {
            $delimiter = $matches[1];
            continue;
        }

        $buffer .= $line . "\n";

        if ($delimiter !== '' && substr(rtrim($buffer), -strlen($delimiter)) === $delimiter) {
            $statement = rtrim($buffer);
            $statement = substr($statement, 0, -strlen($delimiter));
            $statement = trim($statement);
            if ($statement !== '') {
                $statements[] = $statement;
            }
            $buffer = '';
        }
    }

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

    return $statements;
}

function installRunSqlFile(PDO $pdo, string $absolutePath): void
{
    if (!file_exists($absolutePath)) {
        throw new RuntimeException('SQL-Datei nicht gefunden: ' . $absolutePath);
    }

    $sql = trim((string) file_get_contents($absolutePath));
    if ($sql === '') {
        return;
    }

    foreach (installSplitSqlStatements($sql) as $statement) {
        $pdo->exec($statement);
    }
}

function installAvailableMigrations(): array
{
    return [
        '20260316_001_seed_required_settings' => installRootPath() . '/install/migrations/20260316_001_seed_required_settings.php',
        '20260316_002_add_list_signup_notification' => installRootPath() . '/install/migrations/20260316_002_add_list_signup_notification.php',
        '20260316_003_stats_index_optimization' => installRootPath() . '/install/migrations/20260316_003_stats_index_optimization.php',
        '20260317_001_seed_bounce_patterns' => installRootPath() . '/install/migrations/20260317_001_seed_bounce_patterns.php',
        '20260317_002_refresh_bounce_patterns' => installRootPath() . '/install/migrations/20260317_002_refresh_bounce_patterns.php',
        '20260320_001_add_email_template_editor_support' => installRootPath() . '/install/migrations/20260320_001_add_email_template_editor_support.php',
        '20260322_001_add_link_automation_support' => installRootPath() . '/install/migrations/20260322_001_add_link_automation_support.php',
        '20260322_002_add_email_layout_builder_support' => installRootPath() . '/install/migrations/20260322_002_add_email_layout_builder_support.php',
        '20260322_003_add_member_module_support' => installRootPath() . '/install/migrations/20260322_003_add_member_module_support.php',
    ];
}

function installRunPendingMigrations(PDO $pdo): void
{
    installEnsureMigrationTable($pdo);
    $applied = array_fill_keys(installAppliedMigrations($pdo), true);

    foreach (installAvailableMigrations() as $migrationKey => $migrationFile) {
        if (isset($applied[$migrationKey])) {
            installPrint('[skip] ' . $migrationKey);
            continue;
        }

        $runner = require $migrationFile;
        if (!is_callable($runner)) {
            throw new RuntimeException('Migration ist nicht aufrufbar: ' . $migrationFile);
        }

        try {
            $runner($pdo);
            installMarkMigrationApplied($pdo, $migrationKey);
            installPrint('[ok]   ' . $migrationKey);
        } catch (Throwable $e) {
            throw new RuntimeException('Migration fehlgeschlagen [' . $migrationKey . ']: ' . $e->getMessage(), 0, $e);
        }
    }
}

function installStatus(PDO $pdo): void
{
    installEnsureMigrationTable($pdo);
    installPrint('MR Version: ' . MR_APP_VERSION);
    installPrint('Migrationstabelle: ' . installMigrationTableName());
    foreach (installAppliedMigrations($pdo) as $migration) {
        installPrint(' - ' . $migration);
    }
}

function installParseCliOptions(array $argv): array
{
    $options = [];
    foreach (array_slice($argv, 1) as $arg) {
        if (strncmp($arg, '--', 2) !== 0) {
            continue;
        }

        $eqPos = strpos($arg, '=');
        if ($eqPos === false) {
            $options[substr($arg, 2)] = true;
            continue;
        }

        $key = substr($arg, 2, $eqPos - 2);
        $value = substr($arg, $eqPos + 1);
        $options[$key] = $value;
    }

    return $options;
}

function installResolveSchemaPath(array $argv, string $defaultRelativePath = 'install/sql/mr.sql'): string
{
    foreach (array_slice($argv, 1) as $arg) {
        if (strncmp($arg, '--', 2) === 0) {
            continue;
        }

        if ($arg === '') {
            continue;
        }

        if (preg_match('~^[A-Za-z]:\\\\|^/~', $arg)) {
            return $arg;
        }

        return installRootPath() . DIRECTORY_SEPARATOR . ltrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $arg), DIRECTORY_SEPARATOR);
    }

    return installRootPath() . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $defaultRelativePath);
}

function installCreateOrUpdateAdmin(PDO $pdo, string $email, string $plainPassword, string $name = 'Administrator', string $role = 'admin'): void
{
    if (!installHasTable($pdo, 'admin_users')) {
        throw new RuntimeException('Tabelle admin_users fehlt.');
    }

    if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new RuntimeException('Admin-E-Mail ist ungÃƒÆ’Ã‚Â¼ltig.');
    }

    if ($plainPassword === '') {
        throw new RuntimeException('Admin-Passwort darf nicht leer sein.');
    }

    $passwordHash = password_hash($plainPassword, PASSWORD_DEFAULT);
    if ($passwordHash === false) {
        throw new RuntimeException('Passwort-Hash konnte nicht erzeugt werden.');
    }

    $stmt = $pdo->prepare('SELECT id FROM admin_users WHERE email = ? LIMIT 1');
    $stmt->execute([$email]);
    $existingId = $stmt->fetchColumn();

    if ($existingId) {
        $update = $pdo->prepare('UPDATE admin_users SET password_hash = ?, name = ?, role = ?, list_id = NULL, failed_logins = 0, last_login_attempt = NULL, is_active = 1 WHERE id = ?');
        $update->execute([$passwordHash, $name, $role, $existingId]);
        installPrint('Admin aktualisiert: ' . $email);
        return;
    }

    $insert = $pdo->prepare('INSERT INTO admin_users (email, password_hash, name, role, list_id, failed_logins, last_login_attempt, created_at, is_active) VALUES (?, ?, ?, ?, NULL, 0, NULL, NOW(), 1)');
    $insert->execute([$email, $passwordHash, $name, $role]);
    installPrint('Admin angelegt: ' . $email);
}

function installH(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

function installCsrfToken(string $key = 'install_csrf'): string
{
    installStartSession();
    if (empty($_SESSION[$key]) || !is_string($_SESSION[$key])) {
        $_SESSION[$key] = bin2hex(random_bytes(16));
    }
    return $_SESSION[$key];
}

function installVerifyCsrf(?string $token, string $key = 'install_csrf'): bool
{
    installStartSession();
    return is_string($token) && isset($_SESSION[$key]) && hash_equals((string) $_SESSION[$key], $token);
}

function installIsAdminSession(): bool
{
    installStartSession();
    return isset($_SESSION['admin_id']) && (int) $_SESSION['admin_id'] > 0;
}

function installCaptureOutput(callable $callback): string
{
    ob_start();
    try {
        $callback();
        return (string) ob_get_clean();
    } catch (Throwable $e) {
        ob_end_clean();
        throw $e;
    }
}

function installRenderPage(string $title, string $bodyHtml): void
{
    if (!headers_sent()) {
        header('Content-Type: text/html; charset=UTF-8');
    }

    echo '<!doctype html>';
    echo '<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">';
    echo '<title>' . installH($title) . '</title>';
    echo '<style>'
        . 'body{font:16px/1.6 Arial,sans-serif;margin:0;padding:32px;color:#1f2328;background:#f6f8fa;}'
        . 'main{max-width:980px;margin:0 auto;background:#fff;border:1px solid #d0d7de;border-radius:12px;padding:28px;}'
        . 'h1,h2,h3{margin-top:0;}'
        . '.muted{color:#57606a;}'
        . '.box{background:#f6f8fa;border:1px solid #d0d7de;border-radius:8px;padding:16px;margin:16px 0;}'
        . '.ok{border-color:#1a7f37;background:#f0fff4;}'
        . '.warn{border-color:#bf8700;background:#fff8c5;}'
        . '.err{border-color:#cf222e;background:#ffebe9;}'
        . 'code,pre,input,button{font-family:Consolas,"Courier New",monospace;}'
        . 'pre{background:#0d1117;color:#e6edf3;border-radius:8px;padding:14px;overflow:auto;white-space:pre-wrap;}'
        . 'label{display:block;font-weight:700;margin:10px 0 6px;}'
        . 'input{width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid #d0d7de;border-radius:8px;}'
        . 'button,.btn{display:inline-block;background:#0969da;color:#fff;border:0;border-radius:8px;padding:10px 14px;text-decoration:none;cursor:pointer;}'
        . '.btn-secondary{background:#57606a;}'
        . '.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:18px;}'
        . 'ul{padding-left:18px;}'
        . 'table{border-collapse:collapse;width:100%;margin-top:12px;}'
        . 'th,td{border:1px solid #d0d7de;padding:8px;text-align:left;}'
        . '</style></head><body><main>';
    echo $bodyHtml;
    echo function_exists('renderFieldTooltipBootstrap') ? renderFieldTooltipBootstrap() : '';
    echo '</main></body></html>';
}

function installStatusSummary(): array
{
    $summary = [
        'config_exists' => file_exists(installConfigPath()),
        'schema_exists' => file_exists(installSchemaDefaultPath()),
        'locked' => installIsLocked(),
        'db_ok' => false,
        'installed' => false,
        'pending_migrations' => [],
        'error' => '',
    ];

    try {
        $config = installLoadConfig();
        $pdo = installConnectPdo($config['db']);
        $summary['db_ok'] = true;
        $summary['installed'] = installHasAnyCoreTable($pdo);
        if ($summary['installed']) {
            $summary['pending_migrations'] = installPendingMigrationKeys($pdo);
        }
    } catch (Throwable $e) {
        $summary['error'] = $e->getMessage();
    }

    return $summary;
}


