<?php
require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/_layout.php';

$user = require_login($pdo);
$GLOBALS['user'] = $user;
if (!is_superadmin($user)) {
    http_response_code(403);
    exit('Keine Berechtigung.');
}

function kuhrator_update_root(): string {
    $root = realpath(__DIR__.'/..');
    if (!$root) { throw new RuntimeException('Projektordner wurde nicht gefunden.'); }
    return $root;
}

function kuhrator_update_dir(): string {
    $dir = __DIR__.'/../update';
    if (!is_dir($dir)) { @mkdir($dir, 0755, true); }
    $real = realpath($dir);
    return $real ?: $dir;
}

function kuhrator_log(string $message): void {
    @file_put_contents(kuhrator_update_dir().'/last-update.log', '['.date('Y-m-d H:i:s').'] '.$message.PHP_EOL, FILE_APPEND);
}

function kuhrator_package_version(string $name): string {
    if (preg_match('/^qr-code-manager-v([0-9]+(?:[.-][0-9]+)*)\.zip$/', $name, $m)) {
        return 'v'.str_replace('-', '.', $m[1]);
    }
    return 'neue Version';
}

function kuhrator_clean_changelog_html(string $html): string {
    $html = preg_replace('/<\\?php.*?\\?>/s', '', $html);
    $html = preg_replace('/<\\?=.*?\\?>/s', '', $html);
    $html = preg_replace('/<script\\b[^>]*>.*?<\\/script>/is', '', $html);
    $html = preg_replace('/<style\\b[^>]*>.*?<\\/style>/is', '', $html);
    $allowed = '<h2><h3><ul><ol><li><p><code><strong><em><br>';
    return trim(strip_tags($html, $allowed));
}

function kuhrator_package_changelog(string $zipPath): string {
    if (!class_exists('ZipArchive') || !is_file($zipPath)) { return ''; }
    $zip = new ZipArchive();
    if ($zip->open($zipPath) !== true) { return ''; }
    $content = '';
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $name = trim(str_replace('\\\\', '/', (string)$zip->getNameIndex($i)), '/');
        if ($name === 'admin/changelog.php' || preg_match('#^[^/]+/admin/changelog\\.php$#', $name)) {
            $content = (string)$zip->getFromIndex($i);
            break;
        }
    }
    $zip->close();
    if ($content === '') { return ''; }
    if (preg_match('/<h2[^>]*>\\s*Version\\s+[^<]+<\\/h2>\\s*(?:<ul[\\s\\S]*?<\\/ul>|<p[\\s\\S]*?<\\/p>)/i', $content, $m)) {
        return kuhrator_clean_changelog_html($m[0]);
    }
    return '';
}

function kuhrator_latest_package_changelog(array $packages): string {
    if (!$packages || empty($packages[0]['path'])) { return ''; }
    return kuhrator_package_changelog((string)$packages[0]['path']);
}


function kuhrator_update_packages(): array {
    $dir = kuhrator_update_dir();
    $files = glob($dir.'/qr-code-manager-v*.zip') ?: [];
    $packages = [];
    foreach ($files as $file) {
        $name = basename($file);
        if (!preg_match('/^qr-code-manager-v([0-9]+(?:[.-][0-9]+)*)\.zip$/', $name, $m)) { continue; }
        $packages[] = [
            'name' => $name,
            'path' => $file,
            'version' => str_replace('-', '.', $m[1]),
            'size' => @filesize($file) ?: 0,
            'mtime' => @filemtime($file) ?: time(),
        ];
    }
    usort($packages, function($a, $b) { return version_compare($b['version'], $a['version']); });
    return $packages;
}

function kuhrator_zip_has_project_structure(ZipArchive $zip): bool {
    $hasAdmin = false;
    $hasApp = false;
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $name = trim(str_replace('\\\\', '/', (string)$zip->getNameIndex($i)), '/');
        if ($name === '') { continue; }
        if (strpos($name, 'admin/') === 0) { $hasAdmin = true; }
        if (strpos($name, 'app/') === 0) { $hasApp = true; }
        $parts = explode('/', $name);
        if (count($parts) > 2) {
            if ($parts[1] === 'admin') { $hasAdmin = true; }
            if ($parts[1] === 'app') { $hasApp = true; }
        }
    }
    return $hasAdmin && $hasApp;
}

