<?php
require_once __DIR__ . '/../app/helpers/functions.php';
require_once __DIR__ . '/../app/migrations.php';
start_session();

function installer_post(string $key, string $default = ''): string
{
    return trim((string)($_POST[$key] ?? $default));
}

function installer_config_from_post(): array
{
    return [
        'app' => [
            'name' => installer_post('app_name', 'INS Communications Suite'),
            'base_url' => installer_post('app_base_url', ((isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/'))),
            'timezone' => installer_post('app_timezone', 'America/Chicago'),
            'key' => installer_post('app_key', bin2hex(random_bytes(16))),
        ],
        'db' => [
            'host' => installer_post('db_host', 'localhost'),
            'port' => (int)installer_post('db_port', '3306'),
            'database' => installer_post('db_database', ''),
            'username' => installer_post('db_username', ''),
            'password' => installer_post('db_password', ''),
            'charset' => 'utf8mb4',
        ],
        'twilio' => [
            'sid' => installer_post('twilio_sid', ''),
            'token' => installer_post('twilio_token', ''),
            'from_number' => installer_post('twilio_from_number', ''),
            'validate_signature' => true,
        ],
        'mail' => [
            'from_name' => installer_post('mail_from_name', 'INS Communications'),
            'from_email' => installer_post('mail_from_email', ''),
            'smtp_enabled' => !empty($_POST['mail_smtp_enabled']),
            'smtp_host' => installer_post('mail_smtp_host', ''),
            'smtp_port' => (int)installer_post('mail_smtp_port', '587'),
            'smtp_username' => installer_post('mail_smtp_username', ''),
            'smtp_password' => installer_post('mail_smtp_password', ''),
            'smtp_secure' => installer_post('mail_smtp_secure', 'tls'),
        ],
        'imap' => [
            'host' => installer_post('imap_host', ''),
            'port' => (int)installer_post('imap_port', '993'),
            'flags' => installer_post('imap_flags', '/imap/ssl'),
            'username' => installer_post('imap_username', ''),
            'password' => installer_post('imap_password', ''),
            'mailbox' => installer_post('imap_mailbox', 'INBOX'),
        ],
    ];
}

function installer_write_config(array $config): void
{
    $export = "<?php\nreturn " . var_export($config, true) . ";\n";
    file_put_contents(app_config_path(), $export);
}

function installer_pdo(array $config, bool $withDb = true): PDO
{
    $dsn = 'mysql:host=' . $config['db']['host'] . ';port=' . $config['db']['port'];
    if ($withDb && $config['db']['database'] !== '') {
        $dsn .= ';dbname=' . $config['db']['database'];
    }
    $dsn .= ';charset=' . ($config['db']['charset'] ?? 'utf8mb4');
    return new PDO($dsn, $config['db']['username'], $config['db']['password'], [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
}

function installer_current_config(): array
{
    if (!app_config_exists()) {
        throw new RuntimeException('The application is not installed yet. Run the installer first.');
    }
    $config = require app_config_path();
    if (!is_array($config) || empty($config['db'])) {
        throw new RuntimeException('Unable to load config/config.php.');
    }
    return $config;
}

function installer_app_root(): string
{
    $root = realpath(__DIR__ . '/..');
    return $root ?: dirname(__DIR__);
}

function installer_storage_dir(string $suffix = ''): string
{
    $path = installer_app_root() . '/storage/updates';
    if ($suffix !== '') {
        $path .= '/' . ltrim($suffix, '/');
    }
    return $path;
}

function installer_mkdir(string $path): void
{
    if (!is_dir($path) && !mkdir($path, 0775, true) && !is_dir($path)) {
        throw new RuntimeException('Unable to create directory: ' . $path);
    }
}

function installer_can_manage_updates(): bool
{
    if (!app_config_exists()) {
        return false;
    }
    $user = current_user();
    if (!$user) {
        return false;
    }
    if (($user['role'] ?? '') === 'admin') {
        return true;
    }
    try {
        return can_access('settings.manage');
    } catch (Throwable $e) {
        return false;
    }
}

function installer_require_update_access(): void
{
    if (!installer_can_manage_updates()) {
        throw new RuntimeException('Sign in as an admin before applying an update package.');
    }
}

function installer_validate_relative_path(string $path): string
{
    $path = trim(str_replace('\\', '/', $path), '/');
    if ($path === '') {
        throw new RuntimeException('Encountered an empty file path in the update package.');
    }
    $segments = explode('/', $path);
    foreach ($segments as $segment) {
        if ($segment === '' || $segment === '.' || $segment === '..') {
            throw new RuntimeException('Unsafe file path in update package: ' . $path);
        }
    }
    return $path;
}

function installer_package_file_list(string $baseDir): array
{
    $filesDir = rtrim($baseDir, '/') . '/files';
    if (!is_dir($filesDir)) {
        throw new RuntimeException('The update package is missing the files directory.');
    }
    $list = [];
    $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($filesDir, FilesystemIterator::SKIP_DOTS));
    foreach ($iterator as $file) {
        if (!$file->isFile()) {
            continue;
        }
        $relative = substr($file->getPathname(), strlen($filesDir) + 1);
        $list[] = installer_validate_relative_path($relative);
    }
    sort($list);
    return $list;
}

function installer_copy_file(string $from, string $to): void
{
    installer_mkdir(dirname($to));
    if (!copy($from, $to)) {
        throw new RuntimeException('Unable to copy file to ' . $to);
    }
}

function installer_remove_tree(string $path): void
{
    if (!is_dir($path)) {
        if (is_file($path)) {
            @unlink($path);
        }
        return;
    }
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    );
    foreach ($iterator as $item) {
        if ($item->isDir()) {
            @rmdir($item->getPathname());
        } else {
            @unlink($item->getPathname());
        }
    }
    @rmdir($path);
}

function installer_connect_current_db(): PDO
{
    $config = installer_current_config();
    return installer_pdo($config, true);
}

function installer_sync_schema_version(PDO $pdo, string $version): void
{
    $stmt = $pdo->prepare('INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), updated_at = NOW()');
    $stmt->execute(['schema_version', $version]);
}

function installer_apply_update_package(array $file): array
{
    installer_require_update_access();
    if (!class_exists('ZipArchive')) {
        throw new RuntimeException('PHP ZipArchive is required for update packages. Ask your host to enable the zip extension, or use the standalone browser updater.');
    }
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Please choose a valid update package zip file.');
    }

    $tmpRoot = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'aio_update_' . bin2hex(random_bytes(8));
    installer_mkdir($tmpRoot);

    $zip = new ZipArchive();
    if ($zip->open($file['tmp_name']) !== true) {
        installer_remove_tree($tmpRoot);
        throw new RuntimeException('Unable to open the uploaded update package.');
    }
    if (!$zip->extractTo($tmpRoot)) {
        $zip->close();
        installer_remove_tree($tmpRoot);
        throw new RuntimeException('Unable to extract the update package.');
    }
    $zip->close();

    $manifestFile = $tmpRoot . '/manifest.json';
    if (!is_file($manifestFile)) {
        installer_remove_tree($tmpRoot);
        throw new RuntimeException('The update package is missing manifest.json.');
    }
    $manifest = json_decode((string)file_get_contents($manifestFile), true);
    if (!is_array($manifest)) {
        installer_remove_tree($tmpRoot);
        throw new RuntimeException('manifest.json could not be read.');
    }
    if (($manifest['package_type'] ?? '') !== 'aio-update-package') {
        installer_remove_tree($tmpRoot);
        throw new RuntimeException('This file is not a valid All-in-One Communication System update package.');
    }

    $files = [];
    foreach (($manifest['files'] ?? []) as $path) {
        $files[] = installer_validate_relative_path((string)$path);
    }
    if (!$files) {
        $files = installer_package_file_list($tmpRoot);
    }
    if (!$files) {
        installer_remove_tree($tmpRoot);
        throw new RuntimeException('The update package does not contain any files to apply.');
    }

    $root = installer_app_root();
    $stamp = date('Ymd_His');
    $backupRoot = installer_storage_dir('backups/' . $stamp . '/files');
    installer_mkdir($backupRoot);

    foreach ($files as $relative) {
        $source = $tmpRoot . '/files/' . $relative;
        if (!is_file($source)) {
            installer_remove_tree($tmpRoot);
            throw new RuntimeException('The update package is missing file: ' . $relative);
        }
        $target = $root . '/' . $relative;
        if (is_file($target)) {
            installer_copy_file($target, $backupRoot . '/' . $relative);
        }
    }
    installer_copy_file($manifestFile, dirname($backupRoot) . '/manifest.json');

    foreach ($files as $relative) {
        $source = $tmpRoot . '/files/' . $relative;
        $target = $root . '/' . $relative;
        installer_copy_file($source, $target);
    }

    $repairStatus = 'Skipped';
    $targetVersion = trim((string)($manifest['to_version'] ?? ''));
    if ($targetVersion === '' && is_file($root . '/VERSION')) {
        $targetVersion = trim((string)file_get_contents($root . '/VERSION'));
    }
    if (app_config_exists()) {
        try {
            $pdo = installer_connect_current_db();
            app_run_migrations($pdo, false);
            if ($targetVersion !== '') {
                installer_sync_schema_version($pdo, $targetVersion);
            }
            $repairStatus = 'Completed';
        } catch (Throwable $e) {
            $repairStatus = 'Applied code, but post-deploy repair failed: ' . $e->getMessage();
        }
    }

    installer_remove_tree($tmpRoot);

    return [
        'name' => (string)($manifest['package_name'] ?? 'Update Package'),
        'from_version' => (string)($manifest['from_version'] ?? 'unknown'),
        'to_version' => $targetVersion !== '' ? $targetVersion : (string)($manifest['to_version'] ?? 'unknown'),
        'backup_path' => dirname($backupRoot),
        'file_count' => count($files),
        'repair_status' => $repairStatus,
        'notes' => (array)($manifest['notes'] ?? []),
    ];
}

$notice = '';
$error = '';
$action = $_POST['action'] ?? ($_GET['action'] ?? '');
$updateSummary = null;
$showUpdateSection = app_config_exists();
$updateAllowed = installer_can_manage_updates();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        if ($action === 'apply_update_package') {
            verify_csrf();
            $updateSummary = installer_apply_update_package($_FILES['update_package'] ?? []);
            $notice = 'Update package applied successfully.';
        } else {
            $config = installer_config_from_post();
            if ($config['db']['database'] === '') {
                throw new RuntimeException('Database name is required.');
            }
            if ($action === 'test_db') {
                $pdo = installer_pdo($config, false);
                $pdo->exec('CREATE DATABASE IF NOT EXISTS `' . str_replace('`', '``', $config['db']['database']) . '` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
                $pdo = installer_pdo($config, true);
                $notice = 'Database connection succeeded. MySQL version: ' . $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
            } elseif ($action === 'run_install' || $action === 'repair') {
                $pdo = installer_pdo($config, false);
                $pdo->exec('CREATE DATABASE IF NOT EXISTS `' . str_replace('`', '``', $config['db']['database']) . '` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
                $pdo = installer_pdo($config, true);
                app_run_migrations($pdo, true, installer_post('admin_name', 'System Admin'), installer_post('admin_email', 'admin@example.com'), installer_post('admin_password', 'ChangeMe123!'));
                installer_write_config($config);
                @file_put_contents(dirname(app_config_path()) . '/installed.lock', date('c'));
                $notice = $action === 'repair' ? 'Repair completed. Missing tables and columns were added.' : 'Install completed. You can sign in now.';
            }
        }
    } catch (Throwable $e) {
        $error = $e->getMessage();
    }
}
?><!doctype html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Installer</title>
<style>
body{margin:0;font-family:Arial,sans-serif;background:#11345a;color:#0f172a}
.wrap{max-width:1020px;margin:24px auto;background:#f8fafc;border-radius:22px;padding:28px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.card{background:#fff;border:1px solid #d8e0ea;border-radius:18px;padding:20px;margin-top:18px}
.field label{display:block;font-size:13px;color:#475569;margin-bottom:6px}
.field input,.field select{width:100%;padding:11px;border:1px solid #cbd5e1;border-radius:10px}
h1{font-size:44px;margin:8px 0}
button{padding:12px 18px;border:0;border-radius:10px;background:#0f4c81;color:#fff;font-weight:700;cursor:pointer;margin-right:10px}
.btn-link{display:inline-block;margin-right:12px;color:#0f4c81;font-weight:700;text-decoration:none}
pre{white-space:pre-wrap;background:#f8fafc;padding:14px;border-radius:12px;border:1px solid #d8e0ea}
.notice{background:#ecfdf5;border:1px solid #a7f3d0;padding:12px;border-radius:12px}
.error{background:#fef2f2;border:1px solid #fecaca;padding:12px;border-radius:12px}
.kicker{font-size:13px;letter-spacing:.12em;color:#64748b;text-transform:uppercase}
.inline-list{margin:8px 0 0;padding-left:18px;color:#475569}
.small{font-size:13px;color:#64748b}
</style></head>
<body><div class="wrap"><div class="kicker">Corporate Messaging Suite</div><h1>Installer, Repair, and Update Center</h1><p>Use this page to test the database, install the app, repair missing tables after an upgrade, or apply a browser-deployable update package.</p>
<?php if ($notice): ?><div class="notice"><?= e($notice) ?></div><?php endif; ?>
<?php if ($error): ?><div class="error"><?= e($error) ?></div><?php endif; ?>
<?php if ($updateSummary): ?>
<div class="card">
    <h3>Update Applied</h3>
    <div class="grid">
        <div><strong>Package</strong><br><?= e($updateSummary['name']) ?></div>
        <div><strong>Version</strong><br><?= e($updateSummary['from_version']) ?> → <?= e($updateSummary['to_version']) ?></div>
        <div><strong>Files updated</strong><br><?= (int)$updateSummary['file_count'] ?></div>
        <div><strong>Backup</strong><br><code><?= e($updateSummary['backup_path']) ?></code></div>
    </div>
    <p style="margin-top:12px"><strong>Post-deploy repair:</strong> <?= e($updateSummary['repair_status']) ?></p>
    <?php if (!empty($updateSummary['notes'])): ?>
        <ul class="inline-list"><?php foreach ($updateSummary['notes'] as $note): ?><li><?= e((string)$note) ?></li><?php endforeach; ?></ul>
    <?php endif; ?>
</div>
<?php endif; ?>

<?php if ($showUpdateSection): ?>
<div class="card" id="update-packages">
    <div class="kicker">Browser Deployments</div>
    <h3 style="margin-top:6px">Update Packages</h3>
    <?php if ($updateAllowed): ?>
        <p>Upload an update package zip created for the All-in-One Communication System. The updater will back up changed files, apply the package, and run post-deploy repair.</p>
        <form method="post" enctype="multipart/form-data">
            <?= csrf_input() ?>
            <div class="grid">
                <div class="field"><label>Current version</label><input value="<?= e(app_version()) ?>" disabled></div>
                <div class="field"><label>Package requirement</label><input value="ZipArchive / PHP zip extension" disabled></div>
                <div class="field" style="grid-column:1 / span 2"><label>Update package zip</label><input type="file" name="update_package" accept=".zip" required></div>
            </div>
            <div style="margin-top:14px">
                <button name="action" value="apply_update_package">Apply Update Package</button>
                <a class="btn-link" href="index.php?route=settings/index">Back to app</a>
            </div>
        </form>
        <p class="small">Backups are stored under <code>storage/updates/backups/</code>. Code-only packages do not overwrite <code>config/config.php</code> or uploads unless a package explicitly includes those paths.</p>
    <?php else: ?>
        <p>Sign in as an admin to apply update packages from the browser.</p>
        <a class="btn-link" href="index.php?route=auth/login">Go to sign in</a>
    <?php endif; ?>
</div>
<?php endif; ?>

<form method="post"><div class="card"><h3>Application</h3><div class="grid">
<div class="field"><label>App name</label><input name="app_name" value="<?= e(installer_post('app_name', 'INS Communications Suite')) ?>"></div>
<div class="field"><label>Base URL</label><input name="app_base_url" value="<?= e(installer_post('app_base_url', ((isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'] : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/')))) ?>"></div>
<div class="field"><label>Timezone</label><input name="app_timezone" value="<?= e(installer_post('app_timezone', 'America/Chicago')) ?>"></div>
<div class="field"><label>App key</label><input name="app_key" value="<?= e(installer_post('app_key', bin2hex(random_bytes(16)))) ?>"></div>
</div></div>
<div class="card"><h3>Database</h3><div class="grid">
<div class="field"><label>Host</label><input name="db_host" value="<?= e(installer_post('db_host', 'localhost')) ?>"></div>
<div class="field"><label>Port</label><input name="db_port" value="<?= e(installer_post('db_port', '3306')) ?>"></div>
<div class="field"><label>Database name</label><input name="db_database" value="<?= e(installer_post('db_database')) ?>"></div>
<div class="field"><label>Database username</label><input name="db_username" value="<?= e(installer_post('db_username')) ?>"></div>
<div class="field"><label>Database password</label><input type="password" name="db_password" value="<?= e(installer_post('db_password')) ?>"></div>
</div></div>
<div class="card"><h3>Admin user</h3><div class="grid">
<div class="field"><label>Name</label><input name="admin_name" value="<?= e(installer_post('admin_name', 'System Admin')) ?>"></div>
<div class="field"><label>Email</label><input name="admin_email" value="<?= e(installer_post('admin_email', 'admin@example.com')) ?>"></div>
<div class="field"><label>Password</label><input type="password" name="admin_password" value="<?= e(installer_post('admin_password', 'ChangeMe123!')) ?>"></div>
</div></div>
<div class="card"><h3>Optional integrations</h3><div class="grid">
<div class="field"><label>Twilio SID</label><input name="twilio_sid" value="<?= e(installer_post('twilio_sid')) ?>"></div>
<div class="field"><label>Twilio token</label><input type="password" name="twilio_token" value="<?= e(installer_post('twilio_token')) ?>"></div>
<div class="field"><label>Twilio from number</label><input name="twilio_from_number" value="<?= e(installer_post('twilio_from_number')) ?>"></div>
<div class="field"><label>Mail from name</label><input name="mail_from_name" value="<?= e(installer_post('mail_from_name', 'INS Communications')) ?>"></div>
<div class="field"><label>Mail from email</label><input name="mail_from_email" value="<?= e(installer_post('mail_from_email')) ?>"></div>
<div class="field"><label>SMTP host</label><input name="mail_smtp_host" value="<?= e(installer_post('mail_smtp_host')) ?>"></div>
<div class="field"><label>SMTP port</label><input name="mail_smtp_port" value="<?= e(installer_post('mail_smtp_port', '587')) ?>"></div>
<div class="field"><label>SMTP username</label><input name="mail_smtp_username" value="<?= e(installer_post('mail_smtp_username')) ?>"></div>
<div class="field"><label>SMTP password</label><input type="password" name="mail_smtp_password" value="<?= e(installer_post('mail_smtp_password')) ?>"></div>
<div class="field"><label>SMTP secure</label><input name="mail_smtp_secure" value="<?= e(installer_post('mail_smtp_secure', 'tls')) ?>"></div>
<div class="field"><label>IMAP host</label><input name="imap_host" value="<?= e(installer_post('imap_host')) ?>"></div>
<div class="field"><label>IMAP port</label><input name="imap_port" value="<?= e(installer_post('imap_port', '993')) ?>"></div>
<div class="field"><label>IMAP flags</label><input name="imap_flags" value="<?= e(installer_post('imap_flags', '/imap/ssl')) ?>"></div>
<div class="field"><label>IMAP username</label><input name="imap_username" value="<?= e(installer_post('imap_username')) ?>"></div>
<div class="field"><label>IMAP password</label><input type="password" name="imap_password" value="<?= e(installer_post('imap_password')) ?>"></div>
<div class="field"><label>IMAP mailbox</label><input name="imap_mailbox" value="<?= e(installer_post('imap_mailbox', 'INBOX')) ?>"></div>
</div></div>
<div style="margin-top:20px">
<button name="action" value="test_db">Test Database Connection</button>
<button name="action" value="repair">Run Repair / Migrations</button>
<button name="action" value="run_install">Run Full Install</button>
<a class="btn-link" href="index.php">Open app</a>
</div></form></div></body></html>
