<?php

namespace App\Controllers\View;

use App\Models\EventModel;
use App\Models\EventChargesModel;
use App\Models\UserModel;
use App\Models\StudentModel;
use App\Models\StudentClassModel;
use App\Models\ConfigurationModel;
use App\Models\EnrollmentModel;
use App\Models\InvoiceModel;
use App\Models\InvoiceEventModel;
use App\Models\ClassSectionModel;
use App\Models\ParentModel;
use App\Models\PaymentModel;
use App\Models\CalendarModel;
use App\Services\EmailService;
use Config\Database;
use App\Controllers\View\InvoiceController;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\RESTful\ResourceController;
use Dompdf\Dompdf;
use Dompdf\Options;

class EventController extends ResourceController
{
    protected $eventChargesModel;
    protected $studentModel;
    protected $userModel;
    protected $configModel;
    protected $eventModel;
    protected $invoiceController;
    protected $invoiceModel;
    protected $invoiceEventModel;
    protected $studentClassModel;
    protected $classSectionModel;
    protected $paymentModel;
    protected $parentModel;
    protected $emailService;
    protected $schoolYear;
    protected $semester;
    protected $categories;
    protected $enrollmentModel;
    private ?bool $eventChargesHasCreatedBy = null;
    private ?bool $eventChargesHasWaiverSigned = null;

    public function __construct()
    {
        $this->eventChargesModel = new EventChargesModel(); //eventChargesModel
        $this->studentModel = new StudentModel();
        $this->configModel = new ConfigurationModel();
        $this->userModel = new UserModel(); // Add this
        $this->eventModel = new EventModel();
        $this->invoiceController = new InvoiceController();
        $this->invoiceModel = new InvoiceModel();
        $this->invoiceEventModel = new InvoiceEventModel();
        $this->studentClassModel = new StudentClassModel();
        $this->classSectionModel = new ClassSectionModel();
        $this->paymentModel = new PaymentModel();
        $this->enrollmentModel = new EnrollmentModel();
        $this->parentModel = new ParentModel();
        $this->emailService = new EmailService();

        $this->schoolYear = $this->currentSchoolYearName();
        $this->semester = $this->configModel->getConfig('semester');
        $this->categories = [
            'workshops',
            'orientations',
            'field trips',
            'Ramadan programs',
        ];
    }

    private function eventChargesSupportsCreatedBy(): bool
    {
        if ($this->eventChargesHasCreatedBy !== null) {
            return $this->eventChargesHasCreatedBy;
        }

        try {
            $db = Database::connect();
            $this->eventChargesHasCreatedBy = $db->fieldExists('created_by', 'event_charges');
        } catch (\Throwable $e) {
            $this->eventChargesHasCreatedBy = false;
        }

        return $this->eventChargesHasCreatedBy;
    }

    private function currentSchoolYearName(): string
    {
        try {
            return service('schoolYearContext')->resolve(service('request'))->yearName();
        } catch (\Throwable $e) {
            return (string) ($this->configModel->getConfig('school_year') ?? '');
        }
    }

    private function eventChargesSupportsWaiverSigned(): bool
    {
        if ($this->eventChargesHasWaiverSigned !== null) {
            return $this->eventChargesHasWaiverSigned;
        }

        try {
            $db = Database::connect();
            $this->eventChargesHasWaiverSigned = $db->fieldExists('waiver_signed', 'event_charges');
        } catch (\Throwable $e) {
            $this->eventChargesHasWaiverSigned = false;
        }

        return $this->eventChargesHasWaiverSigned;
    }

    private function getEventChargesReturnTo(): string
    {
        $fallback = site_url('administrator/event-charges');
        $returnTo = trim((string) ($this->request->getPost('return_to') ?? ''));
        if ($returnTo === '') {
            return $fallback;
        }

        $base = rtrim((string) base_url(), '/');
        if (str_starts_with($returnTo, '/')) {
            return $returnTo;
        }
        if ($base !== '' && str_starts_with($returnTo, $base)) {
            return $returnTo;
        }

        return $fallback;
    }