function kuhrator_upload_update_package(): array {
    if (empty($_FILES['update_zip']) || !is_array($_FILES['update_zip'])) {
        return [false, 'Keine Datei ausgewählt.'];
    }
    $file = $_FILES['update_zip'];
    if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        return [false, 'Upload fehlgeschlagen. Bitte Dateigrösse und Server-Limits prüfen.'];
    }
    $name = basename((string)($file['name'] ?? ''));
    if (!preg_match('/^qr-code-manager-v[0-9]+(?:[.-][0-9]+)*\\.zip$/', $name)) {
        return [false, 'Ungültiger Dateiname. Erlaubt ist z.B. qr-code-manager-v17-1.zip.'];
    }
    if (($file['size'] ?? 0) < 1000 || ($file['size'] ?? 0) > 50 * 1024 * 1024) {
        return [false, 'Die Update-Datei hat eine ungültige Grösse.'];
    }
    $tmpName = (string)($file['tmp_name'] ?? '');
    if (!is_uploaded_file($tmpName)) {
        return [false, 'Die Datei wurde nicht als gültiger Upload erkannt.'];
    }
    if (!class_exists('ZipArchive')) {
        return [false, 'Die PHP-Erweiterung ZipArchive ist nicht aktiv.'];
    }
    $zip = new ZipArchive();
    if ($zip->open($tmpName) !== true) {
        return [false, 'Die hochgeladene Datei ist kein gültiges ZIP-Archiv.'];
    }
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $entry = (string)$zip->getNameIndex($i);
        if (!kuhrator_is_safe_zip_path($entry)) {
            $zip->close();
            return [false, 'Das ZIP enthält unsichere Pfade und wurde abgelehnt.'];
        }
    }
    if (!kuhrator_zip_has_project_structure($zip)) {
        $zip->close();
        return [false, 'Das ZIP hat keine gültige KuhRator-Struktur. Erwartet werden /admin und /app.'];
    }
    $zip->close();
    $updateDir = kuhrator_update_dir();
    if (!is_writable($updateDir)) {
        return [false, 'Der Ordner /update ist nicht beschreibbar.'];
    }
    $target = $updateDir.'/'.$name;
    if (file_exists($target)) {
        return [false, 'Diese Update-Datei liegt bereits im Ordner /update.'];
    }
    if (!move_uploaded_file($tmpName, $target)) {
        return [false, 'Die Update-Datei konnte nicht nach /update verschoben werden.'];
    }
    @chmod($target, 0644);
    kuhrator_log('Update-Datei hochgeladen: '.$name);
    return [true, 'Update-Datei wurde hochgeladen und ist bereit zur Installation.'];
}

function kuhrator_delete_dir(string $dir): void {
    if (!is_dir($dir)) { return; }
    $items = scandir($dir);
    if (!$items) { return; }
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') { continue; }
        $path = $dir.'/'.$item;
        if (is_dir($path)) { kuhrator_delete_dir($path); }
        else { @unlink($path); }
    }
    @rmdir($dir);
}

function kuhrator_is_safe_zip_path(string $path): bool {
    $path = str_replace('\\', '/', $path);
    if ($path === '' || $path[0] === '/') { return false; }
    if (strpos($path, '../') !== false || strpos($path, '/..') !== false || $path === '..') { return false; }
    return true;
}

function kuhrator_should_skip(string $relative): bool {
    $relative = trim(str_replace('\\', '/', $relative), '/');
    if ($relative === '') { return true; }
    if ($relative === 'app/config.php') { return true; }
    if ($relative === 'update' || strpos($relative, 'update/') === 0) { return true; }
    if ($relative === 'storage' || strpos($relative, 'storage/') === 0) { return true; }
    return false;
}

function kuhrator_copy_tree(string $source, string $target, string $base): void {
    $items = scandir($source);
    if (!$items) { throw new RuntimeException('Update-Ordner konnte nicht gelesen werden: '.$source); }
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') { continue; }
        $src = $source.'/'.$item;
        $relative = str_replace('\\', '/', substr($src, strlen($base) + 1));
        if (kuhrator_should_skip($relative)) { continue; }
        $dst = $target.'/'.$relative;
        if (is_dir($src)) {
            if (!is_dir($dst) && !@mkdir($dst, 0755, true)) {
                throw new RuntimeException('Ordner konnte nicht erstellt werden: '.$relative);
            }
            kuhrator_copy_tree($src, $target, $base);
        } else {
            $dstDir = dirname($dst);
            if (!is_dir($dstDir) && !@mkdir($dstDir, 0755, true)) {
                throw new RuntimeException('Zielordner konnte nicht erstellt werden: '.$dstDir);
            }
            if (!is_writable($dstDir)) {
                throw new RuntimeException('Zielordner ist nicht beschreibbar: '.str_replace(kuhrator_update_root().'/', '', $dstDir));
            }
            if (file_exists($dst) && !is_writable($dst)) {
                throw new RuntimeException('Datei ist nicht beschreibbar: '.$relative);
            }
            if (!@copy($src, $dst)) {
                $err = error_get_last();
                throw new RuntimeException('Datei konnte nicht kopiert werden: '.$relative.(!empty($err['message']) ? ' ('.$err['message'].')' : ''));
            }
            @chmod($dst, 0644);
        }
    }
}

