<?php
/**
 * Web installer — connects to your database, creates every table, sets up
 * essential reference data (roles, priorities, SLA calendar, notification
 * templates), creates your admin account, and writes config/config.php.
 *
 * Run once at https://your-domain/install.php, then DELETE THIS FILE — an
 * installer left on a live site is a standing security risk. The success
 * screen has a one-click delete button for exactly that reason.
 *
 * No dependency on the app's own bootstrap: config.php doesn't exist yet
 * at this point, so this file is deliberately self-contained plain PHP.
 */
declare(strict_types=1);
session_start();
error_reporting(E_ALL & ~E_DEPRECATED);

$appRoot = dirname(__DIR__);
$configDir = $appRoot . '/config';
$configPath = $configDir . '/config.php';
$dbDir = $appRoot . '/database';
$storageAttachments = $appRoot . '/storage/attachments';
$storageLogs = $appRoot . '/storage/logs';

// ---------------------------------------------------------------------
// CSRF (a lightweight, session-based token — the app's own Csrf class
// isn't loadable yet since it lives behind the autoloader/config we
// haven't written).
// ---------------------------------------------------------------------
if (empty($_SESSION['install_csrf'])) {
    $_SESSION['install_csrf'] = bin2hex(random_bytes(24));
}
function csrf_field(): string
{
    return '<input type="hidden" name="_csrf" value="' . htmlspecialchars($_SESSION['install_csrf']) . '">';
}
function csrf_ok(): bool
{
    return isset($_POST['_csrf'], $_SESSION['install_csrf']) && hash_equals($_SESSION['install_csrf'], $_POST['_csrf']);
}
function h(?string $v): string
{
    return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
}

// ---------------------------------------------------------------------
// Already installed? Refuse to run rather than risk reconfiguring or
// re-seeding a live site by accident.
// ---------------------------------------------------------------------
if (is_file($configPath)) {
    renderPage('Already installed', <<<HTML
        <div class="alert alert-info">
          A <code>config/config.php</code> already exists — this system looks like it's already installed.
        </div>
        <p class="text-faint">If you really want to run the installer again (this will <strong>not</strong> touch existing data unless you also point it at a database that already has tables), delete <code>config/config.php</code> first, then reload this page.</p>
        <p class="text-faint">Once everything is working, delete <code>install.php</code> from your server — leaving it in place is a security risk.</p>
    HTML);
    exit;
}

// ---------------------------------------------------------------------
// Step routing
// ---------------------------------------------------------------------
$step = $_POST['step'] ?? 'welcome';

if ($step === 'delete_self' && $_SERVER['REQUEST_METHOD'] === 'POST' && csrf_ok()) {
    @unlink(__FILE__);
    header('Location: ' . dirname($_SERVER['SCRIPT_NAME']) . '/login');
    exit;
}

if ($step === 'test_db' && $_SERVER['REQUEST_METHOD'] === 'POST' && csrf_ok()) {
    handleTestDb();
    exit;
}

if ($step === 'install' && $_SERVER['REQUEST_METHOD'] === 'POST' && csrf_ok()) {
    handleInstall();
    exit;
}

renderWelcome();
exit;