    private function loadEventChargesListingData(bool $includeParents = true): array
    {
        $schoolYear = $this->request->getGet('school_year') ?? $this->schoolYear;
        $semester = $this->request->getGet('semester') ?? $this->semester;
        $filterEventId = (int) ($this->request->getGet('event_id') ?? 0);
        $filterParentId = (int) ($this->request->getGet('parent_id') ?? 0);

        $parents = [];
        if ($includeParents) {
            $parents = $this->userModel->getParents();
            usort($parents, static function (array $a, array $b): int {
                $aLabel = trim(preg_replace('/\s+/', ' ', (string) ($a['firstname'] ?? '') . ' ' . (string) ($a['lastname'] ?? '')));
                $bLabel = trim(preg_replace('/\s+/', ' ', (string) ($b['firstname'] ?? '') . ' ' . (string) ($b['lastname'] ?? '')));

                $nameCmp = strnatcasecmp($aLabel, $bLabel);
                if ($nameCmp !== 0) {
                    return $nameCmp;
                }

                return strnatcasecmp((string) ($a['school_id'] ?? ''), (string) ($b['school_id'] ?? ''));
            });
        }

        $events = $this->eventModel->getActiveEvents($schoolYear);

        $chargesBuilder = $this->eventChargesModel
            ->select('event_charges.*,
                    users.firstname AS parent_firstname, users.lastname AS parent_lastname, users.cellphone AS parent_cellphone,
                    students.firstname AS student_firstname, students.lastname AS student_lastname,
                    events.event_name, events.description AS event_description, events.amount AS event_amount')
            ->join('users', 'users.id = event_charges.parent_id', 'left')
            ->join('students', 'students.id = event_charges.student_id', 'left')
            ->join('events', 'events.id = event_charges.event_id', 'left')
            ->where('event_charges.school_year', $schoolYear)
            ->where('event_charges.semester', $semester);

        if ($filterEventId > 0) {
            $chargesBuilder->where('event_charges.event_id', $filterEventId);
        }

        if ($filterParentId > 0) {
            $chargesBuilder->where('event_charges.parent_id', $filterParentId);
        }

        $charges = $chargesBuilder
            ->orderBy('event_charges.created_at', 'DESC')
            ->findAll();

        foreach ($charges as &$charge) {
            if (empty($charge['class_section_id']) && !empty($charge['student_id'])) {
                $sections = $this->studentClassModel->getClassSectionIdsByStudentId(
                    (int) $charge['student_id'],
                    $schoolYear
                );
                if (!empty($sections)) {
                    $charge['class_section_id'] = $sections[0];
                }
            }
        }
        unset($charge);

        $parentBalances = [];
        try {
            $balanceRows = $this->invoiceModel
                ->select('parent_id, COALESCE(SUM(balance),0) AS total_balance')
                ->where('school_year', $schoolYear)
                ->where('semester', $semester)
                ->groupBy('parent_id')
                ->findAll();

            foreach ($balanceRows as $row) {
                $parentBalances[(int) ($row['parent_id'] ?? 0)] = (float) ($row['total_balance'] ?? 0.0);
            }
        } catch (\Throwable $e) {
            log_message('error', 'Failed to load parent balances: ' . $e->getMessage());
        }

        $sectionIds = array_unique(array_filter(array_column($charges, 'class_section_id')));
        $classSectionNames = [];
        if (!empty($sectionIds)) {
            $sections = $this->classSectionModel
                ->select('class_section_id, class_section_name')
                ->whereIn('class_section_id', $sectionIds)
                ->findAll();
            foreach ($sections as $section) {
                $classSectionNames[(int) ($section['class_section_id'] ?? 0)] = $section['class_section_name'] ?? '';
            }
        }

        return [
            'charges' => $charges,
            'parents' => $parents,
            'events' => $events,
            'school_year' => $schoolYear,
            'semester' => $semester,
            'filterEventId' => $filterEventId,
            'filterParentId' => $filterParentId,
            'parentBalances' => $parentBalances,
            'classSectionNames' => $classSectionNames,
        ];
    }

    private function buildGroupedEventCharges(array $charges): array
    {
        $grouped = [];
        foreach ($charges as $charge) {
            $eventId = (int) ($charge['event_id'] ?? 0);
            if (!isset($grouped[$eventId])) {
                $grouped[$eventId] = [
                    'label' => $charge['event_name'] ?? 'N/A',
                    'rows' => [],
                ];
            }
            $grouped[$eventId]['rows'][] = $charge;
        }

        return $grouped;
    }

    private function applyGroupedEventChargeRowOrder(array $groupedCharges): array
    {
        $rowOrder = $this->request->getGet('row_order');
        if (!is_array($rowOrder) || empty($rowOrder)) {
            return $groupedCharges;
        }

        foreach ($groupedCharges as $eventId => &$group) {
            $rawOrder = $rowOrder[$eventId] ?? $rowOrder[(string) $eventId] ?? null;
            if (!is_string($rawOrder) || trim($rawOrder) === '') {
                continue;
            }

            $orderedIds = array_values(array_filter(array_map(
                static fn ($value): int => (int) trim((string) $value),
                explode(',', $rawOrder)
            )));
            if (empty($orderedIds) || empty($group['rows']) || !is_array($group['rows'])) {
                continue;
            }

            $rowsById = [];
            foreach ($group['rows'] as $row) {
                $rowsById[(int) ($row['id'] ?? 0)] = $row;
            }

            $reorderedRows = [];
            foreach ($orderedIds as $chargeId) {
                if (isset($rowsById[$chargeId])) {
                    $reorderedRows[] = $rowsById[$chargeId];
                    unset($rowsById[$chargeId]);
                }
            }

            foreach ($group['rows'] as $row) {
                $chargeId = (int) ($row['id'] ?? 0);
                if (isset($rowsById[$chargeId])) {
                    $reorderedRows[] = $row;
                    unset($rowsById[$chargeId]);
                }
            }

            $group['rows'] = $reorderedRows;
        }
        unset($group);

        return $groupedCharges;
    }

    private function buildEventChargesPdfFilename(array $data): string
    {
        $label = 'event_participant_lists';
        if (!empty($data['filterEventId'])) {
            foreach (($data['events'] ?? []) as $event) {
                if ((int) ($event['id'] ?? 0) === (int) $data['filterEventId']) {
                    $label = (string) ($event['event_name'] ?? $label);
                    break;
                }
            }
        }

        $safeLabel = trim((string) preg_replace('/[^A-Za-z0-9]+/', '_', strtolower($label)), '_');
        if ($safeLabel === '') {
            $safeLabel = 'event_participant_lists';
        }

        return $safeLabel . '_' . date('Ymd_His') . '.pdf';
    }

    public function index()
    {
        $today = local_date(utc_now(), 'Y-m-d');
        $schoolYear = $this->currentSchoolYearName();

        $events = $this->eventModel
            ->where('school_year', $schoolYear)
            ->orderBy('created_at', 'DESC')
            ->findAll();

        $activeEventCount = $this->eventModel
            ->where('school_year', $schoolYear)
            ->where('expiration_date >=', $today)
            ->countAllResults();

        return view('administrator/events/event_list', [
            'events' => $events,
            'activeEventCount' => $activeEventCount,
            'schoolYear' => $schoolYear,
        ]);
    }


    public function create()
    {
        helper(['form']);
        if (strtolower($this->request->getMethod()) === 'post') {
            $file = $this->request->getFile('flyer');
            $flyerPath = null;

            if ($file && $file->isValid() && !$file->hasMoved()) {
                // Move to public/uploads/event_flyers
                $newName = $file->getRandomName();
                $file->move(FCPATH . 'uploads/event_flyers', $newName);
                $flyerPath = 'event_flyers/' . $newName; // store relative path
            }

            $eventId = $this->eventModel->insert([
                'event_name' => $this->request->getPost('event_name'),
                'event_category' => $this->request->getPost('event_category'),
                'description' => $this->request->getPost('description'),
                'amount' => $this->request->getPost('amount'),
                'flyer' => $flyerPath,
                'expiration_date' => $this->request->getPost('expiration_date'),
                'semester' => $this->request->getPost('semester'),
                'school_year' => $this->request->getPost('school_year'),
                'created_by' => session()->get('user_id'),
            ]);

            if ($eventId && $this->request->getPost('add_to_calendar')) {
                $calendarModel = new CalendarModel();

                $title = trim((string)$this->request->getPost('event_name'));
                $date  = (string)$this->request->getPost('expiration_date');
                $schoolYear = (string)$this->request->getPost('school_year');
                $semester   = (string)$this->request->getPost('semester');

                if ($title !== '' && $date !== '' && $schoolYear !== '') {
                    $data = [
                        'title'          => $title,
                        'description'    => (string)$this->request->getPost('description'),
                        'event_type'     => 'Event',
                        'date'           => $date,
                        'notify_parent'  => $this->request->getPost('notify_parent') ? 1 : 0,
                        'notify_teacher' => $this->request->getPost('notify_teacher') ? 1 : 0,
                        'notify_admin'   => $this->request->getPost('notify_admin') ? 1 : 0,
                        'no_school'      => 0,
                        'school_year'    => $schoolYear,
                        'semester'       => $semester ?: $this->semester,
                    ];
                    if (!$calendarModel->supportsEventType()) {
                        unset($data['event_type']);
                    }

                    $dupQuery = $calendarModel
                        ->where('school_year', $data['school_year'])
                        ->where('date', $data['date'])
                        ->where('title', $data['title']);
                    if ($calendarModel->supportsEventType() && isset($data['event_type'])) {
                        $dupQuery = $dupQuery->where('event_type', $data['event_type']);
                    }
                    $existing = $dupQuery->first();
                    if (!$existing) {
                        $calendarModel->save($data);
                    }
                }
            }

            $redirect = redirect()->to('/administrator/events')->with('success', 'Event created successfully');
            if ($eventId && $this->request->getPost('send_email_parent')) {
                $emailStatus = $this->broadcastEventToParents((int) $eventId);
                $redirect = $this->applyEventEmailFlash($redirect, $emailStatus);
            }

            return $redirect;
        }

        return view('administrator/events/create_event', [
            'categories' => $this->categories,
        ]);
    }


    public function edit($id = null)
    {
        helper(['form']);
        $event = $this->eventModel->find($id);

        if (!$event) {
            return redirect()->to('/administrator/events')->with('error', 'Event not found');
        }

        if (strtolower($this->request->getMethod()) === 'post') {
            log_message('debug', 'POST detected');
            $file = $this->request->getFile('flyer');
            $flyerPath = $event['flyer']; // Default: keep old flyer

            if ($file && $file->isValid() && !$file->hasMoved()) {
                $newName = $file->getRandomName();
                $file->move(FCPATH . 'uploads/event_flyers', $newName);
                $flyerPath = 'event_flyers/' . $newName; // store relative path
            }

            $updated = $this->eventModel->update($id, [
                'event_name' => $this->request->getPost('event_name'),
                'event_category' => $this->request->getPost('event_category'),
                'description' => $this->request->getPost('description'),
                'amount' => $this->request->getPost('amount'),
                'flyer' => $flyerPath,
                'expiration_date' => $this->request->getPost('expiration_date'),
                'semester' => $this->request->getPost('semester'),
                'school_year' => $this->request->getPost('school_year'),
            ]);

            if ($updated) {
                if ($this->request->getPost('add_to_calendar')) {
                    $calendarModel = new CalendarModel();

                    $title = trim((string) $this->request->getPost('event_name'));
                    $date = (string) $this->request->getPost('expiration_date');
                    $schoolYear = (string) $this->request->getPost('school_year');
                    $semester = (string) $this->request->getPost('semester');

                    if ($title !== '' && $date !== '' && $schoolYear !== '') {
                        $data = [
                            'title'          => $title,
                            'description'    => (string) $this->request->getPost('description'),
                            'event_type'     => 'Event',
                            'date'           => $date,
                            'notify_parent'  => $this->request->getPost('notify_parent') ? 1 : 0,
                            'notify_teacher' => $this->request->getPost('notify_teacher') ? 1 : 0,
                            'notify_admin'   => $this->request->getPost('notify_admin') ? 1 : 0,
                            'no_school'      => 0,
                            'school_year'    => $schoolYear,
                            'semester'       => $semester ?: $this->semester,
                        ];
                        if (!$calendarModel->supportsEventType()) {
                            unset($data['event_type']);
                        }

                        $existingByPreviousEvent = $calendarModel
                            ->where('school_year', (string) ($event['school_year'] ?? ''))
                            ->where('date', (string) ($event['expiration_date'] ?? ''))
                            ->where('title', (string) ($event['event_name'] ?? ''));
                        if ($calendarModel->supportsEventType() && isset($data['event_type'])) {
                            $existingByPreviousEvent = $existingByPreviousEvent->where('event_type', $data['event_type']);
                        }
                        $existingCalendar = $existingByPreviousEvent->first();

                        if ($existingCalendar) {
                            $calendarModel->update($existingCalendar['id'], $data);
                        } else {
                            $dupQuery = $calendarModel
                                ->where('school_year', $data['school_year'])
                                ->where('date', $data['date'])
                                ->where('title', $data['title']);
                            if ($calendarModel->supportsEventType() && isset($data['event_type'])) {
                                $dupQuery = $dupQuery->where('event_type', $data['event_type']);
                            }
                            $duplicate = $dupQuery->first();

                            if (!$duplicate) {
                                $calendarModel->save($data);
                            }
                        }
                    }
                }

                $redirect = redirect()->to('/administrator/events')->with('success', 'Event updated successfully');
                if ($this->request->getPost('send_email_parent')) {
                    $emailStatus = $this->broadcastEventToParents((int) $id);
                    $redirect = $this->applyEventEmailFlash($redirect, $emailStatus);
                }
                return $redirect;
            } else {
                log_message('debug', 'GET detected');
                return redirect()->back()->with('error', 'Failed to update event');
            }
        }

        return view('administrator/events/edit_event', [
            'event' => $event,
            'categories' => $this->categories,
        ]);
    }

    private function broadcastEventToParents(int $eventId): array
    {
        $event = $this->eventModel->find($eventId);
        if (!$event) {
            return ['ok' => false, 'count' => 0, 'message' => 'Event email was skipped because the event could not be loaded.'];
        }

        $emails = $this->collectParentBroadcastEmails((string) ($event['school_year'] ?? ''));
        if ($emails === []) {
            return ['ok' => false, 'count' => 0, 'message' => 'Event email was skipped because no parent email addresses were found.'];
        }

        $subject = 'School Event: ' . trim((string) ($event['event_name'] ?? 'Upcoming Event'));
        $innerBody = view('emails/event_broadcast', [
            'event' => $event,
            'eventUrl' => base_url('parent/events'),
            'flyerUrl' => !empty($event['flyer']) ? base_url('uploads/' . ltrim((string) $event['flyer'], '/')) : null,
        ]);
        $body = view('emails/_wrap_layout', [
            'title'     => $subject,
            'subject'   => $subject,
            'body_html' => $innerBody,
        ]);

        $ok = $this->emailService->sendCc($emails, $subject, $body, 'general');
        $batchCount = $this->emailService->getLastCcBatchCount();

        return [
            'ok' => $ok,
            'count' => count($emails),
            'batches' => $batchCount,
            'message' => $ok
                ? 'Event email was sent in ' . $batchCount . ' CC batch(es) for ' . count($emails) . ' recipient(s).'
                : 'The event was saved, but sending the parent CC email failed.',
        ];
    }

    private function collectParentBroadcastEmails(string $schoolYear = ''): array
    {
        $emails = [];

        $db = Database::connect();

        $parentRole = $db->table('roles')
            ->select('id')
            ->where('LOWER(name)', 'parent')
            ->get()
            ->getRow();

        if ($parentRole) {
            $firstParentQuery = $db->table('users')
                ->select('users.email')
                ->join('user_roles', 'user_roles.user_id = users.id')
                ->where('user_roles.role_id', (int) $parentRole->id)
                ->where('users.email IS NOT NULL', null, false)
                ->where('users.email !=', '');

            if ($schoolYear !== '' && $db->fieldExists('school_year', 'users')) {
                $firstParentQuery->where('users.school_year', $schoolYear);
            }

            foreach ($firstParentQuery->get()->getResultArray() as $row) {
                $email = trim((string) ($row['email'] ?? ''));
                if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
                    $emails[strtolower($email)] = $email;
                }
            }
        }

        $secondParentQuery = $db->table('parents')
            ->select('secondparent_email')
            ->where('secondparent_email IS NOT NULL', null, false)
            ->where('secondparent_email !=', '');

        if ($schoolYear !== '' && $db->fieldExists('school_year', 'parents')) {
            $secondParentQuery->where('school_year', $schoolYear);
        }

        foreach ($secondParentQuery->get()->getResultArray() as $row) {
            $email = trim((string) ($row['secondparent_email'] ?? ''));
            if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
                $emails[strtolower($email)] = $email;
            }
        }