function kuhrator_find_source_root(string $tmp): string {
    if (is_dir($tmp.'/admin') && is_dir($tmp.'/app')) { return $tmp; }
    $children = array_values(array_filter(glob($tmp.'/*') ?: [], 'is_dir'));
    if (count($children) === 1 && is_dir($children[0].'/admin') && is_dir($children[0].'/app')) { return $children[0]; }
    throw new RuntimeException('Update-ZIP hat keine gültige KuhRator-Struktur. Erwartet werden die Ordner /admin und /app.');
}

function kuhrator_run_update(PDO $pdo, string $zipName): array {
    $tmp = null;
    try {
        kuhrator_log('Update gestartet: '.$zipName);
        if (!class_exists('ZipArchive')) {
            throw new RuntimeException('Die PHP-Erweiterung ZipArchive ist nicht aktiv. Bitte im Hosting/cPanel aktivieren.');
        }
        if (!preg_match('/^qr-code-manager-v[0-9]+(?:[.-][0-9]+)*\.zip$/', $zipName)) {
            throw new RuntimeException('Ungültiger Dateiname. Erlaubt ist z.B. qr-code-manager-v15-10.zip.');
        }
        $root = kuhrator_update_root();
        $updateDir = kuhrator_update_dir();
        if (!is_writable($updateDir)) {
            throw new RuntimeException('Der Ordner /update ist nicht beschreibbar.');
        }
        $zipPath = realpath($updateDir.'/'.$zipName);
        if (!$zipPath || !is_file($zipPath)) {
            throw new RuntimeException('Update-Datei wurde nicht gefunden.');
        }
        if (strpos($zipPath, $updateDir) !== 0) {
            throw new RuntimeException('Update-Datei liegt nicht im erlaubten Ordner.');
        }

        $zip = new ZipArchive();
        if ($zip->open($zipPath) !== true) {
            throw new RuntimeException('Update-ZIP konnte nicht geöffnet werden.');
        }
        for ($i = 0; $i < $zip->numFiles; $i++) {
            $name = $zip->getNameIndex($i);
            if (!kuhrator_is_safe_zip_path($name)) {
                $zip->close();
                throw new RuntimeException('Update-ZIP enthält unsichere Pfade.');
            }
        }

        $tmp = $updateDir.'/_tmp_'.bin2hex(random_bytes(8));
        if (!@mkdir($tmp, 0755, true)) {
            $zip->close();
            throw new RuntimeException('Temporärer Update-Ordner konnte nicht erstellt werden.');
        }
        if (!$zip->extractTo($tmp)) {
            $zip->close();
            throw new RuntimeException('Update-ZIP konnte nicht entpackt werden.');
        }
        $zip->close();

        $source = kuhrator_find_source_root($tmp);
        kuhrator_copy_tree($source, $root, $source);

        $migrations = $root.'/app/migrations.php';
        if (is_file($migrations)) { require_once $migrations; }
        if (function_exists('run_migrations')) { run_migrations($pdo); }

        kuhrator_delete_dir($tmp);
        $tmp = null;

        $installedName = $updateDir.'/installed-'.$zipName;
        if (!@rename($zipPath, $installedName)) { @unlink($zipPath); }
        kuhrator_log('Update erfolgreich: '.$zipName);
        return [true, 'Die neue Version wurde erfolgreich installiert.'];
    } catch (Throwable $e) {
        if ($tmp && is_dir($tmp)) { kuhrator_delete_dir($tmp); }
        kuhrator_log('Update fehlgeschlagen: '.$e->getMessage());
        return [false, 'Update fehlgeschlagen: '.$e->getMessage()];
    }
}

