<?php
// api/tickets_zip.php — download every generated ticket image for an event as one ZIP
require_once __DIR__ . '/../config.php';
adminGuard();

$db      = db();
$eventId = (int)($_GET['event_id'] ?? 0);

// Ownership check
$ev = $db->prepare('SELECT * FROM events WHERE id=? AND admin_id=?');
$ev->execute([$eventId, $_SESSION['admin_id']]);
$event = $ev->fetch();
if (!$event) { http_response_code(404); exit('Event not found.'); }

if (!class_exists('ZipArchive')) {
    http_response_code(500);
    exit('ZIP support is not enabled on this server (PHP zip extension missing).');
}

// ── Batching (shared hosting: avoid time/disk blowups on big events) ──
// offset/limit carve the ticket list into downloadable parts.
const ZIP_MAX_BATCH     = 500;
const ZIP_DEFAULT_BATCH = 200;

$hasRange = isset($_GET['offset']) || isset($_GET['limit']);
$limit    = (int)($_GET['limit'] ?? ZIP_DEFAULT_BATCH);
$limit    = max(1, min(ZIP_MAX_BATCH, $limit));
$offset   = max(0, (int)($_GET['offset'] ?? 0));

// Total tickets available (for part numbering + range validation)
$tc = $db->prepare('SELECT COUNT(*) FROM guests
                    WHERE event_id=? AND invite_path IS NOT NULL AND invite_path != ""');
$tc->execute([$eventId]);
$total = (int)$tc->fetchColumn();

if (!$total) {
    http_response_code(404);
    exit('No tickets have been generated for this event yet. Generate tickets first, then download.');
}

// Stable ordering so parts never overlap or skip between requests
$g = $db->prepare('SELECT name, token, invite_path FROM guests
                   WHERE event_id=? AND invite_path IS NOT NULL AND invite_path != ""
                   ORDER BY name ASC, id ASC
                   LIMIT ' . $limit . ' OFFSET ' . $offset);
$g->execute([$eventId]);
$rows = $g->fetchAll();

if (!$rows) {
    http_response_code(404);
    exit('That batch is empty — the ticket list has ' . $total . ' item(s). Go back and pick a valid part.');
}

$partNo    = (int)floor($offset / $limit) + 1;
$partCount = (int)ceil($total / $limit);

@set_time_limit(300);
@ignore_user_abort(false);

$tmp = tempnam(sys_get_temp_dir(), 'tickets_');
$zip = new ZipArchive();
if ($zip->open($tmp, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
    @unlink($tmp);
    http_response_code(500);
    exit('Could not create the ZIP archive.');
}

$used = [];
foreach ($rows as $r) {
    $abs = resolveUploadPath($r['invite_path']);
    if (!$abs || !file_exists($abs)) continue;   // skip stale DB rows

    // Readable, collision-free filename: "Ada Obi.png", "Ada Obi-2.png", …
    // Keep Unicode letters/digits so accented names (Olúwaséun, Émeka) stay intact;
    // strip only path separators and characters that are unsafe in filenames.
    $base = preg_replace('/[^\p{L}\p{N} _\-]/u', '', $r['name'] ?? '');
    $base = trim(preg_replace('/\s+/u', ' ', $base)) ?: 'guest';
    $ext  = strtolower(pathinfo($abs, PATHINFO_EXTENSION)) ?: 'png';
    $name = $base . '.' . $ext;
    $n    = 1;
    while (isset($used[strtolower($name)])) { $n++; $name = $base . '-' . $n . '.' . $ext; }
    $used[strtolower($name)] = true;

    $zip->addFile($abs, $name);
}

$count = $zip->numFiles;
$zip->close();

if (!$count) {
    @unlink($tmp);
    http_response_code(404);
    exit('Ticket files are recorded but missing on disk. Try "Regenerate All" first.');
}

adminLog('tickets.download_zip', 'event', $eventId,
         $count . ' tickets' . ($hasRange ? " (part {$partNo}/{$partCount})" : ''));

$slug  = slugify($event['title'] ?: 'event') ?: 'event';
$fname = ($hasRange && $partCount > 1)
    ? sprintf('%s-tickets-part%02d-of-%02d.zip', $slug, $partNo, $partCount)
    : $slug . '-tickets.zip';

// Clear any buffered output so the ZIP isn't corrupted
while (ob_get_level()) ob_end_clean();

header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . $fname . '"');
header('Content-Length: ' . filesize($tmp));
header('Cache-Control: no-store');
header('X-Accel-Buffering: no');
readfile($tmp);
@unlink($tmp);
exit;