// =======================================================================
// Step 1: requirements + DB connection form
// =======================================================================
function renderWelcome(): void
{
    global $appRoot, $dbDir, $configDir, $storageAttachments, $storageLogs;

    $checks = [
        ['PHP 8.0 or later', version_compare(PHP_VERSION, '8.0.0', '>='), true, 'Currently running ' . PHP_VERSION],
        ['PDO MySQL extension', extension_loaded('pdo_mysql'), true, ''],
        ['mbstring extension', extension_loaded('mbstring'), true, ''],
        ['fileinfo extension', extension_loaded('fileinfo'), true, ''],
        ['openssl extension (SMTP/TLS)', extension_loaded('openssl'), true, ''],
        ['zip extension (native Excel export)', extension_loaded('zip'), false, 'Without it, Excel exports automatically fall back to CSV.'],
        ['imap extension (email-to-ticket)', extension_loaded('imap'), false, 'Without it, email-to-ticket intake stays disabled — everything else still works.'],
        ['config/ directory is writable', is_writable($configDir), true, $configDir],
        ['storage/attachments/ is writable', is_dir($storageAttachments) && is_writable($storageAttachments), true, $storageAttachments],
        ['storage/logs/ is writable', is_dir($storageLogs) && is_writable($storageLogs), true, $storageLogs],
        ['database/schema.sql found', is_file($dbDir . '/schema.sql'), true, ''],
    ];

    $blocking = array_filter($checks, fn($c) => $c[2] && !$c[1]);

    $rows = '';
    foreach ($checks as [$label, $ok, $required, $note]) {
        $badge = $ok ? '<span class="badge badge-resolved">OK</span>' : ($required ? '<span class="badge badge-breach">Missing</span>' : '<span class="badge badge-oe">Optional</span>');
        $rows .= '<div class="kv"><span class="k">' . h($label) . ($note ? ' <span class="text-faint">(' . h($note) . ')</span>' : '') . '</span><span class="v">' . $badge . '</span></div>';
    }

    $dbSection = empty($blocking) ? buildDbForm() : '<div class="alert alert-error">Fix the missing requirements above before continuing.</div>';

    renderPage('Install', <<<HTML
        <div class="card">
          <div class="card-header"><h3>1. Requirements check</h3></div>
          <div class="card-body">{$rows}</div>
        </div>
        <div class="card mt-3">
          <div class="card-header"><h3>2. Database connection</h3></div>
          <div class="card-body">
            <p class="text-faint text-sm">Create the database and a user for it in cPanel &rarr; MySQL&reg; Databases first — this installer connects to an existing database, it doesn't create one (cPanel database users normally can't).</p>
            {$dbSection}
          </div>
        </div>
    HTML);
}

function buildDbForm(?array $old = null, ?string $error = null): string
{
    $old = $old ?? ['db_host' => 'localhost', 'db_name' => '', 'db_user' => '', 'db_pass' => ''];
    $errorHtml = $error ? '<div class="alert alert-error">' . h($error) . '</div>' : '';
    $csrf = csrf_field();
    $hostVal = h($old['db_host']);
    $nameVal = h($old['db_name']);
    $userVal = h($old['db_user']);
    $passVal = h($old['db_pass']);
    return <<<HTML
        {$errorHtml}
        <form method="post">
          {$csrf}
          <input type="hidden" name="step" value="test_db">
          <div class="form-row">
            <div class="form-group"><label>Database host</label><input type="text" name="db_host" value="{$hostVal}" required></div>
            <div class="form-group"><label>Database name</label><input type="text" name="db_name" value="{$nameVal}" required></div>
          </div>
          <div class="form-row">
            <div class="form-group"><label>Database user</label><input type="text" name="db_user" value="{$userVal}" required></div>
            <div class="form-group"><label>Database password</label><input type="password" name="db_pass" value="{$passVal}"></div>
          </div>
          <button type="submit" class="btn btn-primary">Test connection &amp; continue</button>
        </form>
    HTML;
}

