<?php
/**
 * Action: Batch generate report card PDFs for a class/term and ZIP them.
 *
 * Renders each PDF in-process via renderReportPdfBytes(). The previous
 * implementation include()'d modules/reports/report_pdf.php inside the loop and
 * passed $_GET['save_path'], which that script has never honoured — it echoes
 * the PDF and exits. Nothing was ever written to disk, so pdf_count was always
 * 0 while the action still reported success and handed back a download URL that
 * 404'd.
 */

if (!defined('DB_HOST')) {
    require_once __DIR__ . '/../../config.php';
}
require_once __DIR__ . '/../../modules/reports/lib/report_pdf_render.php';

if (!function_exists('rbcReportPdfBaseUrl')) {
    /** BASE_URL is defined without a trailing slash; normalise before concatenating. */
    function rbcReportPdfBaseUrl(): string
    {
        $base = defined('BASE_URL') && BASE_URL !== ''
            ? BASE_URL
            : 'http://localhost/edutrack';
        return rtrim($base, '/') . '/';
    }
}

if (!function_exists('rbcPruneOldReportZips')) {
    /**
     * Delete generated batches older than $max_age_hours.
     * These contain student PII and should not accumulate indefinitely.
     */
    function rbcPruneOldReportZips(string $root, int $max_age_hours = 48): void
    {
        if (!is_dir($root)) return;
        $cutoff = time() - ($max_age_hours * 3600);

        foreach ((array) glob($root . '/*') as $path) {
            if ($path === false || !file_exists($path)) continue;
            if (@filemtime($path) > $cutoff) continue;

            if (is_file($path)) {
                @unlink($path);
            } elseif (is_dir($path)) {
                foreach ((array) glob($path . '/*') as $child) {
                    if (is_file($child)) @unlink($child);
                }
                @rmdir($path);
            }
        }
    }
}

function action_batch_generate_report_pdfs(array $params): array
{
    $conn          = getDBConnection();
    $class_name    = trim((string) ($params['class_name'] ?? ''));
    $term          = (string) ($params['term'] ?? getSettingValue('current_term') ?? 'First Term');
    $academic_year = (string) ($params['academic_year'] ?? getSettingValue('academic_year') ?? date('Y'));

    // Hard ceiling — a runaway batch would blow the request time limit and fill
    // the disk. 200 comfortably covers any real class.
    $max_students = 200;

    if ($class_name === '') {
        return ['success' => false, 'error' => 'class_name is required'];
    }

    $stmt = $conn->prepare(
        "SELECT DISTINCT s.id, s.first_name, s.last_name
         FROM students s
         JOIN report_details rd ON rd.student_id = s.id
         WHERE s.class = ? AND rd.term = ? AND rd.academic_year = ? AND s.status = 'active'
         ORDER BY s.first_name, s.last_name"
    );
    $stmt->bind_param('sss', $class_name, $term, $academic_year);
    $stmt->execute();
    $students = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
    $stmt->close();

    if (empty($students)) {
        return [
            'success' => false,
            'error'   => "No students found with report details for {$class_name} {$term} ({$academic_year}).",
        ];
    }

    $truncated = false;
    if (count($students) > $max_students) {
        $students  = array_slice($students, 0, $max_students);
        $truncated = true;
    }

    $root = __DIR__ . '/../../uploads/report_pdfs';
    rbcPruneOldReportZips($root);

    $safe_class = trim((string) preg_replace('/[^A-Za-z0-9_\-]/', '_', $class_name), '_');
    $safe_term  = trim((string) preg_replace('/[^A-Za-z0-9_\-]/', '_', $term), '_');

    // Unguessable batch directory: these ZIPs contain student PII and live under
    // a publicly served path, so a predictable name is effectively an open list.
    $token   = bin2hex(random_bytes(8));
    $dir     = $root . "/{$safe_term}_{$safe_class}_{$token}";
    if (!is_dir($dir) && !@mkdir($dir, 0755, true) && !is_dir($dir)) {
        return ['success' => false, 'error' => 'Could not create the output directory.'];
    }

    $pdf_count = 0;
    $pdf_paths = [];
    $failures  = [];

    foreach ($students as $student) {
        $student_id = (int) $student['id'];
        $filename   = reportPdfFilename($student, $safe_term);
        $pdf_path   = $dir . '/' . $filename;

        try {
            $bytes = renderReportPdfBytes($conn, $student_id, $term, $academic_year);
            if (strlen($bytes) < 100) {
                throw new RuntimeException('Renderer returned an empty document.');
            }
            if (@file_put_contents($pdf_path, $bytes) === false) {
                throw new RuntimeException('Could not write ' . basename($pdf_path));
            }
            $pdf_paths[] = $pdf_path;
            $pdf_count++;
        } catch (Throwable $e) {
            $name = trim(($student['first_name'] ?? '') . ' ' . ($student['last_name'] ?? ''));
            $failures[] = ['student_id' => $student_id, 'student' => $name, 'error' => $e->getMessage()];
            error_log("batch_generate_report_pdfs: student {$student_id} failed - " . $e->getMessage());
        }
    }

    // Nothing rendered — report the failure instead of handing back a URL that
    // is guaranteed to 404.
    if ($pdf_count === 0) {
        @rmdir($dir);
        $reason = $failures[0]['error'] ?? 'Unknown rendering error.';
        return [
            'success'        => false,
            'error'          => "Could not render any report PDFs for {$class_name}. First error: {$reason}",
            'pdf_count'      => 0,
            'total_students' => count($students),
            'failures'       => array_slice($failures, 0, 10),
        ];
    }

    if (!class_exists('ZipArchive')) {
        return [
            'success'        => false,
            'error'          => 'PHP ZipArchive extension is not available, so the PDFs could not be bundled.',
            'pdf_count'      => $pdf_count,
            'total_students' => count($students),
        ];
    }

    $zip_filename = "reports_{$safe_class}_{$safe_term}_{$token}.zip";
    $zip_path     = $root . '/' . $zip_filename;

    $zip = new ZipArchive();
    if ($zip->open($zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
        return [
            'success'        => false,
            'error'          => 'Could not create the ZIP archive.',
            'pdf_count'      => $pdf_count,
            'total_students' => count($students),
        ];
    }
    foreach ($pdf_paths as $path) {
        $zip->addFile($path, basename($path));
    }
    $zip->close();

    // Only advertise a download once the file demonstrably exists.
    if (!is_file($zip_path) || filesize($zip_path) < 100) {
        return [
            'success'        => false,
            'error'          => 'The ZIP archive was not written correctly.',
            'pdf_count'      => $pdf_count,
            'total_students' => count($students),
        ];
    }

    $result = [
        'success'        => true,
        'pdf_count'      => $pdf_count,
        'total_students' => count($students),
        'failed_count'   => count($failures),
        'zip_path'       => $zip_path,
        'zip_bytes'      => filesize($zip_path),
        'download_url'   => rbcReportPdfBaseUrl() . "uploads/report_pdfs/{$zip_filename}",
        'class_name'     => $class_name,
        'term'           => $term,
        'academic_year'  => $academic_year,
    ];
    if ($failures) {
        $result['failures'] = array_slice($failures, 0, 10);
    }
    if ($truncated) {
        $result['note'] = "Capped at {$max_students} students.";
    }

    return $result;
}
