<?php
/**
 * DocumentGenerator - Generate Certificate & Marksheet PDFs (Pattern-based)
 * 
 * Uses FPDF for accurate text positioning on JPG background.
 * A4 Portrait: 595×842 pt = 210×297 mm
 */

namespace App\Helpers;

// Suppress deprecation warnings (they break PDF output via "headers already sent")
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE);
ini_set('display_errors', '0');

// ─── Load FPDF library (try multiple paths) ────────────────────────────────
if (!class_exists('FPDF')) {
    $fpdfCandidates = [
        dirname(dirname(__DIR__)) . '/fpdf/fpdf.php',
        dirname(dirname(dirname(__DIR__))) . '/fpdf/fpdf.php',
        $_SERVER['DOCUMENT_ROOT'] . '/fpdf/fpdf.php',
        $_SERVER['DOCUMENT_ROOT'] . '/gmind/fpdf/fpdf.php',
        '/home/advaysin/public_html/gmind/fpdf/fpdf.php',
        '/home/advaysin/public_html/fpdf/fpdf.php',
    ];
    foreach ($fpdfCandidates as $path) {
        if (file_exists($path)) {
            require_once $path;
            break;
        }
    }
}

class DocumentGenerator
{
    private $ptToMm = 0.3528;

    private $gradeScale = [
        ['min' => 95, 'grade' => 'S'],
        ['min' => 90, 'grade' => 'A++'],
        ['min' => 80, 'grade' => 'A+'],
        ['min' => 70, 'grade' => 'A'],
        ['min' => 60, 'grade' => 'B'],
        ['min' => 50, 'grade' => 'C'],
        ['min' => 40, 'grade' => 'D'],
        ['min' => 0,  'grade' => 'F'],
    ];

    public function generateFromPattern(array $params)
    {
        if (!class_exists('FPDF')) {
            throw new \Exception('FPDF class not found. Please upload FPDF library to /gmind/fpdf/fpdf.php');
        }

        $pattern   = $params['pattern'] ?? [];
        $fields    = $params['fields'] ?? [];
        $student   = $params['student'] ?? [];
        $docType   = $params['doc_type'] ?? 'certificate';
        $bgPath    = $params['bg_path'] ?? '';
        $isPreview = $params['is_preview'] ?? false;

        $pageWidth  = (int)($pattern['page_width']  ?? 595);
        $pageHeight = (int)($pattern['page_height'] ?? 842);

        // ✅ FIX: Try multiple paths to find the background image
        $resolvedBgPath = $this->resolveBackgroundPath($bgPath, $pattern, $docType);

        if (empty($resolvedBgPath) || !file_exists($resolvedBgPath)) {
            $triedPaths = $this->getBackgroundPathCandidates($bgPath, $pattern, $docType);
            throw new \Exception(
                "Background image not found. Tried paths: " . implode(' | ', $triedPaths)
            );
        }

        $pdf = new \FPDF('P', 'mm', [$pageWidth * $this->ptToMm, $pageHeight * $this->ptToMm]);
        $pdf->AddPage();
        $pdf->SetMargins(0, 0, 0);
        $pdf->SetAutoPageBreak(false);

        $pdf->Image($resolvedBgPath, 0, 0, $pageWidth * $this->ptToMm, $pageHeight * $this->ptToMm);

        $pdf->SetFont('Arial', '', 12);
        $pdf->SetTextColor(0, 0, 0);

        // Filter out fields that don't apply to this doc type
        $filteredFields = [];
        foreach ($fields as $f) {
            $key = $f['field_key'] ?? '';
            // Subject marks table ONLY on marksheet
            if ($key === 'subject_marks' && $docType === 'certificate') {
                continue;
            }
            // ✅ from_date and to_date ONLY on certificate (not marksheet)
            if (in_array($key, ['from_date', 'to_date']) && $docType === 'marksheet') {
                continue;
            }
            $filteredFields[] = $f;
        }

        $sortedFields = $this->sortFieldsForRender($filteredFields);

        foreach ($sortedFields as $field) {
            $this->renderField($pdf, $field, $student, $docType, $isPreview);
        }

        return $pdf;
    }