// =======================================================================
// Step 2: test the DB connection, then show the admin-account + demo-data form
// =======================================================================
function handleTestDb(): void
{
    $creds = [
        'db_host' => trim((string)($_POST['db_host'] ?? '')),
        'db_name' => trim((string)($_POST['db_name'] ?? '')),
        'db_user' => trim((string)($_POST['db_user'] ?? '')),
        'db_pass' => (string)($_POST['db_pass'] ?? ''),
    ];

    try {
        $pdo = connectDb($creds);
    } catch (\Throwable $e) {
        renderPage('Install', '<div class="card"><div class="card-body">' . buildDbForm($creds, 'Could not connect: ' . $e->getMessage()) . '</div></div>');
        return;
    }

    $existingTables = (int)$pdo->query("SHOW TABLES LIKE 'users'")->rowCount();
    $hasData = false;
    if ($existingTables > 0) {
        try { $hasData = (int)$pdo->query('SELECT COUNT(*) FROM users')->fetchColumn() > 0; } catch (\Throwable $e) { $hasData = false; }
    }

    if ($hasData) {
        renderPage('Install', '<div class="alert alert-error">This database already has data in its <code>users</code> table — refusing to continue to avoid overwriting an existing install. Point this at an empty database, or clear it out first via phpMyAdmin.</div>' .
            '<div class="card mt-3"><div class="card-body">' . buildDbForm($creds) . '</div></div>');
        return;
    }

    $hidden = '';
    foreach ($creds as $k => $v) { $hidden .= '<input type="hidden" name="' . h($k) . '" value="' . h($v) . '">'; }

    $csrf = csrf_field();
    $dbNameEsc = h($creds['db_name']);
    $appUrlGuess = h(guessAppUrl());

    renderPage('Install', <<<HTML
        <div class="alert alert-success">Connected to <strong>{$dbNameEsc}</strong> successfully.</div>
        <div class="card">
          <div class="card-header"><h3>3. Your admin account</h3></div>
          <div class="card-body">
            <form method="post">
              {$csrf}
              <input type="hidden" name="step" value="install">
              {$hidden}
              <div class="form-group"><label>Your name</label><input type="text" name="admin_name" required></div>
              <div class="form-group"><label>Email (this is your login)</label><input type="email" name="admin_email" required></div>
              <div class="form-group"><label>Password</label><input type="password" name="admin_password" minlength="8" required></div>
              <div class="form-group"><label>App URL <span class="text-faint">(no trailing slash)</span></label><input type="text" name="app_url" value="{$appUrlGuess}" required></div>
              <label class="checkbox-row mb-2"><input type="checkbox" name="load_demo" value="1" checked> Load sample data — a few demo customers, agents and tickets so you can try the system immediately (safe to skip; delete anytime later)</label>
              <button type="submit" class="btn btn-primary btn-block">Install</button>
            </form>
          </div>
        </div>
    HTML);
}

function guessAppUrl(): string
{
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    return $scheme . '://' . $host;
}

// =======================================================================
// Step 3: run schema + core setup (+ optional demo data), create the admin,
// write config.php
// =======================================================================
function handleInstall(): void
{
    global $dbDir, $configPath;

    $creds = [
        'db_host' => trim((string)($_POST['db_host'] ?? '')),
        'db_name' => trim((string)($_POST['db_name'] ?? '')),
        'db_user' => trim((string)($_POST['db_user'] ?? '')),
        'db_pass' => (string)($_POST['db_pass'] ?? ''),
    ];
    $adminName = trim((string)($_POST['admin_name'] ?? ''));
    $adminEmail = trim((string)($_POST['admin_email'] ?? ''));
    $adminPassword = (string)($_POST['admin_password'] ?? '');
    $appUrl = rtrim(trim((string)($_POST['app_url'] ?? '')), '/');
    $loadDemo = !empty($_POST['load_demo']);

    if ($adminName === '' || !filter_var($adminEmail, FILTER_VALIDATE_EMAIL) || strlen($adminPassword) < 8 || $appUrl === '') {
        renderPage('Install', '<div class="alert alert-error">Please fill in a valid name, email, an 8+ character password, and the app URL, then try again.</div>');
        return;
    }

    try {
        $pdo = connectDb($creds);
    } catch (\Throwable $e) {
        renderPage('Install', '<div class="alert alert-error">Lost the database connection: ' . h($e->getMessage()) . '</div>');
        return;
    }

    $log = [];
    try {
        runSqlFile($pdo, $dbDir . '/schema.sql', $log);
        runSqlFile($pdo, $dbDir . '/core_setup.sql', $log);

        $adminRoleId = (int)$pdo->query("SELECT id FROM roles WHERE code = 'ADMIN'")->fetchColumn();
        if (!$adminRoleId) throw new \RuntimeException('core_setup.sql ran but the ADMIN role is missing — cannot create your account.');

        $stmt = $pdo->prepare('INSERT INTO users (employee_no, name, email, password_hash, role_id, is_active) VALUES (:emp, :name, :email, :hash, :role, 1)');
        $stmt->execute([
            'emp' => 'EMP-001', 'name' => $adminName, 'email' => $adminEmail,
            'hash' => password_hash($adminPassword, PASSWORD_DEFAULT), 'role' => $adminRoleId,
        ]);
        $log[] = 'Created admin account for ' . $adminEmail;

        if ($loadDemo) {
            runSqlFile($pdo, $dbDir . '/demo_data.sql', $log);
        }
    } catch (\Throwable $e) {
        renderPage('Install — error', '<div class="alert alert-error">Setup stopped partway through: ' . h($e->getMessage()) . '</div>' .
            '<div class="card mt-3"><div class="card-body"><pre style="white-space:pre-wrap;font-size:12px">' . h(implode("\n", $log)) . '</pre></div></div>');
        return;
    }

    try {
        writeConfig($configPath, $creds, $appUrl);
    } catch (\Throwable $e) {
        renderPage('Install — almost done', '<div class="alert alert-error">Database setup succeeded, but writing config/config.php failed: ' . h($e->getMessage()) .
            '. Copy config/config.sample.php to config/config.php by hand and fill in the values shown below.</div>' .
            '<div class="card mt-3"><div class="card-body"><pre style="white-space:pre-wrap;font-size:12px">' . h(print_r($creds, true)) . '</pre></div></div>');
        return;
    }

    $csrf = csrf_field();
    $appUrlEsc = h($appUrl);
    renderPage('Installed', <<<HTML
        <div class="alert alert-success">Installation complete.</div>
        <div class="card">
          <div class="card-body">
            <p><a href="{$appUrlEsc}/login">Agent console login →</a></p>
            <p><a href="{$appUrlEsc}/portal/login">Customer portal login →</a></p>
            <p class="text-faint text-sm mt-3">Next: set up cron jobs (SLA engine, email intake, notification retry) per DEPLOY.md, and configure outbound email under config/config.php.</p>
          </div>
        </div>
        <div class="card mt-3" style="border-color:var(--red)">
          <div class="card-body">
            <strong style="color:var(--red)">Delete this installer now.</strong>
            <p class="text-sm text-faint">Leaving install.php on a live server is a security risk — anyone who finds the URL could attempt to run it.</p>
            <form method="post" onsubmit="return confirm('Delete install.php? You will need to re-upload it to run the installer again.');">
              {$csrf}
              <input type="hidden" name="step" value="delete_self">
              <button type="submit" class="btn btn-danger">Delete install.php</button>
            </form>
          </div>
        </div>
    HTML);
}

