<?php
/**
 * Branch Fee Controller — Complete Fee Collection System
 *
 * Features:
 *   - index()         → Fee dashboard with stats (Expected, Collected, Due)
 *   - collect()       → Fee collection form (auto-fills course fee)
 *   - store()         → Save fee payment + update student fee_paid/fee_due
 *   - receipt()       → Generate PDF receipt with branch logo + QR code
 *   - reports()       → Course-wise fee report with filter
 *   - exportPdf()     → Export fee report as PDF
 *
 * URL prefix: /branch/fees/*
 */

namespace BranchAdmin\Controllers;

use BranchAdmin\Controllers\BranchAdminController;

class BranchFeeController extends BranchAdminController
{
    /**
     * Fee Dashboard — shows totals + recent payments.
     */
    public function index(): void
    {
        requireBranchAdmin();
        $branchId = $this->getBranchId();

        // Compute totals from students + fee_payments
        $students = $this->getBranchStudentsWithFees($branchId);

        $totalExpected = 0;
        $totalCollected = 0;
        $totalDue = 0;
        foreach ($students as $s) {
            $totalExpected += (float)($s['course_fee'] ?? 0);
            $totalCollected += (float)($s['fee_paid'] ?? 0);
            $totalDue += (float)($s['fee_due'] ?? 0);
        }

        // Recent payments
        $recentPayments = [];
        try {
            $stmt = $this->db->prepare(
                "SELECT fp.*, s.enrollment_no, u.name AS student_name, c.name AS course_name
                 FROM fee_payments fp
                 INNER JOIN students s ON fp.student_id = s.id
                 INNER JOIN users u ON s.user_id = u.id
                 LEFT JOIN courses c ON s.course_id = c.id
                 WHERE s.branch_id = :bid
                 ORDER BY fp.payment_date DESC, fp.id DESC
                 LIMIT 10"
            );
            $stmt->execute([':bid' => $branchId]);
            $recentPayments = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
        } catch (\Throwable $e) {}

        // Students with due fees
        $studentsWithDue = [];
        foreach ($students as $s) {
            if ((float)($s['fee_due'] ?? 0) > 0) {
                $studentsWithDue[] = $s;
            }
        }

        $this->branchAdminView('fees/index', [
            'title'             => 'Fee Management',
            'currentPage'       => 'fees',
            'totalExpected'     => $totalExpected,
            'totalCollected'    => $totalCollected,
            'totalDue'          => $totalDue,
            'recentPayments'    => $recentPayments,
            'studentsWithDue'   => $studentsWithDue,
            'csrfToken'         => $this->csrfToken ?? '',
        ]);
    }

    /**
     * Show fee collection form for a specific student.
     */
    public function collect(array $params): void
    {
        requireBranchAdmin();
        $branchId = $this->getBranchId();
        $id = (int)($params['id'] ?? 0);

        $stmt = $this->db->prepare(
            "SELECT s.*, u.name, u.email, u.phone, c.name AS course_name, c.duration
             FROM students s
             INNER JOIN users u ON s.user_id = u.id
             LEFT JOIN courses c ON s.course_id = c.id
             WHERE s.id = :sid AND s.branch_id = :bid LIMIT 1"
        );
        $stmt->execute([':sid' => $id, ':bid' => $branchId]);
        $student = $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;

        if (!$student) {
            flash('error', 'Student not found.');
            redirect(BASE_PATH . '/branch/students');
            return;
        }

        // Get course fee (branch-specific first, then global)
        $courseFee = $this->getCourseFee($branchId, (int)($student['course_id'] ?? 0));

        // Get total paid
        $totalPaid = $this->getStudentTotalPaid($id);
        $feeDue = max(0, $courseFee - $totalPaid);

        $student['course_fee']  = $courseFee;
        $student['fee_paid']    = $totalPaid;
        $student['fee_due']     = $feeDue;

        // Payment history
        $stmt = $this->db->prepare("SELECT * FROM fee_payments WHERE student_id = :sid ORDER BY payment_date DESC, id DESC");
        $stmt->execute([':sid' => $id]);
        $payments = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];