    /**
     * Add a pattern-based page to an EXISTING FPDF instance (for bulk PDF generation).
     * Creates a new page with background + fields rendered on it.
     *
     * @param \FPDF $pdf Existing FPDF instance to add page to
     * @param array $params Same params as generateFromPattern()
     * @return \FPDF The same PDF instance (for chaining)
     */
    public function addPatternPage(\FPDF $pdf, array $params): \FPDF
    {
        $pattern   = $params['pattern'] ?? [];
        $fields    = $params['fields'] ?? [];
        $student   = $params['student'] ?? [];
        $docType   = $params['doc_type'] ?? 'certificate';
        $bgPath    = $params['bg_path'] ?? '';
        $isPreview = $params['is_preview'] ?? false;

        $pageWidth  = (int)($pattern['page_width']  ?? 595);
        $pageHeight = (int)($pattern['page_height'] ?? 842);

        // Resolve background path
        $resolvedBgPath = $this->resolveBackgroundPath($bgPath, $pattern, $docType);

        if (empty($resolvedBgPath) || !file_exists($resolvedBgPath)) {
            // Skip this page if background not found (don't crash entire bulk)
            $pdf->AddPage();
            $pdf->SetFont('Arial', 'B', 16);
            $pdf->SetTextColor(200, 0, 0);
            $pdf->Cell(0, 50, 'Background image not found for ' . $docType, 0, 1, 'C');
            $pdf->Cell(0, 10, 'Student: ' . ($student['student_name'] ?? 'Unknown'), 0, 1, 'C');
            return $pdf;
        }

        // Add new page to existing PDF
        $pdf->AddPage();
        $pdf->SetMargins(0, 0, 0);
        $pdf->SetAutoPageBreak(false);

        // Place background image
        $pdf->Image($resolvedBgPath, 0, 0, $pageWidth * $this->ptToMm, $pageHeight * $this->ptToMm);

        $pdf->SetFont('Arial', '', 12);
        $pdf->SetTextColor(0, 0, 0);

        // Filter fields
        $filteredFields = [];
        foreach ($fields as $f) {
            $key = $f['field_key'] ?? '';
            if ($key === 'subject_marks' && $docType === 'certificate') continue;
            if (in_array($key, ['from_date', 'to_date']) && $docType === 'marksheet') continue;
            $filteredFields[] = $f;
        }

        $sortedFields = $this->sortFieldsForRender($filteredFields);

        foreach ($sortedFields as $field) {
            $this->renderField($pdf, $field, $student, $docType, $isPreview);
        }

        return $pdf;
    }

    /**
     * Resolve background image path — tries multiple candidates
     */
    private function resolveBackgroundPath(string $bgPath, array $pattern, string $docType): string
    {
        $candidates = $this->getBackgroundPathCandidates($bgPath, $pattern, $docType);

        foreach ($candidates as $path) {
            if (!empty($path) && file_exists($path)) {
                return $path;
            }
        }

        return '';
    }

    /**
     * Get all possible paths where the background image might be
     */
    private function getBackgroundPathCandidates(string $bgPath, array $pattern, string $docType): array
    {
        $candidates = [];

        // If bgPath is explicitly passed, use it + variations
        if (!empty($bgPath)) {
            $candidates[] = $bgPath;

            // If path starts with /, try with DOCUMENT_ROOT
            if ($bgPath[0] === '/') {
                $candidates[] = $_SERVER['DOCUMENT_ROOT'] . $bgPath;
                $candidates[] = rtrim($_SERVER['DOCUMENT_ROOT'], '/') . $bgPath;

                // App might be in /gmind subfolder
                if (defined('BASE_PATH') && BASE_PATH !== '/') {
                    $candidates[] = $_SERVER['DOCUMENT_ROOT'] . BASE_PATH . $bgPath;
                    $candidates[] = rtrim($_SERVER['DOCUMENT_ROOT'], '/') . BASE_PATH . $bgPath;
                }

                // Try /gmind explicitly (common case)
                $candidates[] = $_SERVER['DOCUMENT_ROOT'] . '/gmind' . $bgPath;
                $candidates[] = '/home/advaysin/public_html/gmind' . $bgPath;
            }
        }

        // ✅ CRITICAL FIX: If bgPath is empty, get it from pattern record
        if (empty($bgPath) && !empty($pattern)) {
            $patternBgPath = $docType === 'certificate'
                ? ($pattern['cert_background_path'] ?? '')
                : ($pattern['marksheet_background_path'] ?? '');

            if (!empty($patternBgPath)) {
                // Recursively try with the pattern's background path
                $candidates = array_merge(
                    $candidates,
                    $this->getBackgroundPathCandidates($patternBgPath, [], $docType)
                );
            }
        }

        // Remove duplicates and empty values
        $candidates = array_filter(array_unique($candidates));

        return $candidates;
    }