function connectDb(array $creds): \PDO
{
    $dsn = "mysql:host={$creds['db_host']};dbname={$creds['db_name']};charset=utf8mb4";
    return new \PDO($dsn, $creds['db_user'], $creds['db_pass'], [
        \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
        \PDO::ATTR_EMULATE_PREPARES => false,
    ]);
}

/** Splits a .sql file into individual statements (quote-aware — a naive explode(';') breaks on
 *  semicolons inside string literals, and this codebase has a few) and executes each in order.
 *  Tolerates "table already exists" so a retry after a partial previous failure can proceed. */
function runSqlFile(\PDO $pdo, string $path, array &$log): void
{
    if (!is_file($path)) throw new \RuntimeException(basename($path) . ' not found.');
    $sql = file_get_contents($path);
    $statements = splitSqlStatements($sql);

    $count = 0;
    foreach ($statements as $statement) {
        if ($statement === '') continue;
        try {
            $pdo->exec($statement);
            $count++;
        } catch (\PDOException $e) {
            if ((int)$e->errorInfo[1] === 1050) continue; // table already exists — fine on a retry
            throw new \RuntimeException(basename($path) . ': ' . $e->getMessage() . ' — in statement: ' . mb_substr($statement, 0, 120));
        }
    }
    $log[] = basename($path) . ': ' . $count . ' statement(s) executed.';
}

