<?php
// export_certificates_zip.php
//
// Bundles multiple certificates into one downloadable .zip. Two ways to call it:
//   ?work_order_ref=WO-2026-014   — every approved job sharing that ref
//   ?ids=12,13,14                 — an explicit list (Reports batch-export checkboxes)
//
// Only APPROVED (Is_Locked=1) certificates are ever included.
//
// ARCHITECTURE — zero HTTP loopback (XAMPP-safe):
//   Previous versions made HTTP loopback calls to generate_certificate_pdf.php,
//   which itself made a second loopback to generate_certificate.php. On XAMPP
//   (and many shared-hosting environments) Apache cannot handle nested loopback
//   requests from the same process — the inner request blocks waiting for a free
//   thread that is held by the outer request, causing a deadlock that eventually
//   times out after 60 s.
//
//   This version eliminates both loopbacks entirely:
//     1. For each job, $_GET is overridden and generate_certificate.php is
//        captured with ob_start() / ob_get_clean() in the same PHP process.
//     2. The captured HTML is fed directly into Dompdf — no cURL, no sockets.
//   generate_certificate.php guards its helper-function declarations with
//   defined('CERT_RENDER_HELPERS_LOADED') so they are only declared once even
//   when the file is included N times in a loop.

require_once 'includes/auth.php';
require_role(['admin', 'inspector', 'coordinator']);

require_once 'db.php';

// ---------------------------------------------------------------------------
// DOMPDF BOOTSTRAP
// ---------------------------------------------------------------------------
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
    http_response_code(500);
    exit('PDF generation is not available: run "composer install" in the project root first.');
}
require_once __DIR__ . '/vendor/autoload.php';

if (!class_exists(\Dompdf\Dompdf::class)) {
    http_response_code(500);
    exit('Dompdf is not installed. Run: composer require dompdf/dompdf');
}

use Dompdf\Dompdf;
use Dompdf\Options;

// ---------------------------------------------------------------------------
// RESOLVE JOB LIST
// ---------------------------------------------------------------------------
$workOrderRef = trim($_GET['work_order_ref'] ?? '');
$idsParam     = trim($_GET['ids'] ?? '');

$jobs = [];