function kuhrator_render_success(string $version, string $message): void {
    page_header('Update erfolgreich'); ?>
    <meta http-equiv="refresh" content="5;url=dashboard.php">
    <section class="card update-success-card" style="max-width:560px;margin:64px auto;text-align:center;">
        <div style="font-size:44px;line-height:1;margin-bottom:14px;">✅</div>
        <h2>Update erfolgreich</h2>
        <p class="muted"><?= e($message) ?></p>
        <p class="notice ok">Aktuelle Version: <strong><?= e($version) ?></strong></p>
        <p class="muted">Du wirst in 5 Sekunden automatisch zum Dashboard weitergeleitet.</p>
        <a href="dashboard.php" class="btn" style="margin-top:18px;">Zurück zum Dashboard</a>
    </section>
    <?php page_footer();
}

$error = null;
$notice = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    csrf_check();
    $action = (string)($_POST['action'] ?? 'install');
    if ($action === 'upload') {
        list($ok, $message) = kuhrator_upload_update_package();
        if ($ok) { $notice = $message; } else { $error = $message; }
    } else {
        $selectedPackage = (string)($_POST['package'] ?? '');
        list($ok, $message) = kuhrator_run_update($pdo, $selectedPackage);
        if ($ok) {
            kuhrator_render_success(kuhrator_package_version($selectedPackage), $message);
            exit;
        }
        $error = $message;
    }
}

$packages = kuhrator_update_packages();
page_header('Updates');
?>
<section class="card">
  <h2>Automatische Updates</h2>
  <p class="muted">Lege ein ZIP mit dem Namen <code>qr-code-manager-vXX.zip</code> in den Ordner <code>/update</code>. Superadmins können es hier mit einem Klick installieren.</p>
  <?php $latestChangelog = kuhrator_latest_package_changelog($packages); ?>
  <?php if ($latestChangelog): ?>
    <div class="update-changelog-preview">
      <p class="eyebrow">Änderungen im neuesten Update</p>
      <?= $latestChangelog ?>
    </div>
  <?php endif; ?>

  <?php if ($notice): ?><p class="notice ok"><?= e($notice) ?></p><?php endif; ?>
  <form method="post" enctype="multipart/form-data" class="update-upload-card">
    <input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
    <input type="hidden" name="action" value="upload">
    <div class="update-upload-copy">
      <h3>Update-Paket hochladen</h3>
      <p class="muted">Erlaubt sind ausschliesslich geprüfte ZIP-Dateien im Format <code>qr-code-manager-vXX.zip</code>.</p>
    </div>
    <label class="upload-dropzone">
      <span class="upload-icon">⬆</span>
      <span><strong>Datei auswählen</strong><small>ZIP-Datei bis 50 MB</small></span>
      <input type="file" name="update_zip" accept=".zip" required>
    </label>
    <button class="btn" type="submit">Update-Datei hochladen</button>
  </form>
  <?php if ($error): ?><p class="notice error"><?= e($error) ?></p><?php endif; ?>
  <?php if (!$packages): ?>
    <p class="notice">Aktuell liegt kein Update-Paket im Ordner <code>/update</code>.</p>
  <?php else: ?>
    <div class="table-wrap"><table><thead><tr><th>Version</th><th>Datei</th><th>Grösse</th><th>Datum</th><th></th></tr></thead><tbody>
    <?php foreach ($packages as $p): ?><tr>
      <td><strong>v<?= e($p['version']) ?></strong></td>
      <td><code><?= e($p['name']) ?></code></td>
      <td><?= number_format($p['size']/1024, 1, '.', "'") ?> KB</td>
      <td><?= e(date('d.m.Y H:i', $p['mtime'])) ?></td>
      <td><form method="post" onsubmit="return confirm('Update wirklich installieren? Bitte stelle sicher, dass vorher ein Datenbank-Backup erstellt wurde.');"><input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>"><input type="hidden" name="action" value="install"><input type="hidden" name="package" value="<?= e($p['name']) ?>"><button class="btn" type="submit">Update installieren</button></form></td>
    </tr><?php endforeach; ?>
    </tbody></table></div>
  <?php endif; ?>
</section>
<section class="card subtle-card">
  <h2>Wichtig</h2>
  <ul>
    <li>Vor jedem Update bitte die Datenbank via phpMyAdmin sichern.</li>
    <li><code>app/config.php</code>, <code>/storage</code> und <code>/update</code> werden beim Update nicht überschrieben.</li>
    <li>Nach einem Fehler findest du Details in <code>/update/last-update.log</code>.</li>
    <li>Nach erfolgreicher Installation wird die ZIP-Datei automatisch entfernt oder in <code>installed-...</code> umbenannt.</li>
  </ul>
</section>
<?php page_footer(); ?>