        return array_values($emails);
    }

    private function applyEventEmailFlash($redirect, array $emailStatus)
    {
        if (!empty($emailStatus['message'])) {
            return $redirect->with(
                ($emailStatus['ok'] ?? false) ? 'email_status' : 'email_error',
                $emailStatus['message']
            );
        }

        return $redirect;
    }


    public function delete($id = null)
    {

        $event = $this->eventModel->find($id);

        if (!$event) {
            return redirect()->to('/administrator/events')->with('error', 'Event not found');
        }

        // Delete related charges and collect parent IDs
        $charges = $this->eventChargesModel->where('event_id', $id)->findAll();
        $parentIds = [];        

        foreach ($charges as $charge) {
            $this->eventChargesModel->delete($charge['id']);
            $parentIds[] = $charge['parent_id'];
        }

        // Also remove any invoice_event rows tied to this event
        $eventName = $event['event_name'] ?? null;
        if ($eventName) {
            $builder = $this->invoiceEventModel->where('event_name', $eventName);
            if (!empty($event['school_year'])) {
                $builder->where('school_year', $event['school_year']);
            }
            if (!empty($event['semester'])) {
                $builder->where('semester', $event['semester']);
            }
            $builder->delete();
        }

        // Delete event
        $this->eventModel->delete($id);
        
        $parentIds = array_unique($parentIds);

        foreach ($parentIds as $parentId) {
            $this->invoiceController->generateInvoice($parentId);
        }

        return redirect()->to('/administrator/events')->with('success', 'Event, charges, and invoices updated.');
    }


    // Optionally keep your eventShow / eventUpdate for legacy administrator event charges
    public function eventShow()
    {
        $data = $this->loadEventChargesListingData();

        $semesterOptions = (new EventChargesModel())
            ->select('semester')
            ->distinct()
            ->orderBy('semester', 'ASC')
            ->findColumn('semester');
        $semesterOptions = array_values(array_filter(array_unique($semesterOptions ?? [])));
        if (empty($semesterOptions)) {
            $semesterOptions = [$this->semester];
        }

        $schoolYearOptions = (new EventChargesModel())
            ->select('school_year')
            ->distinct()
            ->orderBy('school_year', 'ASC')
            ->findColumn('school_year');
        $schoolYearOptions = array_values(array_filter(array_unique($schoolYearOptions ?? [])));
        if (empty($schoolYearOptions)) {
            $schoolYearOptions = [$this->schoolYear];
        }

        return view('administrator/events/event_charges', array_merge($data, [
            'semesterOptions' => $semesterOptions,
            'schoolYearOptions' => $schoolYearOptions,
        ]));
    }

    public function eventChargesPdf(): ResponseInterface
    {
        $data = $this->loadEventChargesListingData(false);
        $groupedCharges = $this->buildGroupedEventCharges($data['charges']);
        $groupedCharges = $this->applyGroupedEventChargeRowOrder($groupedCharges);
        $generatedAt = date('m-d-Y h:i A');

        $html = view('administrator/events/event_charges_pdf', array_merge($data, [
            'groupedCharges' => $groupedCharges,
            'generatedAt' => $generatedAt,
        ]));

        $options = new Options();
        $options->set('isRemoteEnabled', true);
        $options->set('defaultFont', 'DejaVu Sans');

        $dompdf = new Dompdf($options);
        $dompdf->loadHtml($html, 'UTF-8');
        $dompdf->setPaper('A4', 'landscape');
        $dompdf->render();

        return $this->response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', 'inline; filename="' . $this->buildEventChargesPdfFilename($data) . '"')
            ->setBody($dompdf->output());
    }


    public function eventUpdate()
    {
        $schoolYear = $this->request->getPost('school_year') ?? $this->schoolYear;
        $semester = $this->request->getPost('semester') ?? $this->semester;

        $parentId = $this->request->getPost('parent_id');
        $eventId = $this->request->getPost('event_id');
        $participations = $this->request->getPost('participation') ?? [];
        $externalParticipants = $this->request->getPost('external_participants') ?? [];

        // Allow "external only" submissions (no enrolled students selected).
        if (!$eventId || (empty($participations) && empty($externalParticipants))) {
            return redirect()->back()->with('error', 'Missing required information.');
        }
        if (!empty($participations) && !$parentId) {
            return redirect()->back()->with('error', 'Select a parent when adding enrolled students.');
        }

        $userId = session()->get('user_id');
        $event = $this->eventModel->getEvent($eventId, $schoolYear);
        if (!$event) {
            return redirect()->back()->with('error', 'Selected event not found for the chosen school year.');
        }

        $parentsForInvoice = [];
        $supportsCreatedBy = $this->eventChargesSupportsCreatedBy();
        $supportsWaiverSigned = $this->eventChargesSupportsWaiverSigned();

        foreach ($participations as $studentId => $value) {
            $existing = $this->eventChargesModel->where([
                'parent_id' => $parentId,
                'student_id' => $studentId,
                'event_id' => $eventId,
                'school_year' => $schoolYear,
                'semester' => $semester
            ])->first();

            if ($value === 'no') {
                if ($existing) {
                    $this->eventChargesModel->delete($existing['id']);
                }
                continue;
            }

            $sectionIds = $this->studentClassModel->getClassSectionIdsByStudentId($studentId, $schoolYear);
            $classSectionId = !empty($sectionIds) ? $sectionIds[0] : null;

            if ($existing) {
                $updateData = [
                    'participation'   => 'yes',
                    'updated_by'      => $userId,
                    'class_section_id'=> $classSectionId,
                ];
                if ($supportsWaiverSigned) {
                    $updateData['waiver_signed'] = (int) ($existing['waiver_signed'] ?? 0);
                }
                if (isset($event['amount']) && ((float)$event['amount'] !== (float)($existing['charged'] ?? 0))) {
                    $updateData['charged'] = $event['amount'];
                }
                $this->eventChargesModel->update($existing['id'], $updateData);
                continue;
            } else {
                $insertData = [
                    'parent_id'     => $parentId,
                    'student_id'    => $studentId,
                    'event_id'      => $eventId,
                    'participation' => 'yes',
                    'charged'       => $event['amount'],
                    'event_paid'    => 0,
                    'class_section_id' => $classSectionId,
                    'school_year'   => $schoolYear,
                    'semester'      => $semester,
                    'updated_by'    => $userId
                ];
                if ($supportsWaiverSigned) {
                    $insertData['waiver_signed'] = 0;
                }
                if ($supportsCreatedBy) {
                    $insertData['created_by'] = $userId;
                }
                $this->eventChargesModel->insert($insertData);
            }
        }

        foreach ($externalParticipants as $pieces) {
            $firstname = $this->normalizeExternalName($pieces['firstname'] ?? '');
            $lastname = $this->normalizeExternalName($pieces['lastname'] ?? '');
            $note = trim($pieces['note'] ?? '');
            $parentFirst = $this->normalizeExternalName($pieces['parent_firstname'] ?? '');
            $parentLast = $this->normalizeExternalName($pieces['parent_lastname'] ?? '');
            $parentPhone = trim($pieces['parent_phone'] ?? '');
            $parentEmail = trim((string)($pieces['parent_email'] ?? ''));
            $markPaid = (string)($pieces['paid'] ?? '0') === '1';
            $waiverSigned = (string)($pieces['waiver_signed'] ?? '0') === '1';

            if (!$firstname && !$lastname) {
                continue;
            }

            $matchedParentId = $this->findExistingParentIdByExternalContact(
                $parentFirst,
                $parentLast,
                $parentEmail,
                $parentPhone
            );

            // Match external participants by kid name + event context.
            // Parent contact info can change (or be fixed later) and should be updated, not used for matching.
            $matchConditions = [
                'event_id' => $eventId,
                'external_firstname' => $firstname,
                'external_lastname' => $lastname,
                'school_year' => $schoolYear,
                'semester' => $semester,
            ];
            if ($matchedParentId) {
                $matchConditions['parent_id'] = $matchedParentId;
            } else {
                // External-only parent: match by external parent contact to avoid duplicates.
                $matchConditions['parent_id'] = null;
                $matchConditions['external_parent_firstname'] = $parentFirst ?: null;
                $matchConditions['external_parent_lastname'] = $parentLast ?: null;
                $matchConditions['external_parent_phone'] = $parentPhone ?: null;
                $matchConditions['external_parent_email'] = $parentEmail ?: null;
            }
            $existingExternal = $this->eventChargesModel->where($matchConditions)->first();

            if ($existingExternal) {
                $updateData = [
                    'participation' => 'yes',
                    'charged' => $event['amount'],
                    'updated_by' => $userId,
                    // Preserve existing payment status; only update contact fields when provided.
                    'external_note' => $note !== '' ? $note : ($existingExternal['external_note'] ?? null),
                    'external_parent_firstname' => $parentFirst !== '' ? $parentFirst : ($existingExternal['external_parent_firstname'] ?? null),
                    'external_parent_lastname' => $parentLast !== '' ? $parentLast : ($existingExternal['external_parent_lastname'] ?? null),
                    'external_parent_phone' => $parentPhone !== '' ? $parentPhone : ($existingExternal['external_parent_phone'] ?? null),
                    'external_parent_email' => $parentEmail !== '' ? $parentEmail : ($existingExternal['external_parent_email'] ?? null),
                ];
                if ($supportsWaiverSigned) {
                    $updateData['waiver_signed'] = $waiverSigned ? 1 : 0;
                }
                $this->eventChargesModel->update($existingExternal['id'], $updateData);
                continue;
            }

            $insertData = [
                'parent_id' => $matchedParentId ?: null,
                'student_id' => null,
                'event_id' => $eventId,
                'participation' => 'yes',
                'charged' => $event['amount'],
                'event_paid' => $markPaid ? 1 : 0,
                'external_firstname' => $firstname,
                'external_lastname' => $lastname,
                'external_note' => $note,
                'external_parent_firstname' => $parentFirst,
                'external_parent_lastname' => $parentLast,
                'external_parent_phone' => $parentPhone,
                'external_parent_email' => $parentEmail,
                'school_year' => $schoolYear,
                'semester' => $semester,
                'updated_by' => $userId,
            ];
            if ($supportsWaiverSigned) {
                $insertData['waiver_signed'] = $waiverSigned ? 1 : 0;
            }
            if ($supportsCreatedBy) {
                $insertData['created_by'] = $userId;
            }
            $this->eventChargesModel->insert($insertData);

            $newChargeId = (int)$this->eventChargesModel->getInsertID();
            if ($markPaid && $newChargeId > 0) {
                $this->applyEventPaymentStatus($newChargeId, true);
            }

            if ($matchedParentId && $this->parentHasEnrollment($matchedParentId, (string)$schoolYear)) {
                $parentsForInvoice[$matchedParentId] = true;
            }
        }

        if ($parentId && $this->parentHasEnrollment((int)$parentId, (string)$schoolYear)) {
            $parentsForInvoice[(int)$parentId] = true;
        }

        foreach (array_keys($parentsForInvoice) as $pid) {
            $this->invoiceController->generateInvoice((string)$pid, (string)$schoolYear, (string)$semester, false);
            $this->fixInvoiceStatusAfterCharge((int)$pid, (string)$schoolYear);
        }

        return redirect()->back()->with('success', 'Event charges updated successfully.');
    }

    public function removeCharge($chargeId = null)
    {
        $returnTo = $this->getEventChargesReturnTo();
        if (!$chargeId) {
            return redirect()->to($returnTo)->with('error', 'Invalid charge.');
        }

        $charge = $this->eventChargesModel->find($chargeId);
        if (!$charge) {
            return redirect()->to($returnTo)->with('error', 'Charge not found.');
        }

        $paymentId = (int)($charge['event_payment_id'] ?? 0);
        if ($paymentId > 0) {
            $this->paymentModel->delete($paymentId);
        }

        $this->eventChargesModel->delete($chargeId);
        if (!empty($charge['parent_id']) && $this->parentHasEnrollment((int)$charge['parent_id'], (string)($charge['school_year'] ?? ''))) {
            $this->invoiceController->generateInvoice((string)$charge['parent_id'], (string)($charge['school_year'] ?? $this->schoolYear), (string)($charge['semester'] ?? $this->semester), false);
        }

        return redirect()->to($returnTo)->with('success', 'Participation removed, invoices updated.');
    }

    public function toggleEventPayment($chargeId = null)
    {
        $returnTo = $this->getEventChargesReturnTo();
        if (!$chargeId) {
            return redirect()->to($returnTo)->with('error', 'Invalid charge.');
        }

        $isPaid = $this->request->getPost('paid') === '1';
        $meta = $this->applyEventPaymentStatus((int)$chargeId, $isPaid);
        if (!$meta) {
            return redirect()->to($returnTo)->with('error', 'Charge not found.');
        }

        if (!empty($meta['invoice_id'])) {
            $this->invoiceController->generateInvoice((string)$meta['parent_id'], (string)$meta['school_year'], (string)($meta['semester'] ?? $this->semester), false);
            $this->fixInvoiceStatusAfterCharge((int)$meta['parent_id'], (string)$meta['school_year']);
        }

        return redirect()->to($returnTo)->with('success', 'Event payment status updated.');
    }

    public function toggleWaiverStatus($chargeId = null)
    {
        $returnTo = $this->getEventChargesReturnTo();
        if (!$chargeId) {
            return redirect()->to($returnTo)->with('error', 'Invalid charge.');
        }

        if (!$this->eventChargesSupportsWaiverSigned()) {
            return redirect()->to($returnTo)->with('error', 'Waiver tracking is not available until the database migration is applied.');
        }

        $charge = $this->eventChargesModel->find($chargeId);
        if (!$charge) {
            return redirect()->to($returnTo)->with('error', 'Charge not found.');
        }

        $signed = $this->request->getPost('waiver_signed') === '1';

        $this->eventChargesModel->update((int) $chargeId, [
            'waiver_signed' => $signed ? 1 : 0,
            'updated_by' => session()->get('user_id'),
        ]);

        return redirect()->to($returnTo)->with('success', $signed ? 'Waiver marked as signed.' : 'Waiver marked as unsigned.');
    }

    private function parentHasEnrollment(int $parentId, string $schoolYear): bool
    {
        if ($parentId <= 0 || $schoolYear === '') {
            return false;
        }

        try {
            return $this->enrollmentModel
                ->where('parent_id', $parentId)
                ->where('school_year', $schoolYear)
                ->countAllResults() > 0;
        } catch (\Throwable $e) {
            return false;
        }
    }

    private function findExistingParentIdByExternalContact(string $first, string $last, string $email, string $phone): ?int
    {
        $first = trim($first);
        $last = trim($last);
        $email = trim($email);
        $phoneDigits = preg_replace('/\D+/', '', (string)$phone);

        if ($first === '' || $last === '' || $email === '' || $phoneDigits === '') {
            return null;
        }

        try {
            $db = Database::connect();
            $expr = "REPLACE(REPLACE(REPLACE(REPLACE(cellphone, '(', ''), ')', ''), '-', ''), ' ', '') =";

            $row = $this->userModel
                ->select('id')
                ->where('LOWER(firstname)', strtolower($first))
                ->where('LOWER(lastname)', strtolower($last))
                ->where('LOWER(email)', strtolower($email))
                ->where($expr, $db->escapeString($phoneDigits), false)
                ->first();

            $id = (int)($row['id'] ?? 0);
            return $id > 0 ? $id : null;
        } catch (\Throwable $e) {
            log_message('error', 'Failed to match external parent to users: ' . $e->getMessage());
            return null;
        }
    }

    private function syncInvoicePaymentSummary(int $invoiceId): void
    {
        if ($invoiceId <= 0) {
            return;
        }

        $invoice = $this->invoiceModel->find($invoiceId);
        if (!$invoice) {
            return;
        }

        $db = \Config\Database::connect();
        $exclude = ['void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled'];

        $paidSum = 0.0;
        try {
            $qb = $db->table('payments')
                ->select('COALESCE(SUM(paid_amount),0) AS tot')
                ->where('invoice_id', $invoiceId)
                ->where('paid_amount >', 0);

            if ($db->fieldExists('status', 'payments')) {
                $qb->groupStart()
                    ->whereNotIn('status', $exclude)
                    ->orWhere('status IS NULL', null, false)
                    ->groupEnd();
            }
            if ($db->fieldExists('is_void', 'payments')) {
                $qb->groupStart()
                    ->where('is_void', 0)
                    ->orWhere('is_void IS NULL', null, false)
                    ->groupEnd();
            }

            $row = $qb->get()->getRowArray();
            $paidSum = (float)($row['tot'] ?? 0.0);
        } catch (\Throwable $e) {
            log_message('error', 'Failed to sum payments for invoice ' . $invoiceId . ': ' . $e->getMessage());
        }

        $discountSum = 0.0;
        try {
            $row = $db->table('discount_usages')
                ->select('COALESCE(SUM(discount_amount),0) AS tot')
                ->where('invoice_id', $invoiceId)
                ->get()
                ->getRowArray();
            $discountSum = (float)($row['tot'] ?? 0.0);
        } catch (\Throwable $e) {
            log_message('error', 'Failed to sum discounts for invoice ' . $invoiceId . ': ' . $e->getMessage());
        }

        $refundSum = 0.0;
        try {
            $row = $db->table('refunds')
                ->select('COALESCE(SUM(refund_paid_amount),0) AS tot')
                ->where('invoice_id', $invoiceId)
                ->whereIn('status', ['Partial', 'Paid'])
                ->get()
                ->getRowArray();
            $refundSum = (float)($row['tot'] ?? 0.0);
        } catch (\Throwable $e) {
            log_message('error', 'Failed to sum refunds for invoice ' . $invoiceId . ': ' . $e->getMessage());
        }

        $total = (float)($invoice['total_amount'] ?? 0.0);
        $newBalance = round($total - $discountSum - $refundSum - $paidSum, 2);
        $newStatus = ($newBalance <= 0.00001)
            ? 'Paid'
            : (($paidSum > 0) ? 'Partially Paid' : 'Unpaid');

        $this->invoiceModel->update($invoiceId, [
            'paid_amount' => $paidSum,
            'balance' => $newBalance,
            'status' => $newStatus,
            'updated_at' => utc_now(),
        ]);
    }

    private function applyEventPaymentStatus(int $chargeId, bool $isPaid): ?array
    {
        if ($chargeId <= 0) {
            return null;
        }

        $charge = $this->eventChargesModel->find($chargeId);
        if (!$charge) {
            return null;
        }

        $event = $this->eventModel->find($charge['event_id']);
        $eventAmount = max(0.0, (float)($event['amount'] ?? 0));
        $paymentId = (int)($charge['event_payment_id'] ?? 0);

        $parentId = (int)($charge['parent_id'] ?? 0);
        $schoolYear = (string)($charge['school_year'] ?? $this->schoolYear);
        $semester = (string)($charge['semester'] ?? $this->semester);

        if ($parentId > 0 && $eventAmount > 0) {
            $hasEnrollment = $this->parentHasEnrollment($parentId, $schoolYear);
            $invoiceQuery = $this->invoiceModel
                ->where('parent_id', $parentId)
                ->where('school_year', $schoolYear)
                ->orderBy('id', 'DESC');
            $invoice = $invoiceQuery->first();

            if (!$invoice && $hasEnrollment) {
                $this->invoiceController->generateInvoice((string)$parentId, $schoolYear, $semester, false);
                $invoice = $invoiceQuery->first();
            }

            if ($invoice) {
                $invoiceId = (int)($invoice['id'] ?? 0);
                if ($invoiceId > 0) {
                    $this->syncInvoicePaymentSummary($invoiceId);
                    $invoice = $this->invoiceModel->find($invoiceId) ?? $invoice;
                }

                if ($isPaid && !$paymentId) {
                    $paymentSchoolYear = (string)($invoice['school_year'] ?? $schoolYear);
                    $paymentSemester = (string)($invoice['semester'] ?? $semester);
                    $paymentId = (int)($this->createEventPayment(
                        $parentId,
                        (int)$invoice['id'],
                        $eventAmount,
                        $paymentSchoolYear,
                        $paymentSemester,
                        (float)($invoice['total_amount'] ?? 0.0),
                        (float)($invoice['paid_amount'] ?? 0.0),
                        (float)($invoice['balance'] ?? 0.0)
                    ) ?? 0);
                } elseif (!$isPaid && $paymentId > 0) {
                    $this->paymentModel->delete($paymentId);
                    $paymentId = 0;
                }

                if (!empty($invoiceId)) {
                    $this->syncInvoicePaymentSummary($invoiceId);
                }
            } elseif (!$hasEnrollment) {
                // External-only parent: do not create an invoice/payment row; just persist the paid flag on the charge.
                $paymentId = 0;
            }
        }

        $this->eventChargesModel->update($chargeId, [
            'event_paid' => $isPaid ? 1 : 0,
            'charged' => $eventAmount,
            'event_payment_id' => $paymentId > 0 ? $paymentId : null,
        ]);

        return [
            'parent_id' => $parentId,
            'school_year' => $schoolYear,
            'semester' => $semester,
            'invoice_id' => isset($invoiceId) && $invoiceId > 0 ? $invoiceId : null,
        ];
    }




    public function getStudentsWithCharges()
    {
        $parentId = $this->request->getGet('parent_id');
        $semester = $this->request->getGet('semester');
        $schoolYear = $this->request->getGet('school_year');

        // Get students for parent
        $students = $this->studentModel->where('parent_id', $parentId)->findAll();

        // Get student_ids that already have charges
        $eventId = $this->request->getGet('event_id');

        $chargesBuilder = $this->eventChargesModel
            ->where('parent_id', $parentId)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear);

        if (!empty($eventId)) {
            $chargesBuilder->where('event_id', $eventId);
        }

        $chargedStudentIds = $chargesBuilder
            ->groupBy('student_id')
            ->select('student_id')
            ->findColumn('student_id');

        $data = [];
        foreach ($students as $student) {
            $data[] = [
                'id' => $student['id'],
                'name' => $student['firstname'] . ' ' . $student['lastname'],
                'charged' => in_array($student['id'], $chargedStudentIds ?? []),
            ];
        }

        return $this->response->setJSON($data);
    }

    private function createEventPayment(
        int $parentId,
        int $invoiceId,
        float $amount,
        string $schoolYear,
        ?string $semester,
        float $invoiceTotal = 0.0,
        float $invoicePaid = 0.0,
        float $invoiceBalance = 0.0
    ): ?int
    {
        if ($amount <= 0 || $invoiceId <= 0 || $parentId <= 0) {
            return null;
        }

        $monthSemester = $semester ?: $this->semester;
        $newPaid = (float)$invoicePaid + $amount;
        $newBalance = (float)$invoiceBalance - $amount;
        $paymentStatus = ($newBalance <= 0.00001)
            ? 'Paid'
            : (($newPaid > 0) ? 'Partially Paid' : 'Unpaid');

        $exclude = ['void', 'voided', 'refunded', 'failed', 'chargeback', 'declined', 'reversed', 'canceled', 'cancelled'];
        $priorCount = $this->paymentModel
            ->where('invoice_id', $invoiceId)
            ->where('paid_amount >', 0)
            ->whereNotIn('status', $exclude)
            ->countAllResults();
        $installmentSeq = $priorCount + 1;

        $data = [
            'parent_id' => $parentId,
            'invoice_id' => $invoiceId,
            'total_amount' => $invoiceTotal > 0 ? $invoiceTotal : $amount,
            'paid_amount' => $amount,
            'balance' => $newBalance,
            'number_of_installments' => $installmentSeq,
            'payment_method' => 'cash',
            'payment_date' => utc_now(),
            'school_year' => $schoolYear,
            'semester' => $monthSemester,
            'status' => $paymentStatus,
            'transaction_id' => $this->paymentModel->generateNewTransactionId(),
            'updated_by' => session()->get('user_id'),
        ];

        if (!$this->paymentModel->insert($data)) {
            log_message('error', 'Failed to insert event payment: ' . json_encode($this->paymentModel->errors()));
            return null;
        }

        return (int)$this->paymentModel->getInsertID();
    }

    private function normalizeExternalName(?string $value): string
    {
        $raw = trim((string)$value);
        if ($raw === '') {
            return '';
        }
        $parts = preg_split('/\s+/', $raw);
        $formatted = array_map(function ($part) {
            $part = trim($part);
            if ($part === '') {
                return '';
            }
            return mb_strtoupper(mb_substr($part, 0, 1)) . mb_strtolower(mb_substr($part, 1));
        }, $parts);
        return implode(' ', array_filter($formatted, fn($p) => $p !== ''));
    }

    private function fixInvoiceStatusAfterCharge($parentId, $schoolYear)
    {
        if (!$parentId || !$schoolYear) {
            return;
        }

        $invoices = $this->invoiceModel
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->findAll();

        foreach ($invoices as $invoice) {
            $status = strtolower(trim($invoice['status'] ?? ''));
            $balance = (float)($invoice['balance'] ?? 0.0);

            if ($balance <= 0.00001 && $status !== 'paid') {
                $this->invoiceModel->update($invoice['id'], ['status' => 'Paid']);
            } elseif ($status === 'paid' && $balance > 0) {
                $this->invoiceModel->update($invoice['id'], ['status' => 'Unpaid']);
            }
        }
    }

}