    private function renderField($pdf, $field, $student, $docType, $isPreview)
    {
        $key      = $field['field_key'];
        $xPt      = (float)($field['x_pos'] ?? 0);
        $yPt      = (float)($field['y_pos'] ?? 0);
        $fontSize = (int)($field['font_size'] ?? 12);
        $style    = $field['font_style'] ?? 'regular';
        $color    = $field['color'] ?? '#000000';
        $align    = $field['alignment'] ?? 'L';
        $width    = (float)($field['width'] ?? 0);
        $height   = (float)($field['height'] ?? 0);
        $border   = (int)($field['border'] ?? 0);
        $enabled  = (int)($field['is_enabled'] ?? 1);

        if (!$enabled) return;

        $xMm = $xPt * $this->ptToMm;
        $yMm = $yPt * $this->ptToMm;
        $wMm = $width * $this->ptToMm;
        $hMm = $height * $this->ptToMm;

        $value = $this->getFieldValue($key, $student, $docType);

        $rgb = $this->hexToRgb($color);
        $pdf->SetTextColor($rgb[0], $rgb[1], $rgb[2]);

        $fpdfStyle = '';
        if ($style === 'bold' || $style === 'bolditalic') $fpdfStyle .= 'B';
        if ($style === 'italic' || $style === 'bolditalic') $fpdfStyle .= 'I';

        switch ($key) {
            case 'photo':
                $this->renderPhoto($pdf, $xMm, $yMm, $wMm, $hMm, $student, $border, $isPreview);
                break;

            case 'qr_code':
                $this->renderQRCode($pdf, $xMm, $yMm, $wMm, $hMm, $student, $isPreview);
                break;

            case 'subject_marks':
                if ($docType === 'marksheet') {
                    $this->renderSubjectMarksTable($pdf, $xMm, $yMm, $wMm, $student, $fontSize);
                }
                break;

            default:
                if (empty($value)) return;
                // ✅ CONVERT TO UPPERCASE
                $value = $this->toUpperCase($value);
                $pdf->SetFont('Arial', $fpdfStyle, $fontSize);
                // ✅ Use SetXY with precise positioning
                // For center alignment, position X at center point
                if ($align === 'C' && $wMm <= 0) {
                    // Center alignment without width — use a reasonable cell width
                    $cellWidth = 100;
                    $pdf->SetXY($xMm - ($cellWidth / 2), $yMm);
                    $pdf->Cell($cellWidth, $fontSize * 0.35, $value, 0, 0, 'C');
                } elseif ($align === 'R' && $wMm <= 0) {
                    // Right alignment — estimate text width
                    $textWidth = $pdf->GetStringWidth($value);
                    $pdf->SetXY($xMm - $textWidth, $yMm);
                    $pdf->Cell($textWidth + 2, $fontSize * 0.35, $value, 0, 0, 'R');
                } else {
                    // Left alignment or width specified
                    $cellWidth = $wMm > 0 ? $wMm : 100;
                    $pdf->SetXY($xMm, $yMm);
                    $pdf->Cell($cellWidth, $fontSize * 0.35, $value, 0, 0, $align);
                }
                break;
        }
    }

    private function toUpperCase($text)
    {
        if (function_exists('mb_strtoupper')) {
            return mb_strtoupper($text, 'UTF-8');
        }
        return strtoupper($text);
    }

    private function getFieldValue($key, $student, $docType)
    {
        $map = [
            'student_name'   => $student['student_name'] ?? ($student['name'] ?? ''),
            'father_name'    => $student['father_name'] ?? '',
            'enrollment_no'  => $student['enrollment_no'] ?? '',
            'certificate_no' => $student['certificate_no'] ?? '',
            'course'         => $student['course'] ?? '',
            'branch_name'    => $student['branch_name'] ?? '',
            'branch_code'    => $student['branch_code'] ?? '',
            'duration'       => $student['duration'] ?? '',
            'date_of_issue'  => $student['date_of_issue'] ?? date('d/m/Y'),
            'from_date'      => $student['from_date'] ?? '',
            'to_date'        => $student['to_date'] ?? '',
            'percentage'     => $student['percentage'] ?? '',
            'grade'          => $student['grade'] ?? '',
        ];
        return $map[$key] ?? '';
    }