function splitSqlStatements(string $sql): array
{
    // Strip full-line comments first so a stray ; or quote inside one can't confuse the parser.
    $sql = preg_replace('/^--.*$/m', '', $sql);

    $statements = [];
    $current = '';
    $inString = false;
    $stringChar = '';
    $len = strlen($sql);

    for ($i = 0; $i < $len; $i++) {
        $char = $sql[$i];
        $current .= $char;

        if ($inString) {
            if ($char === '\\') {
                // Consume the escaped character too, so it can't be mistaken for the closing quote.
                if ($i + 1 < $len) { $current .= $sql[++$i]; }
                continue;
            }
            if ($char === $stringChar) {
                if (($sql[$i + 1] ?? '') === $stringChar) { $current .= $sql[++$i]; continue; } // doubled-quote escape
                $inString = false;
            }
            continue;
        }

        if ($char === "'" || $char === '"') {
            $inString = true;
            $stringChar = $char;
        } elseif ($char === ';') {
            $stmt = trim(substr($current, 0, -1));
            if ($stmt !== '') $statements[] = $stmt;
            $current = '';
        }
    }
    $tail = trim($current);
    if ($tail !== '') $statements[] = $tail;
    return $statements;
}

function writeConfig(string $configPath, array $creds, string $appUrl): void
{
    $appKey = bin2hex(random_bytes(32));
    $config = "<?php\n/**\n * Generated by install.php on " . date('Y-m-d H:i:s') . "\n */\nreturn [\n" .
        "    'app' => [\n" .
        "        'name' => 'ERP.COM.BD Support Desk',\n" .
        "        'url' => " . var_export($appUrl, true) . ",\n" .
        "        'timezone' => 'Asia/Dhaka',\n" .
        "        'debug' => false,\n" .
        "    ],\n" .
        "    'db' => [\n" .
        "        'host' => " . var_export($creds['db_host'], true) . ",\n" .
        "        'name' => " . var_export($creds['db_name'], true) . ",\n" .
        "        'user' => " . var_export($creds['db_user'], true) . ",\n" .
        "        'pass' => " . var_export($creds['db_pass'], true) . ",\n" .
        "        'charset' => 'utf8mb4',\n" .
        "    ],\n" .
        "    'smtp' => [\n" .
        "        'host' => 'mail.example.com', 'port' => 587, 'encryption' => 'tls',\n" .
        "        'username' => 'support@example.com', 'password' => 'change-me',\n" .
        "        'from_email' => 'support@example.com', 'from_name' => 'ERP.COM.BD Support Desk',\n" .
        "    ],\n" .
        "    'imap' => [\n" .
        "        'enabled' => false, 'mailbox' => '{mail.example.com:993/imap/ssl}INBOX',\n" .
        "        'username' => 'support@example.com', 'password' => 'change-me',\n" .
        "        'move_processed_to' => 'Processed', 'move_quarantine_to' => 'Quarantine',\n" .
        "    ],\n" .
        "    'uploads' => [\n" .
        "        'path' => __DIR__ . '/../storage/attachments',\n" .
        "        'max_size_bytes' => 10 * 1024 * 1024,\n" .
        "        'allowed_extensions' => ['png','jpg','jpeg','gif','pdf','doc','docx','xls','xlsx','csv','txt','log','zip'],\n" .
        "    ],\n" .
        "    'session' => [\n" .
        "        'agent_cookie' => 'std_agent_sess', 'portal_cookie' => 'std_portal_sess', 'portal_idle_timeout_minutes' => 30,\n" .
        "    ],\n" .
        "    'security' => [\n" .
        "        'app_key' => " . var_export($appKey, true) . ",\n" .
        "    ],\n" .
        "];\n";

    if (@file_put_contents($configPath, $config) === false) {
        throw new \RuntimeException('Could not write to ' . $configPath . ' — check the config/ directory is writable.');
    }
}

// =======================================================================
// Chrome
// =======================================================================
function renderPage(string $title, string $body): void
{
    echo '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">' .
        '<title>' . h($title) . ' — Support Desk Setup</title>' .
        '<link rel="stylesheet" href="assets/css/app.css"></head><body style="background:var(--bg)">' .
        '<div style="max-width:640px;margin:0 auto;padding:40px 20px">' .
        '<div class="flex items-center gap-3" style="margin-bottom:24px"><img src="assets/img/erpcombd-logo.png" alt="ERP.COM.BD" style="height:34px;width:auto"><div><div style="font-weight:700">Support Desk Setup</div><div class="text-faint text-sm">' . h($title) . '</div></div></div>' .
        $body .
        '</div></body></html>';
}