        $this->branchAdminView('fees/collect', [
            'title'       => 'Collect Fee',
            'currentPage' => 'fees',
            'student'     => $student,
            'payments'    => $payments,
            'csrfToken'   => $this->csrfToken ?? '',
        ]);
    }

    /**
     * Store a fee payment.
     */
    public function store(array $params): void
    {
        requireBranchAdmin();
        $this->validateCsrf();
        $branchId = $this->getBranchId();
        $id = (int)($params['id'] ?? 0);

        if (!$id) {
            flash('error', 'Invalid student ID.');
            redirect(BASE_PATH . '/branch/fees');
            return;
        }

        // Verify student belongs to this branch
        $stmt = $this->db->prepare("SELECT s.id, s.course_id, u.name FROM students s INNER JOIN users u ON s.user_id = u.id WHERE s.id = :sid AND s.branch_id = :bid LIMIT 1");
        $stmt->execute([':sid' => $id, ':bid' => $branchId]);
        $student = $stmt->fetch(\PDO::FETCH_ASSOC);
        if (!$student) {
            flash('error', 'Student not found.');
            redirect(BASE_PATH . '/branch/fees');
            return;
        }

        $amount        = (float)($_POST['amount'] ?? 0);
        $paymentMode   = trim($_POST['payment_mode'] ?? 'cash');
        $transactionId = trim($_POST['transaction_id'] ?? '');
        $paymentDate   = trim($_POST['payment_date'] ?? date('Y-m-d'));
        $remarks       = trim($_POST['remarks'] ?? '');

        if ($amount <= 0) {
            flash('error', 'Amount must be greater than zero.');
            redirect(BASE_PATH . '/branch/fees/collect/' . $id);
            return;
        }

        // ✅ FIX: Check current balance due and prevent overpayment
        $courseFee  = $this->getCourseFee($branchId, (int)$student['course_id']);
        $totalPaid  = $this->getStudentTotalPaid($id);
        $balanceDue = max(0, $courseFee - $totalPaid);

        if ($balanceDue <= 0) {
            flash('error', '❌ Fee is already FULLY PAID for this student. No more collection allowed.');
            redirect(BASE_PATH . '/branch/fees');
            return;
        }

        if ($amount > $balanceDue) {
            flash('error', "❌ Amount (₹" . number_format($amount, 2) . ") exceeds balance due (₹" . number_format($balanceDue, 2) . "). Maximum allowed: ₹" . number_format($balanceDue, 2));
            redirect(BASE_PATH . '/branch/fees/collect/' . $id);
            return;
        }

        $this->ensureFeePaymentColumns();

        // Generate receipt number
        $receiptNo = 'RCP-' . date('Y') . '-' . str_pad((string)rand(100000, 999999), 6, '0', STR_PAD_LEFT);

        try {
            // Insert fee payment — include branch_id (FK constraint requires it)
            $cols = $this->getTableColumns('fee_payments');
            $data = [
                'student_id'      => $id,
                'branch_id'       => $branchId,
                'amount'          => $amount,
                'payment_mode'    => $paymentMode,
                'transaction_id'  => $transactionId ?: null,
                'payment_date'    => $paymentDate ?: date('Y-m-d'),
                'remarks'         => $remarks,
                'receipt_no'      => $receiptNo,
                'created_at'      => date('Y-m-d H:i:s'),
            ];
            $insertData = [];
            foreach ($data as $k => $v) {
                if (in_array($k, $cols, true)) $insertData[$k] = $v;
            }
            $keys    = array_keys($insertData);
            $colList = implode(', ', array_map(fn($c) => "`{$c}`", $keys));
            $phList  = implode(', ', array_map(fn($c) => ":{$c}", $keys));
            $sql     = "INSERT INTO fee_payments ({$colList}) VALUES ({$phList})";
            $params2 = [];
            foreach ($insertData as $k => $v) $params2[":{$k}"] = $v;
            $this->db->prepare($sql)->execute($params2);
            $paymentId = (int)$this->db->lastInsertId();

            // Update students.fee_paid + fee_due (if columns exist)
            $studentCols = $this->getTableColumns('students');
            $newTotalPaid = $this->getStudentTotalPaid($id);
            $newDue = max(0, $courseFee - $newTotalPaid);
            $previousPaid = max(0, $newTotalPaid - $amount);  // ✅ FIX: Define previousPaid for email

            if (in_array('fee_paid', $studentCols, true)) {
                $this->db->prepare("UPDATE students SET fee_paid = :paid, fee_due = :due, updated_at = NOW() WHERE id = :sid")
                    ->execute([':paid' => $newTotalPaid, ':due' => $newDue, ':sid' => $id]);
            }

            $this->auditLog('fee_collected', 'fee_payments', $paymentId, "Collected ₹{$amount} from student ID {$id} ({$student['name']}). Receipt: {$receiptNo}");

            // ═══════════════════════════════════════════════════════════════════
            // ✅ SEND FEE RECEIPT EMAIL TO STUDENT
            // ═══════════════════════════════════════════════════════════════════
            $emailSent = false;
            $emailError = '';
            try {
                $helper = new \App\Helpers\EmailHelper($this->db);

                // Fetch branch name
                $branchName = 'GMIND INSTITUTE';
                try {
                    $stmtB = $this->db->prepare("SELECT name FROM branches WHERE id = :bid LIMIT 1");
                    $stmtB->execute([':bid' => $branchId]);
                    $branchName = $stmtB->fetchColumn() ?: $branchName;
                } catch (\Throwable $e) {}

                // Fetch student details
                $studentEmail  = '';
                $studentName   = '';
                $enrollmentNo  = '';
                $courseName    = '';
                $studentPhone  = '';
                $fatherName    = '';
                try {
                    $stmtS = $this->db->prepare(
                        "SELECT u.email, u.name, u.phone, s.enrollment_no, s.father_name, c.name AS course_name
                         FROM students s
                         INNER JOIN users u ON s.user_id = u.id
                         LEFT JOIN courses c ON s.course_id = c.id
                         WHERE s.id = :sid LIMIT 1"
                    );
                    $stmtS->execute([':sid' => $id]);
                    $sRow = $stmtS->fetch(\PDO::FETCH_ASSOC);
                    if ($sRow) {
                        $studentEmail  = $sRow['email'] ?? '';
                        $studentName   = $sRow['name'] ?? '';
                        $enrollmentNo  = $sRow['enrollment_no'] ?? '';
                        $courseName    = $sRow['course_name'] ?? '';
                        $studentPhone  = $sRow['phone'] ?? '';
                        $fatherName    = $sRow['father_name'] ?? '';
                    }
                } catch (\Throwable $e) {}

                if (!empty($studentEmail) && $helper->isConfigured()) {
                    $emailSubject = "Fee Receipt — {$receiptNo} ({$branchName})";
                    $emailBody = $this->buildFeeReceiptEmailHtml([
                        'student_name'   => $studentName,
                        'enrollment_no'  => $enrollmentNo,
                        'course_name'    => $courseName,
                        'father_name'    => $fatherName,
                        'phone'          => $studentPhone,
                        'branch_name'    => $branchName,
                        'receipt_no'     => $receiptNo,
                        'amount'         => $amount,
                        'payment_mode'   => $paymentMode,
                        'transaction_id' => $transactionId,
                        'payment_date'   => $paymentDate ?: date('Y-m-d'),
                        'course_fee'     => $courseFee,
                        'previous_paid'  => $previousPaid,
                        'total_paid'     => $newTotalPaid,
                        'balance_due'    => $newDue,
                    ]);
                    $emailSent = $helper->send($studentEmail, $studentName, $emailSubject, $emailBody);
                    if (!$emailSent) {
                        $emailError = $helper->getLastError();
                    }
                } elseif (empty($studentEmail)) {
                    $emailError = 'Student has no email address';
                } else {
                    $emailError = 'SMTP not configured';
                }
            } catch (\Throwable $e) {
                $emailError = $e->getMessage();
                error_log('Fee receipt email exception: ' . $e->getMessage());
            }

            // Build success message
            $successMsg = "✅ Fee of ₹" . number_format($amount, 2) . " collected successfully! Receipt No: {$receiptNo}.";
            if ($emailSent) {
                $successMsg .= " 📧 Receipt emailed to student.";
            } else {
                $successMsg .= " ⚠️ Email not sent ({$emailError}).";
            }
            flash('success', $successMsg);

            // ✅ REDIRECT TO PREVIEW PAGE (not direct PDF download)
            redirect(BASE_PATH . '/branch/fees/receipt-preview/' . $paymentId);
        } catch (\Throwable $e) {
            error_log('Fee store error: ' . $e->getMessage());
            flash('error', 'Failed to save payment: ' . $e->getMessage());
            redirect(BASE_PATH . '/branch/fees/collect/' . $id);
        }
    }

    /**
     * Build HTML email body for fee receipt.
     */
    private function buildFeeReceiptEmailHtml(array $data): string
    {
        $studentName   = htmlspecialchars($data['student_name'] ?? '', ENT_QUOTES);
        $enrollmentNo  = htmlspecialchars($data['enrollment_no'] ?? '', ENT_QUOTES);
        $courseName    = htmlspecialchars($data['course_name'] ?? '', ENT_QUOTES);
        $fatherName    = htmlspecialchars($data['father_name'] ?? '', ENT_QUOTES);
        $phone         = htmlspecialchars($data['phone'] ?? '', ENT_QUOTES);
        $branchName    = htmlspecialchars($data['branch_name'] ?? 'GMIND INSTITUTE', ENT_QUOTES);
        $receiptNo     = htmlspecialchars($data['receipt_no'] ?? '', ENT_QUOTES);
        $amount        = htmlspecialchars(number_format((float)($data['amount'] ?? 0), 2), ENT_QUOTES);
        $paymentMode   = htmlspecialchars(ucfirst($data['payment_mode'] ?? 'cash'), ENT_QUOTES);
        $transactionId = htmlspecialchars($data['transaction_id'] ?? '', ENT_QUOTES);
        $paymentDate   = htmlspecialchars($data['payment_date'] ?? date('Y-m-d'), ENT_QUOTES);
        $courseFee     = htmlspecialchars(number_format((float)($data['course_fee'] ?? 0), 2), ENT_QUOTES);
        $previousPaid  = htmlspecialchars(number_format((float)($data['previous_paid'] ?? 0), 2), ENT_QUOTES);
        $totalPaid     = htmlspecialchars(number_format((float)($data['total_paid'] ?? 0), 2), ENT_QUOTES);
        $balanceDue    = htmlspecialchars(number_format((float)($data['balance_due'] ?? 0), 2), ENT_QUOTES);
        $isFullPaid    = ((float)($data['balance_due'] ?? 0) <= 0);
        $balanceColor  = $isFullPaid ? '#16a34a' : '#dc2626';

        $fullPaidBanner = $isFullPaid
            ? '<div style="background:linear-gradient(135deg,#16a34a,#15803d);color:#fff;padding:14px;border-radius:6px;margin:16px 0;text-align:center;font-weight:700;">🎉 FULL FEE PAID — No dues remaining!</div>'
            : '';

        return <<<HTML
<!DOCTYPE html>
<html><head><meta charset="UTF-8"></head>
<body style="margin:0;padding:0;font-family:'Segoe UI',Arial,sans-serif;background:#f3f4f6;">
  <table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:20px;">
    <tr><td align="center">
      <table width="600" cellpadding="0" cellspacing="0" style="background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.06);">
        <tr><td style="background:linear-gradient(135deg,#16a085,#117a65);padding:24px 32px;text-align:center;">
          <h1 style="margin:0;color:#fff;font-size:1.6rem;">GMIND INSTITUTE</h1>
          <p style="margin:6px 0 0;color:rgba(255,255,255,0.9);font-size:0.88rem;">{$branchName}</p>
        </td></tr>
        <tr><td style="padding:32px;">
          <h2 style="margin:0 0 16px;color:#2c1e4a;font-size:1.3rem;">💰 Fee Receipt — {$receiptNo}</h2>
          <p style="margin:0 0 16px;color:#4b5563;font-size:0.95rem;line-height:1.6;">
            Dear <strong>{$studentName}</strong>,<br><br>
            We have received your fee payment. Below are the receipt details:
          </p>
          {$fullPaidBanner}
          <table width="100%" cellpadding="12" cellspacing="0" style="border:1px solid #e5e7eb;border-radius:8px;margin:16px 0;">
            <tr style="background:#f8f4ff;"><td style="color:#6c3fe8;font-weight:600;font-size:0.85rem;">Receipt No</td><td style="color:#2c1e4a;font-weight:700;">{$receiptNo}</td></tr>
            <tr><td style="color:#6c3fe8;font-weight:600;font-size:0.85rem;border-top:1px solid #e5e7eb;">Enrollment No</td><td style="color:#2c1e4a;border-top:1px solid #e5e7eb;">{$enrollmentNo}</td></tr>
            <tr><td style="color:#6c3fe8;font-weight:600;font-size:0.85rem;border-top:1px solid #e5e7eb;">Course</td><td style="color:#2c1e4a;border-top:1px solid #e5e7eb;">{$courseName}</td></tr>
            <tr><td style="color:#6c3fe8;font-weight:600;font-size:0.85rem;border-top:1px solid #e5e7eb;">Payment Date</td><td style="color:#2c1e4a;border-top:1px solid #e5e7eb;">{$paymentDate}</td></tr>
            <tr><td style="color:#6c3fe8;font-weight:600;font-size:0.85rem;border-top:1px solid #e5e7eb;">Payment Mode</td><td style="color:#2c1e4a;border-top:1px solid #e5e7eb;">{$paymentMode}</td></tr>
            <tr style="background:#dcfce7;"><td style="color:#16a34a;font-weight:700;font-size:0.95rem;border-top:1px solid #e5e7eb;">Amount Paid</td><td style="color:#16a34a;font-weight:700;font-size:1.1rem;border-top:1px solid #e5e7eb;">&#8377; {$amount}</td></tr>
          </table>
          <table width="100%" cellpadding="8" cellspacing="0" style="margin:16px 0;">
            <tr><td style="color:#6b7280;font-size:0.85rem;">Total Course Fee</td><td style="text-align:right;color:#2c1e4a;font-weight:600;">&#8377; {$courseFee}</td></tr>
            <tr><td style="color:#6b7280;font-size:0.85rem;">Previous Paid</td><td style="text-align:right;color:#2c1e4a;">&#8377; {$previousPaid}</td></tr>
            <tr><td style="color:#6b7280;font-size:0.85rem;">Total Paid Till Date</td><td style="text-align:right;color:#16a34a;font-weight:700;">&#8377; {$totalPaid}</td></tr>
            <tr><td style="color:#6b7280;font-size:0.85rem;font-weight:600;">Balance Due</td><td style="text-align:right;color:{$balanceColor};font-weight:700;">&#8377; {$balanceDue}</td></tr>
          </table>
          <div style="background:#fee2e2;border-left:4px solid #dc2626;padding:14px 18px;border-radius:6px;margin:20px 0;">
            <p style="margin:0;color:#b91c1c;font-size:0.85rem;"><strong>📌 Important:</strong></p>
            <ul style="margin:6px 0 0;padding-left:20px;color:#b91c1c;font-size:0.82rem;">
              <li>Fee once paid will NOT be refundable.</li>
              <li>Certificate will be issued only after full fee payment.</li>
              <li>Please retain this receipt for future reference.</li>
            </ul>
          </div>
          <p style="margin:24px 0 0;color:#4b5563;font-size:0.88rem;line-height:1.6;">
            If you have any questions, please contact your branch office.<br><br>
            Best regards,<br>
            <strong>{$branchName}</strong>
          </p>
        </td></tr>
        <tr><td style="background:#f8f9fa;padding:16px 32px;text-align:center;border-top:1px solid #e5e7eb;">
          <p style="margin:0;color:#9ca3af;font-size:0.75rem;">This is an automated email. Please do not reply. Generated on {$paymentDate}</p>
        </td></tr>
      </table>
    </td></tr>
  </table>
</body></html>
HTML;
    }

    /**
     * Fee Receipt Preview Page (HTML) with PDF download link.
     */
    public function receiptPreview(array $params): void
    {
        requireBranchAdmin();
        $branchId = $this->getBranchId();
        $paymentId = (int)($params['id'] ?? 0);

        if (!$paymentId) {
            flash('error', 'Invalid payment ID.');
            redirect(BASE_PATH . '/branch/fees');
            return;
        }

        // Load payment + student + course
        $stmt = $this->db->prepare(
            "SELECT fp.*, s.enrollment_no, s.admission_date, s.father_name, s.course_id,
                    u.name AS student_name, u.phone, u.email,
                    c.name AS course_name, c.duration,
                    b.*
             FROM fee_payments fp
             INNER JOIN students s ON fp.student_id = s.id
             INNER JOIN users u ON s.user_id = u.id
             LEFT JOIN courses c ON s.course_id = c.id
             LEFT JOIN branches b ON s.branch_id = b.id
             WHERE fp.id = :pid AND s.branch_id = :bid LIMIT 1"
        );
        $stmt->execute([':pid' => $paymentId, ':bid' => $branchId]);
        $payment = $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;

        if (!$payment) {
            flash('error', 'Payment not found.');
            redirect(BASE_PATH . '/branch/fees');
            return;
        }

        // Compute fee details
        $courseFee    = $this->getCourseFee($branchId, (int)($payment['course_id'] ?? 0));
        $totalPaid    = $this->getStudentTotalPaid((int)$payment['student_id']);
        $previousPaid = $totalPaid - (float)$payment['amount'];
        $balanceDue   = max(0, $courseFee - $totalPaid);
        $isFullPaid   = ($balanceDue <= 0);

        $this->branchAdminView('fees/receipt_preview', [
            'title'         => 'Fee Receipt Preview',
            'currentPage'   => 'fees',
            'payment'       => $payment,
            'courseFee'     => $courseFee,
            'previousPaid'  => $previousPaid,
            'totalPaid'     => $totalPaid,
            'balanceDue'    => $balanceDue,
            'isFullPaid'    => $isFullPaid,
            'studentId'     => (int)$payment['student_id'],
            'paymentId'     => $paymentId,
        ]);
    }

    /**
     * Generate PDF receipt for a payment.
     */
    public function receipt(array $params): void
    {
        requireBranchAdmin();
        $branchId = $this->getBranchId();
        $paymentId = (int)($params['id'] ?? 0);

        if (!$paymentId) {
            flash('error', 'Invalid payment ID.');
            redirect(BASE_PATH . '/branch/fees');
            return;
        }

        // Load payment + student + course — use b.* for branches (schema varies between installs)
        $stmt = $this->db->prepare(
            "SELECT fp.*, s.enrollment_no, s.admission_date, s.father_name, s.course_id,
                    u.name AS student_name, u.phone, u.email,
                    c.name AS course_name, c.duration,
                    b.*
             FROM fee_payments fp
             INNER JOIN students s ON fp.student_id = s.id
             INNER JOIN users u ON s.user_id = u.id
             LEFT JOIN courses c ON s.course_id = c.id
             LEFT JOIN branches b ON s.branch_id = b.id
             WHERE fp.id = :pid AND s.branch_id = :bid LIMIT 1"
        );
        $stmt->execute([':pid' => $paymentId, ':bid' => $branchId]);
        $payment = $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;

        if (!$payment) {
            flash('error', 'Payment not found.');
            redirect(BASE_PATH . '/branch/fees');
            return;
        }

        // Get total paid for this student (for "Previous Paid" line on receipt)
        $totalPaid = $this->getStudentTotalPaid((int)$payment['student_id']);
        $courseFee = $this->getCourseFee($branchId, (int)$payment['course_id'] ?? 0);
        $previousPaid = $totalPaid - (float)$payment['amount'];
        $balanceDue = max(0, $courseFee - $totalPaid);

        // Generate PDF
        $this->generateReceiptPdf([
            'payment'       => $payment,
            'course_fee'    => $courseFee,
            'previous_paid' => $previousPaid,
            'total_paid'    => $totalPaid,
            'balance_due'   => $balanceDue,
        ]);
    }

    /**
     * Fee Reports — course-wise breakdown with filter.
     */
    public function reports(): void
    {
        requireBranchAdmin();
        $branchId = $this->getBranchId();

        $courseFilter = trim($_GET['course_id'] ?? '');
        $dateFrom     = trim($_GET['date_from'] ?? '');
        $dateTo       = trim($_GET['date_to'] ?? '');

        $students = $this->getBranchStudentsWithFees($branchId, $courseFilter);

        // Group by course
        $byCourse = [];
        $grandStudents = 0;
        $grandFee = 0;
        $grandPaid = 0;
        $grandDue = 0;

        foreach ($students as $s) {
            $courseName = $s['course_name'] ?? '—';
            if (!isset($byCourse[$courseName])) {
                $byCourse[$courseName] = [
                    'course_name'   => $courseName,
                    'student_count' => 0,
                    'total_fee'     => 0,
                    'total_paid'    => 0,
                    'total_due'     => 0,
                ];
            }
            $byCourse[$courseName]['student_count']++;
            $byCourse[$courseName]['total_fee']  += (float)($s['course_fee']  ?? 0);
            $byCourse[$courseName]['total_paid'] += (float)($s['fee_paid']    ?? 0);
            $byCourse[$courseName]['total_due']  += (float)($s['fee_due']     ?? 0);

            $grandStudents++;
            $grandFee  += (float)($s['course_fee'] ?? 0);
            $grandPaid += (float)($s['fee_paid']   ?? 0);
            $grandDue  += (float)($s['fee_due']    ?? 0);
        }
        $feeByCourse = array_values($byCourse);

        // Get all courses for filter
        $courses = [];
        try {
            $courses = $this->db->query("SELECT id, name FROM courses WHERE active=1 ORDER BY name")->fetchAll(\PDO::FETCH_ASSOC) ?: [];
        } catch (\Throwable $e) {}

        $this->branchAdminView('fees/reports', [
            'title'        => 'Fee Reports',
            'currentPage'  => 'fees',
            'feeByCourse'  => $feeByCourse,
            'courses'      => $courses,
            'courseFilter' => $courseFilter,
            'dateFrom'     => $dateFrom,
            'dateTo'       => $dateTo,
            'grandStudents'=> $grandStudents,
            'grandFee'     => $grandFee,
            'grandPaid'    => $grandPaid,
            'grandDue'     => $grandDue,
            'csrfToken'    => $this->csrfToken ?? '',
        ]);
    }

    /**
     * Export fee report as PDF.
     */
    public function exportPdf(): void
    {
        requireBranchAdmin();
        $branchId = $this->getBranchId();

        $courseFilter = trim($_GET['course_id'] ?? '');
        $students = $this->getBranchStudentsWithFees($branchId, $courseFilter);

        // Group by course
        $byCourse = [];
        $grandStudents = 0;
        $grandFee = 0;
        $grandPaid = 0;
        $grandDue = 0;

        foreach ($students as $s) {
            $courseName = $s['course_name'] ?? '—';
            if (!isset($byCourse[$courseName])) {
                $byCourse[$courseName] = [
                    'course_name'   => $courseName,
                    'student_count' => 0,
                    'total_fee'     => 0,
                    'total_paid'    => 0,
                    'total_due'     => 0,
                ];
            }
            $byCourse[$courseName]['student_count']++;
            $byCourse[$courseName]['total_fee']  += (float)($s['course_fee']  ?? 0);
            $byCourse[$courseName]['total_paid'] += (float)($s['fee_paid']    ?? 0);
            $byCourse[$courseName]['total_due']  += (float)($s['fee_due']     ?? 0);

            $grandStudents++;
            $grandFee  += (float)($s['course_fee'] ?? 0);
            $grandPaid += (float)($s['fee_paid']   ?? 0);
            $grandDue  += (float)($s['fee_due']    ?? 0);
        }
        $feeByCourse = array_values($byCourse);

        // Get branch info
        $stmt = $this->db->prepare("SELECT * FROM branches WHERE id = :bid LIMIT 1");
        $stmt->execute([':bid' => $branchId]);
        $branch = $stmt->fetch(\PDO::FETCH_ASSOC) ?: [];

        $this->generateReportPdf([
            'feeByCourse'   => $feeByCourse,
            'students'      => $students,
            'branch'        => $branch,
            'grandStudents' => $grandStudents,
            'grandFee'      => $grandFee,
            'grandPaid'     => $grandPaid,
            'grandDue'      => $grandDue,
            'courseFilter'  => $courseFilter,
        ]);
    }

    // ════════════════════════════════════════════════════════════════════════
    // PRIVATE HELPERS
    // ════════════════════════════════════════════════════════════════════════

    /**
     * Get branch students with computed fee values.
     */
    private function getBranchStudentsWithFees(int $branchId, string $courseFilter = ''): array
    {
        $sql = "SELECT s.*, u.name, u.email, u.phone, c.name AS course_name, c.duration
                FROM students s
                INNER JOIN users u ON s.user_id = u.id
                LEFT JOIN courses c ON s.course_id = c.id
                WHERE s.branch_id = :bid";
        $params = [':bid' => $branchId];
        if ($courseFilter !== '') {
            $sql .= " AND s.course_id = :cid";
            $params[':cid'] = $courseFilter;
        }
        $sql .= " ORDER BY s.created_at DESC";

        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        $students = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];

        foreach ($students as &$s) {
            $courseFee = $this->getCourseFee($branchId, (int)($s['course_id'] ?? 0));
            $totalPaid = $this->getStudentTotalPaid((int)$s['id']);
            $s['course_fee'] = $courseFee;
            $s['fee_paid']   = $totalPaid;
            $s['fee_due']    = max(0, $courseFee - $totalPaid);
        }
        unset($s);
        return $students;
    }

    /**
     * Get course fee — branch-specific first, then global.
     */
    private function getCourseFee(int $branchId, int $courseId): float
    {
        // Branch-specific fee
        try {
            $stmt = $this->db->prepare("SELECT course_fee FROM branch_course_settings WHERE branch_id = :bid AND course_id = :cid LIMIT 1");
            $stmt->execute([':bid' => $branchId, ':cid' => $courseId]);
            $bFee = $stmt->fetchColumn();
            if ($bFee !== false && $bFee !== null && (float)$bFee > 0) {
                return (float)$bFee;
            }
        } catch (\Throwable $e) {}

        // Global fee from courses table
        try {
            $stmt = $this->db->prepare("SELECT fee FROM courses WHERE id = :cid LIMIT 1");
            $stmt->execute([':cid' => $courseId]);
            $gFee = $stmt->fetchColumn();
            if ($gFee !== false) return (float)$gFee;
        } catch (\Throwable $e) {}

        // Fallback: students.fee_amount
        return 0;
    }

    /**
     * Get total paid for a student from fee_payments table.
     */
    private function getStudentTotalPaid(int $studentId): float
    {
        try {
            $stmt = $this->db->prepare("SELECT COALESCE(SUM(amount), 0) FROM fee_payments WHERE student_id = :sid");
            $stmt->execute([':sid' => $studentId]);
            return (float)$stmt->fetchColumn();
        } catch (\Throwable $e) {
            // Fallback: try students.fee_paid column
            try {
                $stmt = $this->db->prepare("SELECT COALESCE(fee_paid, 0) FROM students WHERE id = :sid LIMIT 1");
                $stmt->execute([':sid' => $studentId]);
                return (float)$stmt->fetchColumn();
            } catch (\Throwable $e2) {
                return 0;
            }
        }
    }

    private function getTableColumns(string $table): array
    {
        try {
            $stmt = $this->db->prepare("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table");
            $stmt->execute([':table' => $table]);
            return array_map('strtolower', $stmt->fetchAll(\PDO::FETCH_COLUMN) ?: []);
        } catch (\Throwable $e) { return []; }
    }

    /**
     * Safe audit log — writes directly to audit_log table.
     * Silently skips on any error (audit log is non-critical).
     */
    private function auditLog(string $action, string $entityType = '', ?int $entityId = null, ?string $details = null): void
    {
        try {
            // Detect audit_log table columns (some installs may not have all columns)
            $cols = $this->getTableColumns('audit_log');

            $data = [
                'action'      => $action,
                'entity_type' => $entityType ?: null,
                'entity_id'   => $entityId,
                'details'     => $details,
                'ip_address'  => $_SERVER['REMOTE_ADDR'] ?? null,
                'created_at'  => date('Y-m-d H:i:s'),
            ];
            if (in_array('user_id', $cols, true))   $data['user_id']   = $_SESSION['user_id'] ?? null;
            if (in_array('branch_id', $cols, true)) $data['branch_id'] = $this->getBranchId() ?? null;

            $insertData = [];
            foreach ($data as $k => $v) {
                if (in_array($k, $cols, true)) $insertData[$k] = $v;
            }

            if (empty($insertData)) return; // No valid columns — skip silently

            $keys    = array_keys($insertData);
            $colList = implode(', ', array_map(fn($c) => "`{$c}`", $keys));
            $phList  = implode(', ', array_map(fn($c) => ":{$c}", $keys));
            $sql     = "INSERT INTO `audit_log` ({$colList}) VALUES ({$phList})";
            $params  = [];
            foreach ($insertData as $k => $v) $params[":{$k}"] = $v;
            $this->db->prepare($sql)->execute($params);
        } catch (\Throwable $e) {
            // Silently ignore — audit log is non-critical
            error_log('BranchFee auditLog failed: ' . $e->getMessage());
        }
    }

    private function ensureFeePaymentColumns(): void
    {
        $existing = $this->getTableColumns('fee_payments');
        $needed = [
            'receipt_no'     => "ADD COLUMN `receipt_no` VARCHAR(50) NULL DEFAULT NULL",
            'transaction_id' => "ADD COLUMN `transaction_id` VARCHAR(100) NULL DEFAULT NULL",
            'remarks'        => "ADD COLUMN `remarks` TEXT NULL DEFAULT NULL",
            'created_at'     => "ADD COLUMN `created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP",
        ];
        foreach ($needed as $col => $ddl) {
            if (!in_array($col, $existing, true)) {
                try { $this->db->exec("ALTER TABLE `fee_payments` {$ddl}"); } catch (\Throwable $e) {}
            }
        }

        // Ensure students table has fee_paid/fee_due columns
        $studentCols = $this->getTableColumns('students');
        $studentNeeded = [
            'fee_amount' => "ADD COLUMN `fee_amount` DECIMAL(10,2) NOT NULL DEFAULT 0",
            'fee_paid'   => "ADD COLUMN `fee_paid` DECIMAL(10,2) NOT NULL DEFAULT 0",
            'fee_due'    => "ADD COLUMN `fee_due` DECIMAL(10,2) NOT NULL DEFAULT 0",
        ];
        foreach ($studentNeeded as $col => $ddl) {
            if (!in_array($col, $studentCols, true)) {
                try { $this->db->exec("ALTER TABLE `students` {$ddl}"); } catch (\Throwable $e) {}
            }
        }
    }

    /**
     * Load FPDF library (try multiple paths).
     */
    private function loadFpdf(): bool
    {
        if (class_exists('FPDF')) return true;
        $candidates = [
            dirname(dirname(dirname(__DIR__))) . '/fpdf/fpdf.php',
            dirname(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 ($candidates as $path) {
            if (file_exists($path)) {
                require_once $path;
                return class_exists('FPDF');
            }
        }
        return false;
    }

    /**
     * Resolve an image path (try multiple locations).
     */
    private function resolveImagePath(string $path): ?string
    {
        if (empty($path)) return null;
        if ($path[0] === '/' && file_exists($path)) return realpath($path);

        $docRoot = $_SERVER['DOCUMENT_ROOT'] ?? '/home/advaysin/public_html';
        $candidates = [
            $docRoot . '/' . ltrim($path, '/'),
            $docRoot . '/gmind/' . ltrim($path, '/'),
            '/home/advaysin/public_html/' . ltrim($path, '/'),
            '/home/advaysin/public_html/gmind/' . ltrim($path, '/'),
            dirname(dirname(dirname(dirname(__DIR__)))) . '/' . ltrim($path, '/'),
        ];
        foreach ($candidates as $c) {
            if (file_exists($c)) return realpath($c);
        }
        return null;
    }

    /**
     * Generate the fee receipt PDF.
     */
    private function generateReceiptPdf(array $data): void
    {
        if (!$this->loadFpdf()) {
            flash('error', 'FPDF library not found. Please upload FPDF to /gmind/fpdf/fpdf.php');
            redirect(BASE_PATH . '/branch/fees');
            return;
        }

        $payment = $data['payment'];
        $courseFee = $data['course_fee'];
        $previousPaid = $data['previous_paid'];
        $totalPaid = $data['total_paid'];
        $balanceDue = $data['balance_due'];

        $pdf = new \FPDF('P', 'mm', 'A4');
        $pdf->AddPage();
        $pdf->SetMargins(15, 15, 15);
        $pdf->SetAutoPageBreak(true, 15);

        // ─── Header (Branch Logo + Name + Receipt Title) ─────────────────
        $logoPath = $this->resolveImagePath($payment['logo'] ?? '');
        $hasLogo = false;
        if ($logoPath) {
            try {
                $pdf->Image($logoPath, 15, 12, 25, 25);
                $hasLogo = true;
            } catch (\Throwable $e) {}
        }

        $pdf->SetFont('Arial', 'B', 18);
        $pdf->SetTextColor(44, 30, 74);
        $pdf->Cell(0, 10, strtoupper($payment['name'] ?? $payment['branch_name'] ?? 'GMIND INSTITUTE'), 0, 1, 'C');
        $pdf->SetFont('Arial', '', 9);
        $pdf->SetTextColor(100, 116, 139);

        // ✅ Build address safely — branches table schema varies between installs
        // Use b.* columns directly: address, city, state, pin_code (all optional)
        $addrParts = array_filter([
            $payment['address'] ?? '',
            $payment['city'] ?? '',
            $payment['state'] ?? '',
            $payment['pin_code'] ?? $payment['pin'] ?? '',
        ]);
        $branchAddr = implode(', ', $addrParts);
        if (!empty($branchAddr)) {
            $pdf->Cell(0, 5, $branchAddr, 0, 1, 'C');
        }

        // ✅ Phone + email (use b.* columns directly)
        $branchPhone = $payment['phone'] ?? $payment['branch_phone'] ?? '';
        $branchEmail = $payment['email'] ?? $payment['branch_email'] ?? '';
        if (!empty($branchPhone) || !empty($branchEmail)) {
            $contactLine = '';
            if (!empty($branchPhone)) $contactLine .= 'Phone: ' . $branchPhone;
            if (!empty($branchEmail)) $contactLine .= (!empty($branchPhone) ? '  |  ' : '') . 'Email: ' . $branchEmail;
            $pdf->Cell(0, 5, $contactLine, 0, 1, 'C');
        }
        $pdf->Ln(3);

        // Horizontal line
        $pdf->SetDrawColor(108, 63, 232);
        $pdf->SetLineWidth(0.8);
        $pdf->Line(15, $pdf->GetY(), 195, $pdf->GetY());
        $pdf->Ln(5);

        // Receipt title
        $pdf->SetFont('Arial', 'B', 16);
        $pdf->SetTextColor(22, 160, 133);
        $pdf->Cell(0, 10, 'FEE RECEIPT', 0, 1, 'C');
        $pdf->Ln(2);

        // Receipt No + Date
        $pdf->SetFont('Arial', 'B', 10);
        $pdf->SetTextColor(44, 30, 74);
        $pdf->Cell(95, 7, 'Receipt No: ' . ($payment['receipt_no'] ?? 'RCP-' . $payment['id']), 0, 0);
        $pdf->Cell(95, 7, 'Date: ' . date('d M Y', strtotime($payment['payment_date'] ?? date('Y-m-d'))), 0, 1, 'R');
        $pdf->Ln(3);

        // Student details box
        $pdf->SetFillColor(248, 250, 252);
        $pdf->Rect(15, $pdf->GetY(), 180, 38, 'F');
        $pdf->SetY($pdf->GetY() + 3);

        $pdf->SetFont('Arial', 'B', 10);
        $pdf->SetTextColor(44, 30, 74);
        $pdf->Cell(45, 6, 'Student Name:', 0, 0);
        $pdf->SetFont('Arial', '', 10);
        $pdf->Cell(50, 6, $payment['student_name'] ?? '', 0, 0);
        $pdf->SetFont('Arial', 'B', 10);
        $pdf->Cell(35, 6, 'Enrollment No:', 0, 0);
        $pdf->SetFont('Arial', '', 10);
        $pdf->Cell(50, 6, $payment['enrollment_no'] ?? '', 0, 1);

        $pdf->SetFont('Arial', 'B', 10);
        $pdf->Cell(45, 6, 'Course:', 0, 0);
        $pdf->SetFont('Arial', '', 10);
        $pdf->Cell(50, 6, $payment['course_name'] ?? '—', 0, 0);
        $pdf->SetFont('Arial', 'B', 10);
        $pdf->Cell(35, 6, "Father's Name:", 0, 0);
        $pdf->SetFont('Arial', '', 10);
        $pdf->Cell(50, 6, $payment['father_name'] ?? '—', 0, 1);

        $pdf->SetFont('Arial', 'B', 10);
        $pdf->Cell(45, 6, 'Phone:', 0, 0);
        $pdf->SetFont('Arial', '', 10);
        $pdf->Cell(50, 6, $payment['phone'] ?? '—', 0, 0);
        $pdf->SetFont('Arial', 'B', 10);
        $pdf->Cell(35, 6, 'Branch Code:', 0, 0);
        $pdf->SetFont('Arial', '', 10);
        $pdf->Cell(50, 6, $payment['branch_code'] ?? '—', 0, 1);
        $pdf->Ln(8);

        // Fee breakdown table
        $pdf->SetFillColor(108, 63, 232);
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetFont('Arial', 'B', 10);
        $pdf->Cell(120, 8, 'DESCRIPTION', 1, 0, 'L', true);
        $pdf->Cell(60, 8, 'AMOUNT (Rs.)', 1, 1, 'R', true);

        $pdf->SetTextColor(44, 30, 74);
        $pdf->SetFont('Arial', '', 10);
        $pdf->Cell(120, 8, 'Total Course Fee', 1, 0);
        $pdf->Cell(60, 8, number_format($courseFee, 2), 1, 1, 'R');

        $pdf->Cell(120, 8, 'Previous Paid Amount', 1, 0);
        $pdf->Cell(60, 8, number_format($previousPaid, 2), 1, 1, 'R');

        $pdf->SetFont('Arial', 'B', 10);
        $pdf->SetFillColor(220, 252, 231);
        $pdf->Cell(120, 8, 'Current Payment (' . ucfirst($payment['payment_mode'] ?? 'cash') . ')', 1, 0, true);
        $pdf->Cell(60, 8, number_format((float)$payment['amount'], 2), 1, 1, 'R', true);

        $pdf->SetFont('Arial', '', 10);
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Cell(120, 8, 'Total Paid Till Date', 1, 0);
        $pdf->Cell(60, 8, number_format($totalPaid, 2), 1, 1, 'R');

        $pdf->SetFont('Arial', 'B', 10);
        $pdf->SetFillColor(254, 226, 226);
        $pdf->SetTextColor(220, 38, 38);
        $pdf->Cell(120, 8, 'Balance Due', 1, 0, true);
        $pdf->Cell(60, 8, number_format($balanceDue, 2), 1, 1, 'R', true);
        $pdf->Ln(8);

        // Payment details
        $pdf->SetTextColor(44, 30, 74);
        $pdf->SetFont('Arial', 'B', 10);
        if (!empty($payment['transaction_id'])) {
            $pdf->Cell(0, 6, 'Transaction ID: ' . $payment['transaction_id'], 0, 1);
        }
        if (!empty($payment['remarks'])) {
            $pdf->Cell(0, 6, 'Remarks: ' . $payment['remarks'], 0, 1);
        }
        $pdf->Ln(3);

        // Amount in words
        $pdf->SetFont('Arial', 'B', 10);
        $pdf->Cell(0, 6, 'Amount in Words: ' . $this->numberToWords((float)$payment['amount']) . ' Rupees Only', 0, 1);
        $pdf->Ln(5);

        // ─── QR Code (right side) + Terms & Conditions (left side) ───────
        // Generate QR code via QuickChart API — encodes receipt verification URL
        $qrTempPath = null;
        try {
            $verifyUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http') . '://'
                       . ($_SERVER['HTTP_HOST'] ?? 'localhost')
                       . (defined('BASE_PATH') ? BASE_PATH : '/gmind')
                       . '/branch/fees/receipt/' . ($payment['id'] ?? 0);
            $qrApiUrl = 'https://quickchart.io/qr?text=' . urlencode($verifyUrl) . '&size=150&margin=2';

            $qrTempPath = sys_get_temp_dir() . '/qr_rcp_' . ($payment['id'] ?? 'tmp') . '_' . time() . '.png';
            $qrData = @file_get_contents($qrApiUrl);
            if ($qrData !== false && strlen($qrData) > 100) {
                file_put_contents($qrTempPath, $qrData);
            } else {
                $qrTempPath = null;
            }
        } catch (\Throwable $e) {
            $qrTempPath = null;
        }

        // Left side: Terms & Conditions box
        $termsStartY = $pdf->GetY();
        $pdf->SetFillColor(254, 247, 205);
        $pdf->Rect(15, $termsStartY, 120, 50, 'F');
        $pdf->SetXY(18, $termsStartY + 3);
        $pdf->SetFont('Arial', 'B', 9);
        $pdf->SetTextColor(146, 64, 14);
        $pdf->Cell(114, 5, 'TERMS & CONDITIONS', 0, 1);

        $pdf->SetFont('Arial', '', 7.5);
        $pdf->SetTextColor(120, 53, 15);
        $terms = [
            '1. Fee once paid will NOT be refundable.',
            '2. Certificate will be issued only after full payment.',
            '3. This is a computer-generated receipt — verify online via QR.',
            '4. Please retain this receipt for future reference.',
        ];
        foreach ($terms as $t) {
            $pdf->SetX(18);
            $pdf->MultiCell(114, 4.5, $t, 0, 'L');
        }

        // Right side: QR Code
        if ($qrTempPath && file_exists($qrTempPath)) {
            $pdf->Image($qrTempPath, 145, $termsStartY + 5, 35, 35);
            $pdf->SetXY(145, $termsStartY + 42);
            $pdf->SetFont('Arial', '', 7);
            $pdf->SetTextColor(100, 116, 139);
            $pdf->Cell(50, 4, 'Scan to verify', 0, 1, 'C');
            // Cleanup temp file
            @unlink($qrTempPath);
        } else {
            // Fallback if QR fails — show verification text
            $pdf->SetXY(145, $termsStartY + 15);
            $pdf->SetFont('Arial', 'B', 8);
            $pdf->SetTextColor(44, 30, 74);
            $pdf->Cell(50, 5, 'Verify Online:', 0, 1, 'C');
            $pdf->SetFont('Arial', '', 7);
            $pdf->SetTextColor(100, 116, 139);
            $pdf->MultiCell(50, 4, 'Use receipt no. to verify at institute office', 0, 'C');
        }
        $pdf->Ln(8);

        // Signatures
        $pdf->SetTextColor(44, 30, 74);
        $pdf->SetFont('Arial', '', 10);
        $pdf->Cell(95, 6, '_______________________', 0, 0, 'C');
        $pdf->Cell(95, 6, '_______________________', 0, 1, 'C');
        $pdf->Cell(95, 6, 'Student Signature', 0, 0, 'C');
        $pdf->Cell(95, 6, 'Authorized Signatory', 0, 1, 'C');
        $pdf->Ln(5);

        // Footer
        $pdf->SetTextColor(150, 150, 150);
        $pdf->SetFont('Arial', 'I', 8);
        $pdf->Cell(0, 5, 'This is a computer-generated receipt. Generated on ' . date('d M Y, h:i A'), 0, 1, 'C');

        // Output
        $downloadName = 'Receipt_' . ($payment['receipt_no'] ?? $payment['id']) . '_' . preg_replace('/[^a-zA-Z0-9]/', '_', $payment['student_name'] ?? 'student') . '.pdf';
        header('Content-Type: application/pdf');
        header('Content-Disposition: attachment; filename="' . $downloadName . '"');
        header('Cache-Control: private, max-age=0, must-revalidate');
        while (ob_get_level()) ob_end_clean();
        $pdf->Output('D', $downloadName);
        exit;
    }

    /**
     * Generate the fee report PDF (course-wise summary).
     */
    private function generateReportPdf(array $data): void
    {
        if (!$this->loadFpdf()) {
            flash('error', 'FPDF library not found. Please upload FPDF to /gmind/fpdf/fpdf.php');
            redirect(BASE_PATH . '/branch/fees/reports');
            return;
        }

        $feeByCourse = $data['feeByCourse'];
        $branch      = $data['branch'];
        $grandStudents = $data['grandStudents'];
        $grandFee      = $data['grandFee'];
        $grandPaid     = $data['grandPaid'];
        $grandDue      = $data['grandDue'];
        $courseFilter  = $data['courseFilter'];

        $pdf = new \FPDF('P', 'mm', 'A4');
        $pdf->AddPage();
        $pdf->SetMargins(15, 15, 15);
        $pdf->SetAutoPageBreak(true, 15);

        // ─── Header ───────────────────────────────────────────────────────
        $logoPath = $this->resolveImagePath($branch['logo'] ?? '');
        if ($logoPath) {
            try { $pdf->Image($logoPath, 15, 12, 25, 25); } catch (\Throwable $e) {}
        }

        $pdf->SetFont('Arial', 'B', 18);
        $pdf->SetTextColor(44, 30, 74);
        $pdf->Cell(0, 10, strtoupper($branch['name'] ?? 'GMIND INSTITUTE'), 0, 1, 'C');
        $pdf->SetFont('Arial', '', 9);
        $pdf->SetTextColor(100, 116, 139);
        $pdf->Cell(0, 5, 'Branch Code: ' . ($branch['branch_code'] ?? '—'), 0, 1, 'C');
        $pdf->Ln(3);

        $pdf->SetDrawColor(108, 63, 232);
        $pdf->SetLineWidth(0.8);
        $pdf->Line(15, $pdf->GetY(), 195, $pdf->GetY());
        $pdf->Ln(5);

        // Report title
        $pdf->SetFont('Arial', 'B', 16);
        $pdf->SetTextColor(22, 160, 133);
        $pdf->Cell(0, 10, 'FEE COLLECTION REPORT', 0, 1, 'C');
        $pdf->SetFont('Arial', '', 10);
        $pdf->SetTextColor(100, 116, 139);
        $filterText = 'All Courses';
        if (!empty($courseFilter)) {
            foreach ($feeByCourse as $c) {
                // Just show "Filtered"
            }
            $filterText = 'Filtered by Course';
        }
        $pdf->Cell(0, 6, 'Generated on: ' . date('d M Y, h:i A') . '  |  ' . $filterText, 0, 1, 'C');
        $pdf->Ln(5);

        // ─── Summary Cards ───────────────────────────────────────────────
        $pdf->SetFont('Arial', 'B', 11);
        $pdf->SetTextColor(255, 255, 255);

        // Total Expected
        $pdf->SetFillColor(37, 99, 235);
        $pdf->Rect(15, $pdf->GetY(), 58, 22, 'F');
        $pdf->SetXY(15, $pdf->GetY() + 3);
        $pdf->Cell(58, 6, 'TOTAL EXPECTED', 0, 1, 'C');
        $pdf->SetFont('Arial', 'B', 13);
        $pdf->Cell(58, 8, 'Rs. ' . number_format($grandFee, 2), 0, 1, 'C');

        $pdf->SetY($pdf->GetY() - 11);
        $pdf->SetFont('Arial', 'B', 11);
        $pdf->SetFillColor(22, 163, 74);
        $pdf->Rect(75, $pdf->GetY(), 58, 22, 'F');
        $pdf->SetXY(75, $pdf->GetY() + 3);
        $pdf->Cell(58, 6, 'TOTAL COLLECTED', 0, 1, 'C');
        $pdf->SetFont('Arial', 'B', 13);
        $pdf->Cell(58, 8, 'Rs. ' . number_format($grandPaid, 2), 0, 1, 'C');

        $pdf->SetY($pdf->GetY() - 11);
        $pdf->SetFont('Arial', 'B', 11);
        $pdf->SetFillColor(220, 38, 38);
        $pdf->Rect(135, $pdf->GetY(), 58, 22, 'F');
        $pdf->SetXY(135, $pdf->GetY() + 3);
        $pdf->Cell(58, 6, 'TOTAL DUE', 0, 1, 'C');
        $pdf->SetFont('Arial', 'B', 13);
        $pdf->Cell(58, 8, 'Rs. ' . number_format($grandDue, 2), 0, 1, 'C');
        $pdf->Ln(15);

        // ─── Course-wise Table ───────────────────────────────────────────
        $pdf->SetFont('Arial', 'B', 12);
        $pdf->SetTextColor(44, 30, 74);
        $pdf->Cell(0, 8, 'Course-wise Fee Breakdown', 0, 1);
        $pdf->Ln(2);

        // Table header
        $pdf->SetFillColor(108, 63, 232);
        $pdf->SetTextColor(255, 255, 255);
        $pdf->SetFont('Arial', 'B', 9);
        $pdf->Cell(60, 8, 'Course Name', 1, 0, 'C', true);
        $pdf->Cell(20, 8, 'Students', 1, 0, 'C', true);
        $pdf->Cell(35, 8, 'Total Fee', 1, 0, 'C', true);
        $pdf->Cell(35, 8, 'Total Paid', 1, 0, 'C', true);
        $pdf->Cell(33, 8, 'Total Due', 1, 1, 'C', true);

        // Table rows
        $pdf->SetTextColor(44, 30, 74);
        $pdf->SetFont('Arial', '', 9);
        $rowColor = false;
        foreach ($feeByCourse as $row) {
            if ($rowColor) {
                $pdf->SetFillColor(248, 250, 252);
                $pdf->Cell(60, 7, substr($row['course_name'] ?? '', 0, 35), 1, 0, 'L', true);
                $pdf->Cell(20, 7, (int)$row['student_count'], 1, 0, 'C', true);
                $pdf->Cell(35, 7, 'Rs. ' . number_format((float)$row['total_fee'], 2), 1, 0, 'R', true);
                $pdf->Cell(35, 7, 'Rs. ' . number_format((float)$row['total_paid'], 2), 1, 0, 'R', true);
                $pdf->Cell(33, 7, 'Rs. ' . number_format((float)$row['total_due'], 2), 1, 1, 'R', true);
            } else {
                $pdf->Cell(60, 7, substr($row['course_name'] ?? '', 0, 35), 1, 0, 'L');
                $pdf->Cell(20, 7, (int)$row['student_count'], 1, 0, 'C');
                $pdf->Cell(35, 7, 'Rs. ' . number_format((float)$row['total_fee'], 2), 1, 0, 'R');
                $pdf->Cell(35, 7, 'Rs. ' . number_format((float)$row['total_paid'], 2), 1, 0, 'R');
                $pdf->Cell(33, 7, 'Rs. ' . number_format((float)$row['total_due'], 2), 1, 1, 'R');
            }
            $rowColor = !$rowColor;
        }

        // Grand total row
        $pdf->SetFillColor(255, 247, 205);
        $pdf->SetTextColor(120, 53, 15);
        $pdf->SetFont('Arial', 'B', 10);
        $pdf->Cell(60, 9, 'GRAND TOTAL', 1, 0, 'L', true);
        $pdf->Cell(20, 9, $grandStudents, 1, 0, 'C', true);
        $pdf->Cell(35, 9, 'Rs. ' . number_format($grandFee, 2), 1, 0, 'R', true);
        $pdf->Cell(35, 9, 'Rs. ' . number_format($grandPaid, 2), 1, 0, 'R', true);
        $pdf->Cell(33, 9, 'Rs. ' . number_format($grandDue, 2), 1, 1, 'R', true);
        $pdf->Ln(8);

        // Collection rate
        $rate = $grandFee > 0 ? round(($grandPaid / $grandFee) * 100, 2) : 0;
        $pdf->SetTextColor(44, 30, 74);
        $pdf->SetFont('Arial', 'B', 11);
        $pdf->Cell(0, 7, 'Overall Collection Rate: ' . $rate . '%', 0, 1, 'C');
        $pdf->Ln(5);

        // Footer
        $pdf->SetTextColor(150, 150, 150);
        $pdf->SetFont('Arial', 'I', 8);
        $pdf->Cell(0, 5, 'This is a computer-generated report from Gmind Institute Management System. Generated on ' . date('d M Y, h:i A'), 0, 1, 'C');

        $downloadName = 'Fee_Report_' . date('Y-m-d') . '.pdf';
        header('Content-Type: application/pdf');
        header('Content-Disposition: attachment; filename="' . $downloadName . '"');
        header('Cache-Control: private, max-age=0, must-revalidate');
        while (ob_get_level()) ob_end_clean();
        $pdf->Output('D', $downloadName);
        exit;
    }

    /**
     * Convert number to words (Indian format).
     */
    private function numberToWords(float $num): string
    {
        $num = (int)round($num);
        if ($num == 0) return 'Zero';

        $ones = ['', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine',
                 'Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen',
                 'Seventeen', 'Eighteen', 'Nineteen'];
        $tens = ['', '', 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety'];

        $words = '';

        // Crores
        if ($num >= 10000000) {
            $words .= $this->numberToWords((int)($num / 10000000)) . ' Crore ';
            $num = $num % 10000000;
        }
        // Lakhs
        if ($num >= 100000) {
            $words .= $this->numberToWords((int)($num / 100000)) . ' Lakh ';
            $num = $num % 100000;
        }
        // Thousands
        if ($num >= 1000) {
            $words .= $this->numberToWords((int)($num / 1000)) . ' Thousand ';
            $num = $num % 1000;
        }
        // Hundreds
        if ($num >= 100) {
            $words .= $ones[(int)($num / 100)] . ' Hundred ';
            $num = $num % 100;
        }
        // Tens and ones
        if ($num >= 20) {
            $words .= $tens[(int)($num / 10)];
            $num = $num % 10;
        }
        if ($num > 0) {
            $words .= ($words ? ' ' : '') . $ones[$num];
        }

        return trim($words);
    }
}