    private function renderPhoto($pdf, $xMm, $yMm, $wMm, $hMm, $student, $border, $isPreview)
    {
        $photoPath = '';
        $finalW = $wMm > 0 ? $wMm : 30;
        $finalH = $hMm > 0 ? $hMm : 40;

        // For preview: try test photo from session FIRST (priority)
        if ($isPreview) {
            $testPhotoPath = $this->getTestPhotoPath();
            if ($testPhotoPath && file_exists($testPhotoPath)) {
                $photoPath = $testPhotoPath;
            }
        }

        // Try student photo
        if (empty($photoPath) && !empty($student['photo'])) {
            $photoPath = $this->resolvePhotoPath($student['photo']);
        }

        // Fallback: placeholder
        if (empty($photoPath) || !file_exists($photoPath)) {
            $placeholderPath = $this->createPhotoPlaceholder($finalW, $finalH);
            if ($placeholderPath) {
                $photoPath = $placeholderPath;
            }
        }

        if ($photoPath && file_exists($photoPath)) {
            // ✅ FIX: For bulk PDFs, copy to a unique temp file to avoid FPDF caching issues
            $uniqueId = uniqid('photo_', true);
            $tempPhotoPath = sys_get_temp_dir() . '/' . $uniqueId . '.' . pathinfo($photoPath, PATHINFO_EXTENSION);
            if (@copy($photoPath, $tempPhotoPath)) {
                $photoPath = $tempPhotoPath;
            }

            try {
                $pdf->Image($photoPath, $xMm, $yMm, $finalW, $finalH);
                if ($border) {
                    $pdf->Rect($xMm, $yMm, $finalW, $finalH);
                }
            } catch (\Exception $e) {
                $pdf->Rect($xMm, $yMm, $finalW, $finalH);
                $pdf->SetXY($xMm, $yMm + ($finalH / 2) - 2);
                $pdf->SetFont('Arial', '', 8);
                $pdf->Cell($finalW, 4, 'PHOTO', 0, 0, 'C');
            }

            // Cleanup temp file if we created one
            if (isset($tempPhotoPath) && file_exists($tempPhotoPath)) {
                @unlink($tempPhotoPath);
            }
        }
    }

    /**
     * Resolve photo path — try multiple locations and return ABSOLUTE path
     *
     * Photos may be stored as:
     *   uploads/students/xxx.jpg          (relative, no leading slash)
     *   /uploads/students/xxx.jpg         (absolute from web root)
     *   /home/advaysin/public_html/gmind/uploads/students/xxx.jpg  (full absolute)
     *
     * Returns: Absolute filesystem path if found, original path otherwise
     */
    private function resolvePhotoPath(string $photoPath): string
    {
        // Skip empty
        if (empty($photoPath)) {
            return $photoPath;
        }

        // If it's already an absolute path that exists, return it
        if ($photoPath[0] === '/' && file_exists($photoPath)) {
            return realpath($photoPath);
        }

        // Build candidates — try ABSOLUTE paths only (FPDF needs absolute paths)
        $docRoot = $_SERVER['DOCUMENT_ROOT'] ?? '/home/advaysin/public_html';
        $scriptDir = dirname(dirname(dirname(__DIR__)));  // /home/advaysin/public_html/gmind

        $candidates = [];

        // If path starts with /, try with various roots
        if ($photoPath[0] === '/') {
            $candidates[] = $docRoot . $photoPath;                          // /home/advaysin/public_html + /uploads/...
            $candidates[] = $docRoot . '/gmind' . $photoPath;               // /home/advaysin/public_html/gmind + /uploads/...
            $candidates[] = '/home/advaysin/public_html' . $photoPath;
            $candidates[] = '/home/advaysin/public_html/gmind' . $photoPath;
            $candidates[] = $scriptDir . $photoPath;
        } else {
            // Relative path (no leading /) — prepend with various roots
            $candidates[] = $docRoot . '/' . $photoPath;
            $candidates[] = $docRoot . '/gmind/' . $photoPath;
            $candidates[] = '/home/advaysin/public_html/' . $photoPath;
            $candidates[] = '/home/advaysin/public_html/gmind/' . $photoPath;
            $candidates[] = $scriptDir . '/' . $photoPath;
            $candidates[] = $scriptDir . '/public/' . $photoPath;
            $candidates[] = dirname(dirname(dirname(dirname(__DIR__)))) . '/' . $photoPath;
            $candidates[] = dirname(dirname(dirname(dirname(__DIR__)))) . '/gmind/' . $photoPath;
        }

        // Try each candidate
        foreach ($candidates as $c) {
            if (file_exists($c)) {
                return realpath($c);  // Return absolute canonical path
            }
        }

        // Last resort: try relative path from current working directory
        // (this works for file_exists but NOT for FPDF Image — that's why we tried absolute first)
        if (file_exists($photoPath)) {
            return realpath($photoPath);
        }

        // Not found — return original (placeholder will be used)
        return $photoPath;
    }

