<?php

/**
 * Key Lair — setup wizard.
 *
 * Deliberately self-contained (no dependency on KeyLair\Config / Database)
 * since it has to run BEFORE config/config.php exists. Once config.php is
 * written and the schema is installed, this file refuses to do anything
 * further — the app's own first-run flow in admin/login.php takes over from
 * there for creating the actual admin account.
 */

session_start();
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');

define('KV_ROOT', dirname(__DIR__));
define('KV_CONFIG_PATH', KV_ROOT . '/config/config.php');
define('KV_SCHEMA_PATH', KV_ROOT . '/sql/schema.sql');

function kv_csrf_token(): string
{
    if (empty($_SESSION['setup_csrf'])) {
        $_SESSION['setup_csrf'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['setup_csrf'];
}

function kv_csrf_valid(): bool
{
    return !empty($_POST['csrf_token']) && !empty($_SESSION['setup_csrf'])
        && hash_equals($_SESSION['setup_csrf'], $_POST['csrf_token']);
}

function kv_e(?string $v): string
{
    return htmlspecialchars($v ?? '', ENT_QUOTES, 'UTF-8');
}

/** @return array{ok: bool, error?: string, pdo?: PDO} */
function kv_test_connection(string $host, string $port, string $user, string $pass): array
{
    try {
        $pdo = new PDO(
            "mysql:host={$host};port={$port};charset=utf8mb4",
            $user,
            $pass,
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 5]
        );
        return ['ok' => true, 'pdo' => $pdo];
    } catch (PDOException $e) {
        return ['ok' => false, 'error' => $e->getMessage()];
    }
}

function kv_ensure_database(PDO $pdo, string $dbName): array
{
    try {
        $pdo->exec('CREATE DATABASE IF NOT EXISTS `' . str_replace('`', '', $dbName) . '` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
        $pdo->exec('USE `' . str_replace('`', '', $dbName) . '`');
        return ['ok' => true];
    } catch (PDOException $e) {
        return ['ok' => false, 'error' => $e->getMessage()];
    }
}

function kv_write_config(array $data): bool
{
    $encryptionKey = bin2hex(random_bytes(32));
    $host = str_replace(["'", '\\'], '', $data['host']);
    $port = (int) $data['port'];
    $dbName = str_replace(["'", '\\'], '', $data['database']);
    $user = str_replace(["'", '\\'], '', $data['username']);
    $pass = str_replace(["'", '\\'], '', $data['password']);
    $baseUrl = str_replace(["'", '\\'], '', $data['base_url']);
    $mailHost = parse_url($baseUrl, PHP_URL_HOST) ?: 'localhost';
    $mailFrom = 'no-reply@' . str_replace(["'", '\\'], '', $mailHost);

    $contents = <<<PHP
<?php
/**
 * Key Lair — configuration
 * Generated by the setup wizard on {$data['generated_at']}.
 */

return [
    'db' => [
        'host'     => '{$host}',
        'port'     => {$port},
        'database' => '{$dbName}',
        'username' => '{$user}',
        'password' => '{$pass}',
        'charset'  => 'utf8mb4',
    ],
    'app' => [
        'base_url' => '{$baseUrl}',
        'name' => 'Key Lair',
        'debug' => false,
    ],
    'mail' => [
        'from_address' => '{$mailFrom}',
        'from_name' => 'Key Lair',
    ],
    'security' => [
        'encryption_key' => '{$encryptionKey}',
        'rate_limit_claim_max_requests' => 20,
        'rate_limit_claim_window_seconds' => 60,
    ],
];

PHP;

    return @file_put_contents(KV_CONFIG_PATH, $contents) !== false;
}

/**
 * A fresh install's schema.sql already includes everything every migration
 * file under sql/migrations/ would add, so mark them all as applied right
 * away — otherwise admin/migrations.php would list them as pending, and
 * running one would fail (e.g. "duplicate constraint") since schema.sql
 * already created what it tries to add.
 */
function kv_mark_migrations_applied(PDO $pdo): void
{
    $pdo->exec(
        'CREATE TABLE IF NOT EXISTS schema_migrations (
            filename   VARCHAR(190) NOT NULL PRIMARY KEY,
            applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            source     VARCHAR(16) NOT NULL DEFAULT \'manual\'
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
    );

    $dir = KV_ROOT . '/sql/migrations';
    if (!is_dir($dir)) {
        return;
    }
    // source = 'install': schema.sql (just imported) already includes
    // everything every current migration file adds, so none of them were
    // actually run here -- this only prevents them from showing as
    // "Pending" and being re-run. src/Migrator.php's Applied list
    // deliberately excludes these, so a fresh install starts with an empty
    // Applied history instead of a wall of filenames nothing here ever ran.
    $stmt = $pdo->prepare("INSERT IGNORE INTO schema_migrations (filename, applied_at, source) VALUES (:filename, NOW(), 'install')");
    foreach (scandir($dir) as $file) {
        if (str_ends_with($file, '.sql')) {
            $stmt->execute(['filename' => $file]);
        }
    }
}

function kv_tables_exist(PDO $pdo): bool
{
    try {
        $stmt = $pdo->query("SHOW TABLES LIKE 'admin_users'");
        return $stmt->fetch() !== false;
    } catch (PDOException $e) {
        return false;
    }
}

function kv_run_schema(PDO $pdo): array
{
    if (!file_exists(KV_SCHEMA_PATH)) {
        return ['ok' => false, 'error' => 'sql/schema.sql not found.'];
    }

    $sql = file_get_contents(KV_SCHEMA_PATH);

    // Strip comments line by line BEFORE splitting on ';' — an inline trailing
    // comment (e.g. "-- note; more text") can itself contain a semicolon, which
    // would otherwise be mistaken for a statement boundary and cut a CREATE
    // TABLE statement in half.
    $cleanLines = [];
    foreach (explode("\n", $sql) as $line) {
        $line = preg_replace('/--\s.*$/', '', $line); // requires a space after -- per standard SQL comment syntax
        if (trim($line) !== '') {
            $cleanLines[] = $line;
        }
    }
    $clean = implode("\n", $cleanLines);
    $statements = array_filter(array_map('trim', explode(';', $clean)));

    try {
        foreach ($statements as $statement) {
            if ($statement === '') {
                continue;
            }
            $pdo->exec($statement);
        }
        return ['ok' => true];
    } catch (PDOException $e) {
        return ['ok' => false, 'error' => $e->getMessage()];
    }
}

// ---------------------------------------------------------------------
// Determine current state
// ---------------------------------------------------------------------

$configExists = file_exists(KV_CONFIG_PATH);
$existingConnection = null;
$existingTablesInstalled = false;
$existingConnectionError = null;

if ($configExists) {
    $configData = @include KV_CONFIG_PATH;
    if (is_array($configData)) {
        $db = $configData['db'] ?? [];
        try {
            $pdo = new PDO(
                sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', $db['host'] ?? '', $db['port'] ?? 3306, $db['database'] ?? ''),
                $db['username'] ?? '',
                $db['password'] ?? '',
                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 5]
            );
            $existingConnection = $pdo;
            $existingTablesInstalled = kv_tables_exist($pdo);
        } catch (PDOException $e) {
            $existingConnectionError = $e->getMessage();
        }
    } else {
        $existingConnectionError = 'config/config.php exists but could not be read.';
    }
}

// Block entirely once a real installation is already up and running.
if ($configExists && $existingConnection && $existingTablesInstalled) {
    $adminCount = (int) $existingConnection->query('SELECT COUNT(*) FROM admin_users')->fetchColumn();
    if ($adminCount > 0) {
        http_response_code(403);
        ?>
        <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Already installed</title>
        <style>body{font-family:system-ui,sans-serif;background:#17212e;color:#dbe3ea;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;}
        .box{max-width:420px;text-align:center;} a{color:#5aa8dd;}</style></head>
        <body><div class="box"><h1 style="font-size:18px;">Key Lair is already set up</h1>
        <p style="color:#8a97a3;font-size:14px;">This installation is complete. Go to <a href="admin/login.php">the admin login</a>.
        To reinstall from scratch, remove <code>config/config.php</code> first (and back up your database).</p></div></body></html>
        <?php
        exit;
    }
}

// ---------------------------------------------------------------------
// Handle actions
// ---------------------------------------------------------------------

$step = $_GET['step'] ?? ($configExists ? 'install' : 'welcome');
$error = null;
$dbFormValues = ['host' => '127.0.0.1', 'port' => '3306', 'database' => 'key_lair', 'username' => '', 'password' => ''];

if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_db') {
    if (!kv_csrf_valid()) {
        $error = 'Your session expired. Please try again.';
        $step = 'db';
    } else {
        $dbFormValues = [
            'host' => trim($_POST['host'] ?? ''),
            'port' => trim($_POST['port'] ?? '3306'),
            'database' => trim($_POST['database'] ?? ''),
            'username' => trim($_POST['username'] ?? ''),
            'password' => $_POST['password'] ?? '',
        ];

        $test = kv_test_connection($dbFormValues['host'], $dbFormValues['port'], $dbFormValues['username'], $dbFormValues['password']);
        if (!$test['ok']) {
            $error = "Couldn't connect to MySQL with those details: " . $test['error'];
            $step = 'db';
        } else {
            $dbResult = kv_ensure_database($test['pdo'], $dbFormValues['database']);
            if (!$dbResult['ok']) {
                $error = "Connected to MySQL, but couldn't create/select the database '{$dbFormValues['database']}': " . $dbResult['error']
                    . ' — you may need to create it manually first (see INSTALL.md).';
                $step = 'db';
            } else {
                $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
                $guessedBaseUrl = $scheme . ($_SERVER['HTTP_HOST'] ?? 'localhost');

                $written = kv_write_config([
                    'host' => $dbFormValues['host'],
                    'port' => $dbFormValues['port'],
                    'database' => $dbFormValues['database'],
                    'username' => $dbFormValues['username'],
                    'password' => $dbFormValues['password'],
                    'base_url' => $guessedBaseUrl,
                    'generated_at' => date('Y-m-d H:i:s'),
                ]);

                if (!$written) {
                    $error = "Connected successfully, but couldn't write config/config.php — check that the config/ folder is writable by the web server (see INSTALL.md).";
                    $step = 'db';
                } else {
                    header('Location: setup.php?step=install');
                    exit;
                }
            }
        }
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'run_install') {
    if (!kv_csrf_valid()) {
        $error = 'Your session expired. Please try again.';
    } elseif (!$existingConnection) {
        $error = 'No database connection available.';
    } else {
        $result = kv_run_schema($existingConnection);
        if ($result['ok']) {
            kv_mark_migrations_applied($existingConnection);
            header('Location: setup.php?step=mail');
            exit;
        }
        $error = 'Database setup failed: ' . $result['error'];
    }
    $step = 'install';
}

if ($step === 'install' && $configExists && $existingConnection && $existingTablesInstalled) {
    // Tables already exist but no admin yet (interrupted setup) — just hand off.
    header('Location: admin/login.php');
    exit;
}

if ($step === 'install' && $configExists && !$existingConnection) {
    $step = 'config_error';
}

?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Key Lair Setup</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet">
<style>
  :root{
    --bg-void:#17212e; --bg-panel:#1c2733; --bg-panel-2:#24323f; --line:#2a3947;
    --text-primary:#dbe3ea; --text-muted:#8a97a3; --text-faint:#56626d;
    --signal:#5aa8dd; --signal-dim:#3d7aa8; --key-gold:#94c93d; --danger:#d9614a;
    --font-display:'Space Grotesk',sans-serif; --font-body:'Inter',sans-serif; --font-mono:'JetBrains Mono',monospace;
  }
  *{box-sizing:border-box;}
  body{background:var(--bg-void); color:var(--text-primary); font-family:var(--font-body); min-height:100vh; display:flex; align-items:center; justify-content:center; padding:24px; margin:0;}
  .box{width:100%; max-width:460px; background:var(--bg-panel); border:1px solid var(--line); border-radius:18px; padding:30px 28px;}
  .brand{display:flex; align-items:center; gap:8px; margin-bottom:8px; font-family:var(--font-display); font-weight:600; font-size:15px;}
  .brand-icon{width:20px; height:20px; border-radius:5px; object-fit:contain;}
  .steps{display:flex; gap:6px; margin-bottom:22px;}
  .steps span{height:4px; flex:1; border-radius:99px; background:var(--line);}
  .steps span.done{background:var(--signal);}
  h1{font-family:var(--font-display); font-size:19px; font-weight:600; margin:0 0 8px;}
  p.sub{color:var(--text-muted); font-size:13px; line-height:1.55; margin:0 0 20px;}
  label{display:block; font-size:12px; font-weight:600; color:var(--text-muted); margin-bottom:6px;}
  input{width:100%; background:var(--bg-void); border:1px solid var(--line); color:var(--text-primary); font-family:var(--font-body); font-size:13.5px; padding:10px 12px; border-radius:8px; outline:none; margin-bottom:14px;}
  input:focus{border-color:var(--signal-dim);}
  .row{display:flex; gap:10px;}
  .row > div{flex:1;}
  button{width:100%; background:var(--signal); color:var(--bg-void); border:none; font-family:var(--font-body); font-size:13.5px; font-weight:600; padding:11px; border-radius:8px; cursor:pointer;}
  button:hover{background:#5ff0cf;}
  .error{background:rgba(232,115,95,0.12); border:1px solid var(--danger); color:var(--danger); font-size:12.5px; padding:10px 12px; border-radius:8px; margin-bottom:16px; line-height:1.5;}
  .hint{font-size:11.5px; color:var(--text-faint); margin:-8px 0 14px;}
  code{font-family:var(--font-mono); background:var(--bg-void); padding:1px 5px; border-radius:4px; font-size:12px;}
  a{color:var(--signal);}
</style>
</head>
<body>
<div class="box">
  <div class="brand"><img class="brand-icon" src="/assets/favicon-default.png" alt="">Key Lair Setup</div>
  <div class="steps">
    <span class="<?= in_array($step, ['welcome','db','install','mail'], true) ? 'done' : '' ?>"></span>
    <span class="<?= in_array($step, ['db','install','mail'], true) ? 'done' : '' ?>"></span>
    <span class="<?= in_array($step, ['install','mail'], true) ? 'done' : '' ?>"></span>
  </div>

  <?php if ($error): ?><div class="error"><?= kv_e($error) ?></div><?php endif; ?>

  <?php if ($step === 'welcome'): ?>
    <h1>Welcome</h1>
    <p class="sub">This wizard will connect Key Lair to your MySQL database and set up the required tables. You'll need the database host, a username, and a password with permission to create databases (or an existing empty database).</p>
    <a href="setup.php?step=db" style="display:block; text-decoration:none;"><button type="button">Let's go</button></a>

  <?php elseif ($step === 'db'): ?>
    <h1>Database connection</h1>
    <p class="sub">Enter the MySQL connection details for this installation.</p>
    <form method="post">
      <input type="hidden" name="csrf_token" value="<?= kv_e(kv_csrf_token()) ?>">
      <input type="hidden" name="action" value="save_db">
      <div class="row">
        <div>
          <label>Database host</label>
          <input type="text" name="host" value="<?= kv_e($dbFormValues['host']) ?>" required>
        </div>
        <div>
          <label>Port</label>
          <input type="text" name="port" value="<?= kv_e($dbFormValues['port']) ?>" required>
        </div>
      </div>
      <label>Database name</label>
      <input type="text" name="database" value="<?= kv_e($dbFormValues['database']) ?>" required>
      <div class="hint">Created automatically if it doesn't exist yet and your user has permission.</div>
      <label>Database username</label>
      <input type="text" name="username" value="<?= kv_e($dbFormValues['username']) ?>" required>
      <label>Database password</label>
      <input type="password" name="password" value="<?= kv_e($dbFormValues['password']) ?>">
      <button type="submit">Test connection & continue</button>
    </form>

  <?php elseif ($step === 'config_error'): ?>
    <h1>Can't connect to the database</h1>
    <p class="sub">A <code>config/config.php</code> file already exists, but Key Lair couldn't connect using the details in it:</p>
    <div class="error"><?= kv_e($existingConnectionError) ?></div>
    <p class="sub">Edit <code>config/config.php</code> directly to fix the connection details, or delete the file to run this wizard again from scratch.</p>

  <?php elseif ($step === 'install'): ?>
    <h1>Set up the database</h1>
    <p class="sub">Connected successfully. Click below to create the tables Key Lair needs.</p>
    <form method="post">
      <input type="hidden" name="csrf_token" value="<?= kv_e(kv_csrf_token()) ?>">
      <input type="hidden" name="action" value="run_install">
      <button type="submit">Install database tables</button>
    </form>

  <?php elseif ($step === 'mail'): ?>
    <h1>Outgoing mail (optional)</h1>
    <p class="sub">Key Lair can email password reset links to admins, using PHP's
    built-in <code>mail()</code> — no external mail service needed, but this server
    needs a mail transfer agent installed. You can skip this and set it up later from
    <b>Mail</b> in the admin panel, or do it now:</p>
    <div class="hint" style="margin-bottom:14px;">Run this once over SSH (installs
    Postfix in send-only mode, no prompts):</div>
    <pre style="background:var(--bg-void); border:1px solid var(--line); border-radius:8px; padding:12px; font-family:var(--font-mono); font-size:11.5px; color:var(--text-primary); overflow-x:auto; white-space:pre-wrap; margin:0 0 16px;">echo "postfix postfix/main_mailer_type string 'Internet Site'" | sudo debconf-set-selections
echo "postfix postfix/mailname string $(hostname -f)" | sudo debconf-set-selections
sudo apt install -y postfix</pre>
    <a href="admin/login.php" style="display:block; text-decoration:none;"><button type="button">Continue to sign in</button></a>
  <?php endif; ?>
</div>
</body>
</html>