if ($workOrderRef !== '') {
    $stmt = $pdo->prepare(
        "SELECT ID, Job_No FROM tbl_jobs WHERE Work_Order_Ref = ? AND Is_Locked = 1 ORDER BY ID ASC"
    );
    $stmt->execute([$workOrderRef]);
    $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);

} elseif ($idsParam !== '') {
    $ids = array_filter(array_map('intval', explode(',', $idsParam)));
    if (!empty($ids)) {
        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        $stmt = $pdo->prepare(
            "SELECT ID, Job_No FROM tbl_jobs WHERE Is_Locked = 1 AND ID IN ($placeholders) ORDER BY ID ASC"
        );
        $stmt->execute($ids);
        $jobs = $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

if (empty($jobs)) {
    http_response_code(404);
    die('No approved certificates found matching this request. Only approved/locked certificates can be downloaded.');
}

// ---------------------------------------------------------------------------
// BUILD DOMPDF OPTIONS (shared across all PDFs in this ZIP)
// ---------------------------------------------------------------------------
$pdfOptions = new Options();
$pdfOptions->set('isRemoteEnabled',      false);
$pdfOptions->set('isPhpEnabled',         false);
$pdfOptions->set('isHtml5ParserEnabled', true);
$pdfOptions->set('chroot',               realpath(__DIR__));
$pdfOptions->set('defaultFont',          'DejaVu Sans');
// BUG FIX: see the matching comment in generate_certificate_pdf.php — without
// this, Dompdf ignores the existing `@media print` rules that remove
// .cert-paper's box-shadow/border/20mm bottom margin, causing every
// certificate in the batch to spill onto extra near-blank pages.
$pdfOptions->set('defaultMediaType',     'print');
// DEFENSE-IN-DEPTH for the Arabic font: see the matching comment in
// generate_certificate_pdf.php — Dompdf's font-family cache directory
// (vendor/dompdf/dompdf/lib/fonts by default) is not always writable after
// a fresh deploy. Use a project-local, guaranteed-writable folder instead;
// falls back silently to Dompdf's default if it can't be created.
$fontCacheDir = __DIR__ . '/storage/dompdf_font_cache';
if (!is_dir($fontCacheDir)) {
    @mkdir($fontCacheDir, 0775, true);
}
if (is_dir($fontCacheDir) && is_writable($fontCacheDir)) {
    $pdfOptions->set('fontDir',   $fontCacheDir);
    $pdfOptions->set('fontCache', $fontCacheDir);
}

// ---------------------------------------------------------------------------
// BUILD ZIP — generate each certificate in-process via output buffering
// ---------------------------------------------------------------------------
$tmpZipPath = tempnam(sys_get_temp_dir(), 'liftek_certs_') . '.zip';
$zip        = new ZipArchive();
$zip->open($tmpZipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE);

$addedCount  = 0;
$lastError   = null; // last caught exception message — surfaced to admins only, never to the public
$_orig_GET   = $_GET; // save original request parameters

foreach ($jobs as $job) {
    // ------------------------------------------------------------------
    // Step A: capture generate_certificate.php output as HTML string.
    // Override $_GET so the template reads the correct job id and mode.
    // Auth is already satisfied (we called require_role() above, and
    // generate_certificate.php's require_once 'includes/auth.php' is
    // idempotent — it will see the already-started session).
    //
    // Wrapped in try/catch: a PHP \Error (e.g. a TypeError from a bad
    // function call inside the template) is a \Throwable in PHP 7+ and
    // would otherwise abort the WHOLE batch, not just this one job.
    // ------------------------------------------------------------------
    $_GET = ['id' => (string) (int) $job['ID'], 'mode' => 'pdf'];

    try {
        ob_start();
        // include (not require_once) so the template body re-executes each
        // iteration. Function declarations inside generate_certificate.php
        // are guarded by defined('CERT_RENDER_HELPERS_LOADED') and are only
        // declared on the first pass.
        include __DIR__ . '/generate_certificate.php';
        $html = ob_get_clean();
    } catch (\Throwable $e) {
        if (ob_get_level() > 0) { ob_end_clean(); }
        $lastError = 'HTML render failed for job ' . $job['ID'] . ': ' . $e->getMessage();
        error_log('[export_zip] ' . $lastError);
        continue;
    }

    if ($html === false || trim($html) === '') {
        $lastError = 'Empty HTML for job ID ' . $job['ID'];
        error_log('[export_zip] ' . $lastError);
        continue;
    }

    // ------------------------------------------------------------------
    // Step B: render HTML → PDF bytes with Dompdf.
    // ------------------------------------------------------------------
    try {
        $dompdf = new Dompdf($pdfOptions);
        $dompdf->loadHtml($html, 'UTF-8');
        $dompdf->setPaper('A4', 'portrait');
        $dompdf->render();
        $pdfBytes = $dompdf->output();
    } catch (\Throwable $e) {
        // Most common cause: the GD PHP extension is disabled, so Dompdf
        // cannot decode the embedded PNG/JPEG images (logo, photos, QR).
        $lastError = 'Dompdf error for job ID ' . $job['ID'] . ': ' . $e->getMessage();
        error_log('[export_zip] ' . $lastError);
        continue;
    }

    if (empty($pdfBytes)) {
        $lastError = 'Dompdf produced empty output for job ID ' . $job['ID'];
        error_log('[export_zip] ' . $lastError);
        continue;
    }

    // ------------------------------------------------------------------
    // Step C: add PDF to ZIP archive.
    // ------------------------------------------------------------------
    $safeName = preg_replace('/[^A-Za-z0-9_\-]/', '_', $job['Job_No']);
    $zip->addFromString("Certificate_{$safeName}.pdf", $pdfBytes);
    $addedCount++;
}

// Restore the original request parameters.
$_GET = $_orig_GET;

$zip->close();

if ($addedCount === 0) {
    @unlink($tmpZipPath);

    // Admin gets a short, actionable diagnostic; everyone else gets a
    // generic message — never expose raw exception text/stack traces to
    // non-admin users (Secure Error Handling rule).
    $me = current_user();
    if ($me && ($me['role'] ?? '') === 'admin') {
        $hint = 'PDF generation failed for every certificate in this batch. The most common cause is the PHP "gd" extension being disabled in php.ini — Dompdf needs it to embed images (logo, photos, QR code). Enable "extension=gd" in php.ini and restart Apache.';
        $detail = $lastError ? ' Last error: ' . htmlspecialchars($lastError) : '';
        die($hint . $detail);
    }
    die('Could not generate any certificates for this download. Please try again later or contact support.');
}

// ---------------------------------------------------------------------------
// STREAM ZIP TO BROWSER
// ---------------------------------------------------------------------------
$zipFilename = $workOrderRef !== ''
    ? 'Liftek_WorkOrder_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $workOrderRef) . '.zip'
    : 'Liftek_Certificates_' . date('Y-m-d_His') . '.zip';

header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="' . $zipFilename . '"');
header('Content-Length: ' . filesize($tmpZipPath));
readfile($tmpZipPath);
@unlink($tmpZipPath);
exit;