    /**
     * Get test photo path — uses SAME session name as main app
     */
    private function getTestPhotoPath()
    {
        if (session_status() === PHP_SESSION_NONE) {
            if (defined('SESSION_NAME')) {
                session_name(SESSION_NAME);
            }
            session_start();
        }

        if (!empty($_SESSION['test_photo_path']) && file_exists($_SESSION['test_photo_path'])) {
            return $_SESSION['test_photo_path'];
        }

        // Fallback: check temp file by session id
        $tempFile = sys_get_temp_dir() . '/pattern_test_photo_' . session_id() . '.jpg';
        if (file_exists($tempFile) && (time() - filemtime($tempFile)) < 3600) {
            return $tempFile;
        }

        return null;
    }

    private function renderQRCode($pdf, $xMm, $yMm, $wMm, $hMm, $student, $isPreview)
    {
        $qrData = $student['qr_data'] ?? '';
        if (empty($qrData)) {
            $qrData = "Name: " . ($student['student_name'] ?? '') . "\n";
            $qrData .= "Enrollment: " . ($student['enrollment_no'] ?? '') . "\n";
            $qrData .= "Certificate: " . ($student['certificate_no'] ?? '');
        }

        $size = max($wMm, $hMm);
        if ($size <= 0) $size = 25;
        if ($size > 80) $size = 80;
        if ($size < 10) $size = 10;

        $qrTempPath = $this->downloadQRCode($qrData, $size);

        if ($qrTempPath && file_exists($qrTempPath)) {
            // ✅ FIX: Copy to unique temp file to avoid FPDF caching issues in bulk PDFs
            $uniqueId = uniqid('qr_', true);
            $uniqueQrPath = sys_get_temp_dir() . '/' . $uniqueId . '.png';
            if (@copy($qrTempPath, $uniqueQrPath)) {
                $pdf->Image($uniqueQrPath, $xMm, $yMm, $size, $size);
                @unlink($uniqueQrPath);
            } else {
                $pdf->Image($qrTempPath, $xMm, $yMm, $size, $size);
            }
        }
    }

    private function renderSubjectMarksTable($pdf, $xMm, $yMm, $wMm, $student, $fontSize)
    {
        $subjects = $student['subjects'] ?? [];
        if (empty($subjects)) return;

        $tableWidth = $wMm > 0 ? $wMm : 150;
        $rowHeight = 7;
        $headerHeight = 8;

        $colModule   = $tableWidth * 0.12;
        $colSubject  = $tableWidth * 0.38;
        $colMax      = $tableWidth * 0.12;
        $colObtained = $tableWidth * 0.14;
        $colPercent  = $tableWidth * 0.12;
        $colGrade    = $tableWidth * 0.12;

        $pdf->SetXY($xMm, $yMm);
        $pdf->SetFont('Arial', 'B', $fontSize);
        $pdf->SetFillColor(220, 220, 220);
        $pdf->SetTextColor(0, 0, 0);

        $pdf->Cell($colModule, $headerHeight, 'MODULE', 1, 0, 'C', true);
        $pdf->Cell($colSubject, $headerHeight, 'SUBJECT', 1, 0, 'C', true);
        $pdf->Cell($colMax, $headerHeight, 'MAX', 1, 0, 'C', true);
        $pdf->Cell($colObtained, $headerHeight, 'OBTAINED', 1, 0, 'C', true);
        $pdf->Cell($colPercent, $headerHeight, '%', 1, 0, 'C', true);
        $pdf->Cell($colGrade, $headerHeight, 'GRADE', 1, 0, 'C', true);
        $pdf->Ln();

        $pdf->SetFont('Arial', '', $fontSize);
        $y = $yMm + $headerHeight;

        $totalMax = 0;
        $totalObtained = 0;

        foreach ($subjects as $idx => $subj) {
            $max = (int)($subj['max'] ?? 100);
            $obtained = (int)($subj['obtained'] ?? 0);
            $percent = $max > 0 ? round(($obtained / $max) * 100, 1) : 0;
            $grade = $subj['grade'] ?? $this->calculateGrade($percent);

            $totalMax += $max;
            $totalObtained += $obtained;

            $pdf->SetXY($xMm, $y);
            $subjName = $this->toUpperCase($subj['name'] ?? '');
            $pdf->Cell($colModule, $rowHeight, $idx + 1, 1, 0, 'C');
            $pdf->Cell($colSubject, $rowHeight, $subjName, 1, 0, 'L');
            $pdf->Cell($colMax, $rowHeight, $max, 1, 0, 'C');
            $pdf->Cell($colObtained, $rowHeight, $obtained, 1, 0, 'C');
            $pdf->Cell($colPercent, $rowHeight, $percent . '%', 1, 0, 'C');
            $pdf->Cell($colGrade, $rowHeight, $grade, 1, 0, 'C');
            $pdf->Ln();
            $y += $rowHeight;
        }

        $totalPercent = $totalMax > 0 ? round(($totalObtained / $totalMax) * 100, 1) : 0;
        $totalGrade = $this->calculateGrade($totalPercent);

        $pdf->SetXY($xMm, $y);
        $pdf->SetFont('Arial', 'B', $fontSize);
        $pdf->SetFillColor(240, 240, 240);
        $pdf->Cell($colModule + $colSubject, $rowHeight, 'TOTAL', 1, 0, 'R', true);
        $pdf->Cell($colMax, $rowHeight, $totalMax, 1, 0, 'C', true);
        $pdf->Cell($colObtained, $rowHeight, $totalObtained, 1, 0, 'C', true);
        $pdf->Cell($colPercent, $rowHeight, $totalPercent . '%', 1, 0, 'C', true);
        $pdf->Cell($colGrade, $rowHeight, $totalGrade, 1, 0, 'C', true);
    }

    private function calculateGrade($percent)
    {
        $percent = (float)$percent;
        foreach ($this->gradeScale as $g) {
            if ($percent >= $g['min']) return $g['grade'];
        }
        return 'F';
    }

    private function sortFieldsForRender($fields)
    {
        $images = [];
        $texts = [];
        $tables = [];

        foreach ($fields as $f) {
            $key = $f['field_key'] ?? '';
            if (in_array($key, ['photo', 'qr_code'])) {
                $images[] = $f;
            } elseif ($key === 'subject_marks') {
                $tables[] = $f;
            } else {
                $texts[] = $f;
            }
        }

        return array_merge($images, $texts, $tables);
    }

    private function hexToRgb($hex)
    {
        $hex = ltrim($hex, '#');
        if (strlen($hex) === 3) {
            $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
        }
        if (strlen($hex) !== 6) return [0, 0, 0];
        return [
            hexdec(substr($hex, 0, 2)),
            hexdec(substr($hex, 2, 2)),
            hexdec(substr($hex, 4, 2)),
        ];
    }

    private function createPhotoPlaceholder($wMm, $hMm)
    {
        $tempDir = sys_get_temp_dir();
        $path = $tempDir . '/photo_placeholder_' . time() . '.png';
        $w = 150;
        $h = (int)($w * ($hMm / $wMm));
        if ($h < 50) $h = 200;
        $img = imagecreatetruecolor($w, $h);
        $bg = imagecolorallocate($img, 240, 240, 240);
        $txt = imagecolorallocate($img, 150, 150, 150);
        imagefilledrectangle($img, 0, 0, $w, $h, $bg);
        $personColor = imagecolorallocate($img, 200, 200, 200);
        $headCx = (int)($w / 2);
        $headCy = (int)($h * 0.3);
        $headW  = (int)($w * 0.2);
        $headH  = (int)($h * 0.15);
        imagefilledellipse($img, $headCx, $headCy, $headW, $headH, $personColor);
        $bodyX1 = (int)($w * 0.35);
        $bodyY1 = (int)($h * 0.45);
        $bodyX2 = (int)($w * 0.65);
        $bodyY2 = (int)($h * 0.95);
        imagefilledrectangle($img, $bodyX1, $bodyY1, $bodyX2, $bodyY2, $personColor);
        imagestring($img, 3, (int)($w / 2) - 30, $h - 25, 'PHOTO', $txt);
        imagepng($img, $path);
        imagedestroy($img);
        return $path;
    }

    private function downloadQRCode($data, $sizeMm)
    {
        $tempDir = sys_get_temp_dir();
        $hash = md5($data);
        $path = $tempDir . '/qr_' . $hash . '.png';

        if (file_exists($path) && (time() - filemtime($path)) < 3600) {
            return $path;
        }

        $url = 'https://quickchart.io/qr?text=' . urlencode($data) . '&size=300&margin=2';

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        $imgData = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode === 200 && $imgData) {
            file_put_contents($path, $imgData);
            return $path;
        }

        return $this->generateQRLocally($data, $path);
    }

    private function generateQRLocally($data, $path)
    {
        $size = 200;
        $img = imagecreatetruecolor($size, $size);
        $white = imagecolorallocate($img, 255, 255, 255);
        $black = imagecolorallocate($img, 0, 0, 0);
        imagefilledrectangle($img, 0, 0, $size, $size, $white);
        imagerectangle($img, 0, 0, $size-1, $size-1, $black);
        imagestring($img, 2, 10, 10, 'QR', $black);
        imagestring($img, 1, 10, $size - 20, substr($data, 0, 30), $black);
        imagepng($img, $path);
        imagedestroy($img);
        return $path;
    }

    public function generate(array $params)
    {
        $template = $params['template'] ?? [];
        $fields = $params['fields'] ?? [];
        $student = $params['student'] ?? [];
        $docType = $params['_doc_type'] ?? ($params['doc_type'] ?? 'certificate');
        $bgPath = $params['bg_path'] ?? '';

        $pattern = [
            'page_width'  => $template['page_width']  ?? 595,
            'page_height' => $template['page_height'] ?? 842,
        ];

        return $this->generateFromPattern([
            'pattern'    => $pattern,
            'fields'     => $fields,
            'student'    => $student,
            'doc_type'   => $docType,
            'bg_path'    => $bgPath,
            'is_preview' => false
        ]);
    }

    /**
     * Generate a verification hash for a certificate/marksheet.
     *
     * This hash is used to verify document authenticity when scanned via QR code
     * or accessed via the public verification portal (/verify/{documentNumber}).
     *
     * NOTE: This is a STATIC method so it can be called without instantiating
     * the DocumentGenerator class (e.g., DocumentGenerator::generateVerificationHash($data))
     *
     * Accepts BOTH:
     *   - int  → certificate ID (will hash the ID + salt)
     *   - array → certificate data fields (certificate_no, enrollment_no, etc.)
     *
     * @param mixed $data Certificate ID (int) OR certificate data (array)
     * @return string 64-character SHA-256 hash
     */
    public static function generateVerificationHash($data): string
    {
        // Add a salt for security (can be moved to config if needed)
        $salt = 'gmind_cert_verify_2025';

        // If int is passed (certificate ID), hash it directly
        if (is_int($data) || is_string($data)) {
            $string = 'CERT_ID:' . $data . '|' . $salt;
            return hash('sha256', $string);
        }

        // If array is passed, build a deterministic string from key data points
        if (is_array($data)) {
            $parts = [
                $data['certificate_no'] ?? '',
                $data['enrollment_no']  ?? '',
                $data['student_name']   ?? '',
                $data['course']         ?? '',
                $data['date_of_issue']  ?? '',
            ];
            $string = implode('|', $parts) . '|' . $salt;
            return hash('sha256', $string);
        }

        // Fallback: hash whatever was passed
        return hash('sha256', 'UNKNOWN:' . serialize($data) . '|' . $salt);
    }

    /**
     * Generate a short verification code (8 chars) — useful for QR codes
     */
    public static function generateShortVerificationCode($data): string
    {
        $hash = self::generateVerificationHash($data);
        return strtoupper(substr($hash, 0, 8));
    }

    /**
     * Build verification URL for QR code
     */
    public static function buildVerificationUrl(string $documentNumber, string $hash = ''): string
    {
        $baseUrl = ($_SERVER['REQUEST_SCHEME'] ?? 'https') . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost');
        $basePath = defined('BASE_PATH') ? BASE_PATH : '/gmind';
        $url = $baseUrl . $basePath . '/verify/' . urlencode($documentNumber);
        if (!empty($hash)) {
            $url .= '?h=' . substr($hash, 0, 16);
        }
        return $url;
    }
}
