<?php

namespace App\Controllers\View;

use App\Controllers\BaseController;
use App\Models\ConfigurationModel;
use App\Models\EarlyDismissalSignatureModel;
use App\Models\ParentAttendanceReportModel;
use App\Models\StudentModel;
use App\Models\StudentClassModel;
use App\Models\ClassSectionModel;
use App\Models\AttendanceDataModel;
use App\Models\AttendanceRecordModel;
use App\Services\SemesterRangeService;

class ParentAttendanceReportController extends BaseController
{

    protected $db;
    protected $configModel;
    protected $reportModel;
    protected $studentModel;
    protected $studentClassModel;
    protected $classSectionModel;
    protected $attendanceDataModel;
    protected $earlyDismissalSignatureModel;
    protected $attendanceRecordModel;

    public function __construct()
    {
        $this->db                = \Config\Database::connect();
        $this->configModel       = new ConfigurationModel();
        $this->reportModel       = new ParentAttendanceReportModel();
        $this->studentModel      = new StudentModel();
        $this->studentClassModel = new StudentClassModel();
        $this->classSectionModel = new ClassSectionModel();
        $this->attendanceDataModel = new AttendanceDataModel();
        $this->attendanceRecordModel = new AttendanceRecordModel();
        $this->earlyDismissalSignatureModel = new EarlyDismissalSignatureModel();
        helper(['form', 'url']);
    }

    // GET: parent form to report absence/late/early dismissal
    public function form()
    {
        $parentId = $this->resolvePrimaryParentIdFromSession();
        if (!$parentId) {
            return redirect()->to('/login');
        }

        $students = $this->db->table('students')
            ->where('parent_id', $parentId)
            ->orderBy('firstname', 'ASC')
            ->orderBy('lastname', 'ASC')
            ->get()->getResultArray();

        // Build a list of Sundays (last few + upcoming weeks)
        [$sundays, $defaultDate] = $this->computeSundays();

        // Load upcoming reports for preview/edit (today and forward within this school year)
        $schoolYear = (string) $this->configModel->getConfig('school_year');
        $todayYmd   = local_date(utc_now(), 'Y-m-d');
        $previewRows = $this->reportModel->builder()
            ->select('parent_attendance_reports.*, s.firstname, s.lastname')
            ->join('students s', 's.id = parent_attendance_reports.student_id', 'left')
            ->where('parent_attendance_reports.parent_id', (int) $parentId)
            ->where('parent_attendance_reports.school_year', $schoolYear)
            ->where('parent_attendance_reports.report_date >=', $todayYmd)
            ->orderBy('parent_attendance_reports.report_date', 'ASC')
            ->orderBy('s.firstname', 'ASC')
            ->orderBy('s.lastname', 'ASC')
            ->get()->getResultArray();

        $cutoffThreshold = $this->normalizeCutoffTime((string) $this->configModel->getConfig('parent_report_cutoff'));

        return view('parent/report_attendance', [
            'students'    => $students,
            'today'       => local_date(utc_now(), 'Y-m-d'),
            'sundays'     => $sundays,      // array of ['value'=>Y-m-d, 'label'=>...] items
            'defaultDate' => $defaultDate,   // preselected date (upcoming Sunday)
            'myReports'   => $previewRows,
            'cutoffThreshold' => $cutoffThreshold,
        ]);
    }

    // POST: save parent report
    public function submit()
    {
        $parentId = $this->resolvePrimaryParentIdFromSession();
        if (!$parentId) {
            return redirect()->to('/login');
        }

        $post = $this->request->getPost();

        $rules = [
            'student_ids' => [
                'rules'  => 'required',
                'errors' => [
                    'required' => 'At least select one student',
                ],
            ],
            'date'           => 'permit_empty|valid_date[Y-m-d]',
            'type'           => 'required|in_list[absent,late,early_dismissal]',
            'arrival_time'   => 'permit_empty|regex_match[/^\d{2}:\d{2}(:\d{2})?$/]',
            'dismiss_time'   => 'permit_empty|regex_match[/^\d{2}:\d{2}(:\d{2})?$/]',
            'reason'         => 'permit_empty|string',
        ];

        if (! $this->validate($rules)) {
            return redirect()->back()->withInput()->with('error', implode(' ', $this->validator->getErrors()));
        }

        $studentIdsRaw = (array) ($post['student_ids'] ?? []);
        $studentIds = [];
        foreach ($studentIdsRaw as $sidRaw) {
            $sid = (int) $sidRaw;
            if ($sid > 0) {
                $studentIds[] = $sid;
            }
        }

        $datesInput = $post['dates'] ?? ($post['date'] ?? null);
        $rawDates   = [];
        if (is_array($datesInput)) {
            $rawDates = $datesInput;
        } elseif ($datesInput !== null) {
            $rawDates = [$datesInput];
        }
        $rawDates = array_values(array_filter(array_map(function ($val) {
            return substr((string) $val, 0, 10);
        }, $rawDates)));
        $rawDates = array_values(array_unique(array_filter($rawDates)));

        if (empty($rawDates)) {
            return redirect()->back()->withInput()->with('error', 'Please select at least one Sunday date.');
        }

        $type        = (string) $post['type'];
        $arrivalTime = $post['arrival_time'] ?? null;
        $dismissTime = $post['dismiss_time'] ?? null;
        $reasonRaw   = $post['reason'] ?? null;
        $reason      = is_string($reasonRaw) ? trim($reasonRaw) : null;

        // Enforce Sunday-only and future-or-today selection
        $todayCheck = new \DateTime('today');
        $cutoffThreshold = $this->normalizeCutoffTime((string) $this->configModel->getConfig('parent_report_cutoff'));
        $tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
        $tz = null;
        $nowTz = null;
        try {
            $tz = new \DateTimeZone($tzName ?: 'UTC');
            $nowTz = new \DateTime('now', $tz);
        } catch (\Throwable $e) {
            $tz = null;
            $nowTz = null;
        }
        $validDates = [];

        $cap = $this->firstSundayOfJune((string) $this->configModel->getConfig('school_year'));

        $lateCutoff = null;
        $lateNow = null;
        $lateTodayStr = $todayCheck->format('Y-m-d');
        if ($type === 'late') {
            try {
                if (!$tz) {
                    $tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
                    $tz = new \DateTimeZone($tzName ?: 'UTC');
                }
                $lateNow = $nowTz ?: new \DateTime('now', $tz);
                $cm = $this->configModel;
                $cutoffStr = (string) ($cm->getConfig('late_report_cutoff')
                            ?: $cm->getConfig('late_cutoff_time')
                            ?: '10:10');
                if (preg_match('/^(\d{1,2}):(\d{2})/', $cutoffStr, $m)) {
                    $hh = str_pad((string) ((int) $m[1]), 2, '0', STR_PAD_LEFT);
                    $mm = str_pad($m[2], 2, '0', STR_PAD_LEFT);
                    $cutoffStr = $hh . ':' . $mm;
                } else {
                    $cutoffStr = '10:10';
                }
                $lateCutoff = new \DateTime($lateTodayStr . ' ' . $cutoffStr . ':00', $tz);
            } catch (\Throwable $e) {
                log_message('warning', 'Late cutoff prep failed: ' . $e->getMessage());
                $lateCutoff = null;
                $lateNow = null;
            }
        }

        foreach ($rawDates as $entry) {
            $dt = \DateTime::createFromFormat('Y-m-d', $entry);
            if (!$dt) {
                return redirect()->back()->withInput()->with('error', 'Invalid date selected: ' . $entry);
            }
            if ((int)$dt->format('w') !== 0) {
                return redirect()->back()->withInput()->with('error', 'Please select Sunday dates only.');
            }
            if ($dt < $todayCheck) {
                return redirect()->back()->withInput()->with('error', 'Past dates are not allowed. Please select today or a future Sunday.');
            }
            if ($nowTz && $tz) {
                try {
                    $cutoff = new \DateTime($entry . ' ' . $cutoffThreshold . ':00', $tz);
                    if ($nowTz >= $cutoff) {
                        $abbr = $cutoff->format('T');
                        return redirect()->back()->withInput()->with('error', 'Reporting closes at ' . $cutoffThreshold . ' ' . $abbr . ' on the report date.');
                    }
                } catch (\Throwable $e) {
                    // ignore and continue
                }
            }
            if ($dt > $cap) {
                return redirect()->back()->withInput()->with('error', 'The last available date is the first Sunday of June.');
            }
            if ($type === 'late' && $lateCutoff && $lateNow && $entry === $lateTodayStr && $lateNow > $lateCutoff) {
                return redirect()->back()->withInput()->with('error', 'Late reporting is closed for today. Please contact the office if needed.');
            }
            $validDates[$entry] = $dt;
        }

        // Enforce required time fields for specific types
        if ($type === 'late' && (!is_string($arrivalTime) || $arrivalTime === '')) {
            return redirect()->back()->withInput()->with('error', 'Please provide an expected arrival time for late reporting.');
        }
        if ($type === 'early_dismissal' && (!is_string($dismissTime) || $dismissTime === '')) {
            return redirect()->back()->withInput()->with('error', 'Please provide a dismissal time for early dismissal.');
        }
        if ($type !== 'early_dismissal') {
            if ($reason === null || $reason === '') {
                return redirect()->back()->withInput()->with('error', 'Please provide a reason for absence or late arrival.');
            }
        }
        $reason = ($reason !== null && $reason !== '') ? $reason : null;

        // Upper bound already enforced by $validDates building

        $semester    = (string) $this->configModel->getConfig('semester');
        $schoolYear  = (string) $this->configModel->getConfig('school_year');
        $semesterResolver = new SemesterRangeService($this->configModel);

        $inserted        = 0;
        $blocked         = [];
        $successNames    = [];
        $successDetails  = [];
        $submitterUserId = (int) (session()->get('user_id') ?? 0);
        $selectedDateList = array_keys($validDates);

        foreach ($studentIds as $sid) {
            if ($sid <= 0) continue;

            $sectionRow = $this->db->table('student_class')
                ->select('class_section_id')
                ->where('student_id', $sid)
                ->where('school_year', $schoolYear)
                ->orderBy('updated_at', 'DESC')
                ->get()->getRowArray();
            $classSectionId = $sectionRow['class_section_id'] ?? null;
            $classLabel = $this->resolveClassSectionLabel($classSectionId);
            $stu = $this->studentModel->select('firstname, lastname')->find($sid);
            $studentName = $stu ? trim(($stu['firstname'] ?? '') . ' ' . ($stu['lastname'] ?? '')) : ('Student #' . $sid);

            foreach ($validDates as $reportDate => $_dt) {
                $semesterForDate = $semesterResolver->getSemesterForDate($reportDate);
                if ($semesterForDate === '') {
                    $semesterForDate = $semester;
                }
                $saved = false;

                try {
                    $qb = $this->reportModel->builder();
                    $qb->where('student_id', $sid)
                        ->where('report_date', $reportDate);

                    if ($type === 'early_dismissal') {
                        $existingALAny = (clone $qb)
                            ->groupStart()
                            ->where('type', 'absent')
                            ->orWhere('type', 'late')
                            ->groupEnd()
                            ->get()->getRowArray();

                        if ($existingALAny) {
                            $blocked[] = $studentName . ' (' . $reportDate . ')';
                        } else {
                            $existingED = (clone $qb)->where('type', 'early_dismissal')->get()->getFirstRow('array');
                            if ($existingED) {
                                $this->reportModel->update((int)$existingED['id'], [
                                    'dismiss_time' => $dismissTime ?: $existingED['dismiss_time'],
                                    'status'       => 'new',
                                ]);
                                $inserted++;
                                $saved = true;
                                $dismissTimeUsed = $dismissTime ?: ($existingED['dismiss_time'] ?? null);
                                $dismissDisplay = $this->formatTimeDisplay($dismissTimeUsed);
                                $successNames[] = [
                                    'name'       => $studentName,
                                    'type'       => 'Early Dismissal',
                                    'time'       => $dismissDisplay ?: 'N/A',
                                    'date'       => $reportDate,
                                    'date_label' => $this->formatReportDate($reportDate) ?? $reportDate,
                                ];
                                $successDetails[] = [
                                    'student_id'   => $sid,
                                    'name'         => $studentName,
                                    'class_label'  => $classLabel,
                                    'type'         => $type,
                                    'type_label'   => $this->formatParentReportType($type),
                                    'arrival_time' => null,
                                    'dismiss_time' => $dismissDisplay,
                                    'date'         => $reportDate,
                                    'date_label'   => $this->formatReportDate($reportDate) ?? $reportDate,
                                ];
                            } else {
                                $ok = $this->reportModel->insert([
                                    'parent_id'        => $parentId,
                                    'student_id'       => $sid,
                                    'class_section_id' => $classSectionId,
                                    'report_date'      => $reportDate,
                                    'type'             => 'early_dismissal',
                                    'dismiss_time'     => $dismissTime ?: null,
                                    'reason'           => $reason ?: null,
                                    'semester'         => $semesterForDate,
                                    'school_year'      => $schoolYear,
                                    'status'           => 'new',
                                ], false);
                                if ($ok) {
                                    $inserted++;
                                    $saved = true;
                                    $dismissTimeFmt = $this->formatTimeDisplay($dismissTime);
                                    $successNames[] = [
                                        'name'       => $studentName,
                                        'type'       => 'Early Dismissal',
                                        'time'       => $dismissTimeFmt ?: 'N/A',
                                        'date'       => $reportDate,
                                        'date_label' => $this->formatReportDate($reportDate) ?? $reportDate,
                                    ];
                                    $successDetails[] = [
                                        'student_id'   => $sid,
                                        'name'         => $studentName,
                                        'class_label'  => $classLabel,
                                        'type'         => $type,
                                        'type_label'   => $this->formatParentReportType($type),
                                        'arrival_time' => null,
                                        'dismiss_time' => $dismissTimeFmt,
                                        'date'         => $reportDate,
                                        'date_label'   => $this->formatReportDate($reportDate) ?? $reportDate,
                                    ];
                                }
                            }
                        }
                    } else {
                        $existingAL = (clone $qb)
                            ->groupStart()
                            ->where('type', 'absent')
                            ->orWhere('type', 'late')
                            ->groupEnd()
                            ->get()->getRowArray();

                        $existingED = (clone $qb)->where('type', 'early_dismissal')->get()->getFirstRow('array');

                        if ($existingAL || $existingED) {
                            $blocked[] = $studentName . ' (' . $reportDate . ')';
                        } else {
                            $ok = $this->reportModel->insert([
                                'parent_id'        => $parentId,
                                'student_id'       => $sid,
                                'class_section_id' => $classSectionId,
                                'report_date'      => $reportDate,
                                'type'             => $type,
                                'arrival_time'     => ($type === 'late') ? ($arrivalTime ?: null) : null,
                                'reason'           => $reason ?: null,
                                'semester'         => $semesterForDate,
                                'school_year'      => $schoolYear,
                                'status'           => 'new',
                            ], false);
                            if ($ok) {
                                $inserted++;
                                $saved = true;
                                $arrivalFmt = $this->formatTimeDisplay($arrivalTime);
                                $successNames[] = [
                                    'name'       => $studentName,
                                    'type'       => ucfirst($type),
                                    'time'       => ($type === 'late') ? ($arrivalFmt ?: 'N/A') : 'N/A',
                                    'date'       => $reportDate,
                                    'date_label' => $this->formatReportDate($reportDate) ?? $reportDate,
                                ];
                                $successDetails[] = [
                                    'student_id'   => $sid,
                                    'name'         => $studentName,
                                    'class_label'  => $classLabel,
                                    'type'         => $type,
                                    'type_label'   => $this->formatParentReportType($type),
                                    'arrival_time' => ($type === 'late') ? $arrivalFmt : null,
                                    'dismiss_time' => null,
                                    'date'         => $reportDate,
                                    'date_label'   => $this->formatReportDate($reportDate) ?? $reportDate,
                                ];
                            }
                        }
                    }
                } catch (\Throwable $e) {
                    log_message('error', 'Parent report save failed: ' . $e->getMessage());
                }

                if ($saved && in_array($type, ['absent', 'late'], true)) {
                    try {
                            $this->reflectToAttendanceData(
                                studentId: $sid,
                                reportDate: $reportDate,
                                type: $type,
                                semester: $semesterForDate,
                                schoolYear: $schoolYear,
                                classSectionId: $classSectionId,
                                reason: $reason,
                                arrivalTime: $arrivalTime
                            );
                    } catch (\Throwable $e) {
                        log_message('error', 'Unable to reflect parent report to attendance_data: ' . $e->getMessage());
                    }
                }
            }
        }

        // --- Final message output ---
        if ($inserted <= 0) {
            $err = !empty($blocked)
                ? ('Already submitted and cannot be changed for: ' . implode(', ', array_slice($blocked, 0, 5)) . (count($blocked) > 5 ? '…' : ''))
                : 'Could not save your report. Please try again.';
            return redirect()->back()->withInput()->with('error', $err);
        }

        // ✅ Build formatted success message
        $msg = '✅ <strong>Submission received successfully for ' . count($successNames) . ' item' . (count($successNames) > 1 ? 's' : '') . '.</strong><br>';
        $msg .= '<ul style="margin-top:5px;">';
        foreach ($successNames as $s) {
            $dateLabel = $s['date_label'] ?? ($s['date'] ?? '');
            $msg .= '<li><strong>' . esc($s['name']) . '</strong> — ' . esc($s['type'])
                . ($dateLabel ? ' on <strong>' . esc($dateLabel) . '</strong>' : '')
                . (($s['time'] ?? '') && $s['time'] !== 'N/A' ? ' at <strong>' . esc($s['time']) . '</strong>' : '')
                . '</li>';
        }
        $msg .= '</ul>';
        $dateSummary = $this->formatDateList($selectedDateList);
        if ($dateSummary !== '') {
            $msg .= '<strong>Date(s):</strong> ' . esc($dateSummary);
        }

        if (!empty($blocked)) {
            $msg .= '<br><em>Note:</em> Already submitted and unchanged for ' . count($blocked) . ' student' . (count($blocked) > 1 ? 's' : '') . '.';
        }

        if (!empty($successDetails)) {
            try {
                $this->sendParentReportNotifications([
                    'parent_id'    => $parentId,
                    'submitter_id' => $submitterUserId > 0 ? $submitterUserId : $parentId,
                    'students'     => $successDetails,
                    'type'         => $type,
                    'dates'        => $selectedDateList,
                    'report_date'  => $selectedDateList[0] ?? null,
                    'reason'       => $reason,
                    'semester'     => $semester,
                    'school_year'  => $schoolYear,
                ]);
            } catch (\Throwable $e) {
                log_message('error', 'Parent report confirmation email failed: ' . $e->getMessage());
            }
        }

        return redirect()->to(site_url('parent/report-attendance'))->with('message', $msg);
    }

    /**
     * Returns an array of Sunday dates and a recommended default selection.
     * @return array{0: array<int, array{value:string,label:string}>, 1: string}
     */
    private function computeSundays(int $weeksBefore = 4, int $weeksAfter = 26): array
    {
        $today = new \DateTime('today');
        $dow   = (int)$today->format('w'); // 0=Sun

        // This Sunday (today if Sunday, else last Sunday)
        $thisSunday = clone $today;
        if ($dow !== 0) {
            $thisSunday->modify('last sunday');
        }

        // Default selection: upcoming Sunday (today if Sunday)
        $default = clone $today;
        if ($dow !== 0) {
            $default->modify('next sunday');
        }

        // Upper bound: first Sunday of June (school year end)
        $schoolYear = (string) $this->configModel->getConfig('school_year');
        $cap        = $this->firstSundayOfJune($schoolYear);

        $dates = [];
        // Backward Sundays (limited)
        for ($i = $weeksBefore; $i >= 1; $i--) {
            $d = (clone $thisSunday)->modify('-' . $i . ' week');
            $dates[] = $d;
        }
        // Forward Sundays from this Sunday up to the cap (inclusive)
        $cursor = clone $thisSunday; // this Sunday
        while ($cursor <= $cap) {
            $dates[] = clone $cursor;
            $cursor->modify('+1 week');
        }

        // Determine date range for calendar query
        $rangeStart = reset($dates);
        $rangeEnd   = end($dates);
        $rangeStartStr = $rangeStart instanceof \DateTime ? $rangeStart->format('Y-m-d') : null;
        $rangeEndStr   = $rangeEnd instanceof \DateTime ? $rangeEnd->format('Y-m-d') : null;

        // Fetch No School dates from calendar_events within range
        $noSchool = [];
        if ($rangeStartStr && $rangeEndStr) {
            try {
                $builder = $this->db->table('calendar_events')
                    ->select('date')
                    ->where('no_school', 1)
                    ->groupStart()
                    ->where('school_year', $schoolYear)
                    ->orWhere('school_year IS NULL', null, false)
                    ->groupEnd()
                    ->where('date >=', $rangeStartStr)
                    ->where('date <=', $rangeEndStr);
                $rows = $builder->get()->getResultArray();
                foreach ($rows as $r) {
                    $d = substr((string)($r['date'] ?? ''), 0, 10);
                    if ($d !== '') $noSchool[$d] = true;
                }
            } catch (\Throwable $e) {
                // ignore, fallback below
            }
        }

        // Fallback using CalendarModel heuristics if none found
        if (empty($noSchool) && $rangeStartStr && $rangeEndStr) {
            try {
                /** @var \App\Models\CalendarModel $cal */
                $cal = model(\App\Models\CalendarModel::class);
                $events = (array) ($cal->getEventsBySchoolYear($schoolYear) ?? []);
                foreach ($events as $ev) {
                    $d = substr((string)($ev['date'] ?? ''), 0, 10);
                    $ns = (int)($ev['no_school'] ?? 0);
                    if ($ns === 1 && $d >= $rangeStartStr && $d <= $rangeEndStr) {
                        $noSchool[$d] = true;
                    }
                }
            } catch (\Throwable $e) {
                // ignore
            }
        }

        // Filter out no-school Sundays
        $filtered = [];
        foreach ($dates as $d) {
            $ymd = $d->format('Y-m-d');
            if (!isset($noSchool[$ymd])) {
                $filtered[] = $d;
            }
        }

        // Keep only today or future Sundays (no past dates displayed)
        $filteredFuture = [];
        foreach ($filtered as $d) {
            if ($d >= $today) {
                $filteredFuture[] = $d;
            }
        }

        $filteredCapped = [];
        foreach ($filteredFuture as $d) {
            if ($d <= $cap) {
                $filteredCapped[] = $d;
            }
        }

        // Choose a sensible default (nearest allowed Sunday on/after today)
        $defaultYmd = $default->format('Y-m-d');
        $needFind = isset($noSchool[$defaultYmd]) || (new \DateTime($defaultYmd) < $today) || (new \DateTime($defaultYmd) > $cap);
        if ($needFind) {
            $found = null;
            foreach ($filteredCapped as $d) {
                $found = $d;
                break;
            }
            if ($found instanceof \DateTime) {
                $defaultYmd = $found->format('Y-m-d');
            }
        }

        // Build value/label pairs
        $out = [];
        foreach ($filteredCapped as $d) {
            $out[] = [
                'value' => $d->format('Y-m-d'),
                'label' => $d->format('m-d-Y'),
            ];
        }

        return [$out, $defaultYmd];
    }

    /**
     * First Sunday of June for the given school year.
     * If school_year is like "2024-2025", we use 2025-06 (end year).
     * Fallback: if parsing fails, use upcoming June based on today.
     */
    private function firstSundayOfJune(string $schoolYear): \DateTime
    {
        $today = new \DateTime('today');
        $endYear = null;
        if (preg_match('/^(\d{4})\D(\d{4})$/', $schoolYear, $m)) {
            $endYear = (int)$m[2];
        }
        if ($endYear === null) {
            $y = (int)$today->format('Y');
            // If we're past June, cap to next year's June; otherwise this year's June
            $endYear = ((int)$today->format('n') > 6) ? ($y + 1) : $y;
        }

        $juneFirst = new \DateTime(sprintf('%04d-06-01', $endYear));
        if ((int)$juneFirst->format('w') !== 0) {
            $juneFirst->modify('next sunday');
        }
        return $juneFirst; // DateTime (midnight)
    }

    /**
     * Insert or update attendance_data for admin daily view based on parent report.
     * - For 'absent': upsert an ABSENT row and mark is_reported='yes'.
     * - For 'late': if no row exists, insert LATE and mark is_reported='yes'.
     *   Teachers are allowed to override parent-created late entries (see AttendanceController guard).
     */
    private function reflectToAttendanceData(
        int $studentId,
        string $reportDate,
        string $type,
        string $semester,
        string $schoolYear,
        ?int $classSectionId,
        ?string $reason = null,
        ?string $arrivalTime = null
    ): void {
        if ($classSectionId === null || $classSectionId <= 0) {
            // Try resolve from student_class for current year, then fallback to last known assignment
            $row = $this->db->table('student_class')
                ->select('class_section_id')
                ->where('student_id', $studentId)
                ->where('school_year', $schoolYear)
                ->orderBy('updated_at', 'DESC')
                ->get()->getRowArray();
            $classSectionId = (int) ($row['class_section_id'] ?? 0);
            if ($classSectionId <= 0) {
                $row = $this->db->table('student_class')
                    ->select('class_section_id')
                    ->where('student_id', $studentId)
                    ->orderBy('updated_at', 'DESC')
                    ->get()->getRowArray();
                $classSectionId = (int) ($row['class_section_id'] ?? 0);
            }
        }

        // Existing attendance for that date/term (strict, then relaxed)
        $existing = $this->attendanceDataModel
            ->where('student_id', $studentId)
            ->where('class_section_id', $classSectionId)
            ->where('date', $reportDate)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->first();
        if (!$existing) {
            $existing = $this->attendanceDataModel
                ->where('student_id', $studentId)
                ->where('date', $reportDate)
                ->where('semester', $semester)
                ->where('school_year', $schoolYear)
                ->first();
        }
        if (!$existing) {
            $existing = $this->attendanceDataModel->builder()
                ->where('student_id', $studentId)
                ->where('semester', $semester)
                ->where('school_year', $schoolYear)
                ->where("DATE(`date`)", $reportDate, false)
                ->get()->getFirstRow('array');
        }

        if (($classSectionId === null || $classSectionId <= 0) && !empty($existing['class_section_id'])) {
            $classSectionId = (int) $existing['class_section_id'];
        }

        $hasIsReported = $this->db->fieldExists('is_reported', 'attendance_data');
        $hasLegacyReported = $this->db->fieldExists('reported', 'attendance_data');
        $hasIsNotified = $this->db->fieldExists('is_notified', 'attendance_data');

        $status = ($type === 'late') ? 'late' : (($type === 'absent') ? 'absent' : null);
        if ($status === null) return;

        // Build parent-reported reason
        $reasonParts = ['Parent-reported'];
        if ($type === 'late' && $arrivalTime) {
            $reasonParts[] = 'ETA ' . substr($arrivalTime, 0, 5);
        }
        if (!empty($reason)) {
            $reasonParts[] = trim($reason);
        }
        $reasonText = implode(' — ', $reasonParts);

        if (!$existing) {
            if ($classSectionId === null || $classSectionId <= 0) {
                // Try last known attendance_data entry for class/section
                $fallback = $this->attendanceDataModel->builder()
                    ->select('class_section_id, class_id, school_id')
                    ->where('student_id', $studentId)
                    ->where('school_year', $schoolYear)
                    ->orderBy('date', 'DESC')
                    ->limit(1)
                    ->get()->getFirstRow('array');
                if (!$fallback) {
                    $fallback = $this->attendanceDataModel->builder()
                        ->select('class_section_id, class_id, school_id')
                        ->where('student_id', $studentId)
                        ->orderBy('date', 'DESC')
                        ->limit(1)
                        ->get()->getFirstRow('array');
                }
                if (!empty($fallback['class_section_id'])) {
                    $classSectionId = (int) $fallback['class_section_id'];
                }
            }

            if ($classSectionId === null || $classSectionId <= 0) {
                return; // cannot insert without a class section
            }

            // class_id required by attendance_data validation
            $classId = $this->classSectionModel->getClassId($classSectionId);
            if (!$classId || $classId <= 0) {
                $fallback = $this->attendanceDataModel->builder()
                    ->select('class_id')
                    ->where('student_id', $studentId)
                    ->where('school_year', $schoolYear)
                    ->orderBy('date', 'DESC')
                    ->limit(1)
                    ->get()->getFirstRow('array');
                if (!$fallback) {
                    $fallback = $this->attendanceDataModel->builder()
                        ->select('class_id')
                        ->where('student_id', $studentId)
                        ->orderBy('date', 'DESC')
                        ->limit(1)
                        ->get()->getFirstRow('array');
                }
                $classId = (int) ($fallback['class_id'] ?? 0);
            }
            if (!$classId || $classId <= 0) return;

            // Student school_id
            $stu = $this->studentModel->select('school_id')->find($studentId);
            $schoolId = (string) ($stu['school_id'] ?? '');
            if ($schoolId === '') {
                $fallback = $this->attendanceDataModel->builder()
                    ->select('school_id')
                    ->where('student_id', $studentId)
                    ->where('school_year', $schoolYear)
                    ->orderBy('date', 'DESC')
                    ->limit(1)
                    ->get()->getFirstRow('array');
                if (!$fallback) {
                    $fallback = $this->attendanceDataModel->builder()
                        ->select('school_id')
                        ->where('student_id', $studentId)
                        ->orderBy('date', 'DESC')
                        ->limit(1)
                        ->get()->getFirstRow('array');
                }
                $schoolId = (string) ($fallback['school_id'] ?? '');
            }
            if ($schoolId === '') return;

            // Insert a new row
            $row = [
                'class_id'         => $classId,
                'class_section_id' => $classSectionId,
                'student_id'       => $studentId,
                'school_id'        => $schoolId,
                'date'             => $reportDate,
                'status'           => $status,
                'reason'           => $reasonText,
                'semester'         => $semester,
                'school_year'      => $schoolYear,
                'modified_by'      => (int) (session()->get('user_id') ?? 0),
                'created_at'       => utc_now(),
                'updated_at'       => utc_now(),
            ];
            if ($hasIsReported) {
                $row['is_reported'] = 'yes';
            }
            if ($hasLegacyReported) {
                $row['reported'] = 1;
            }
            if ($hasIsNotified) {
                $row['is_notified'] = 'no';
            }
            $this->attendanceDataModel->insert($row, false);
            $this->bumpAttendanceRecordFromParent($studentId, $classSectionId, $schoolId, $status, $semester, $schoolYear);
            return;
        }

        // If already exists: only mark reported for matching status; do not override teacher-marked present
        $existingStatus = strtolower((string) ($existing['status'] ?? ''));
        if ($existingStatus === $status) {
            $update = [
                'reason'     => $existing['reason'] ?: $reasonText,
                'updated_at' => utc_now(),
            ];
            if ($hasIsReported) {
                $update['is_reported'] = 'yes';
            }
            if ($hasLegacyReported) {
                $update['reported'] = 1;
            }
            $this->attendanceDataModel->update((int) $existing['id'], $update);
            return;
        }

        if ($existingStatus === '' || $existingStatus === 'present') {
            $update = [
                'status'     => $status,
                'reason'     => $existing['reason'] ?: $reasonText,
                'updated_at' => utc_now(),
            ];
            if ($hasIsReported) {
                $update['is_reported'] = 'yes';
            }
            if ($hasLegacyReported) {
                $update['reported'] = 1;
            }
            $this->attendanceDataModel->update((int) $existing['id'], $update);

            $classSectionId = (int) ($existing['class_section_id'] ?? $classSectionId ?? 0);
            $schoolId = (string) ($existing['school_id'] ?? '');
            if ($classSectionId > 0 && $schoolId !== '') {
                $this->adjustAttendanceRecordForStatusChange(
                    studentId: $studentId,
                    classSectionId: $classSectionId,
                    schoolId: $schoolId,
                    semester: $semester,
                    schoolYear: $schoolYear,
                    oldStatus: $existingStatus,
                    newStatus: $status
                );
            }
        }
    }

    private function bumpAttendanceRecordFromParent(
        int $studentId,
        int $classSectionId,
        string $schoolId,
        string $status,
        string $semester,
        string $schoolYear
    ): void {
        $status = strtolower($status);
        $record = $this->attendanceRecordModel->where([
            'student_id' => $studentId,
            'semester'   => $semester,
            'school_year' => $schoolYear,
        ])->first();

        $now = utc_now();
        if ($record) {
            $updates = [
                'total_presence'   => (int)($record['total_presence'] ?? 0),
                'total_absence'    => (int)($record['total_absence'] ?? 0),
                'total_late'       => (int)($record['total_late'] ?? 0),
                'total_attendance' => (int)($record['total_attendance'] ?? 0) + 1,
                'updated_at'       => $now,
                'modified_by'      => (int) (session()->get('user_id') ?? 0),
            ];
            if ($status === 'present') {
                $updates['total_presence']++;
            } elseif ($status === 'absent') {
                $updates['total_absence']++;
            } elseif ($status === 'late') {
                $updates['total_late']++;
            }
            $this->attendanceRecordModel->update((int) $record['id'], $updates);
            return;
        }

        $insert = [
            'class_section_id' => $classSectionId,
            'student_id'       => $studentId,
            'school_id'        => $schoolId,
            'semester'         => $semester,
            'school_year'      => $schoolYear,
            'created_at'       => $now,
            'updated_at'       => $now,
            'modified_by'      => (int) (session()->get('user_id') ?? 0),
            'total_presence'   => ($status === 'present') ? 1 : 0,
            'total_absence'    => ($status === 'absent')  ? 1 : 0,
            'total_late'       => ($status === 'late')    ? 1 : 0,
            'total_attendance' => 1,
        ];
        $this->attendanceRecordModel->insert($insert);
    }

    private function adjustAttendanceRecordForStatusChange(
        int $studentId,
        int $classSectionId,
        string $schoolId,
        string $semester,
        string $schoolYear,
        string $oldStatus,
        string $newStatus
    ): void {
        $oldStatus = strtolower($oldStatus);
        $newStatus = strtolower($newStatus);

        if ($oldStatus === $newStatus) {
            return;
        }

        $record = $this->attendanceRecordModel->where([
            'student_id' => $studentId,
            'semester'   => $semester,
            'school_year' => $schoolYear,
        ])->first();

        if (!$record) {
            $this->bumpAttendanceRecordFromParent($studentId, $classSectionId, $schoolId, $newStatus, $semester, $schoolYear);
            return;
        }

        $updates = [
            'total_presence'   => (int) ($record['total_presence'] ?? 0),
            'total_absence'    => (int) ($record['total_absence'] ?? 0),
            'total_late'       => (int) ($record['total_late'] ?? 0),
            'total_attendance' => (int) ($record['total_attendance'] ?? 0),
            'updated_at'       => utc_now(),
            'modified_by'      => (int) (session()->get('user_id') ?? 0),
        ];

        if ($oldStatus === 'present') {
            $updates['total_presence'] = max(0, $updates['total_presence'] - 1);
        } elseif ($oldStatus === 'absent') {
            $updates['total_absence'] = max(0, $updates['total_absence'] - 1);
        } elseif ($oldStatus === 'late') {
            $updates['total_late'] = max(0, $updates['total_late'] - 1);
        }

        if ($newStatus === 'present') {
            $updates['total_presence']++;
        } elseif ($newStatus === 'absent') {
            $updates['total_absence']++;
        } elseif ($newStatus === 'late') {
            $updates['total_late']++;
        }

        $this->attendanceRecordModel->update((int) $record['id'], $updates);
    }

    // GET: staff view to list reports (today & upcoming by default)
    public function list()
    {
        $role = strtolower((string) (session()->get('role') ?? ''));
        if (!in_array($role, ['teacher', 'teacher_assistant', 'administrator', 'admin'], true)) {
            return redirect()->to('/access_denied');
        }
        $start = (string)($this->request->getGet('start') ?: local_date(utc_now(), 'Y-m-d'));
        $end   = (string)($this->request->getGet('end')   ?: ''); // open-ended

        $schoolYearParam = $this->request->getGet('school_year');
        $semesterParam   = $this->request->getGet('semester');
        $schoolYear = (is_string($schoolYearParam) && $schoolYearParam !== '')
            ? (string)$schoolYearParam
            : (string)$this->configModel->getConfig('school_year');
        $semester   = (is_string($semesterParam) && $semesterParam !== '')
            ? (string)$semesterParam
            : (string)$this->configModel->getConfig('semester');

        $rows = $this->reportModel->listForDateRange($start, $end ?: null, $schoolYear, $semester);

        return view('attendance/parent_reports', [
            'rows'       => $rows,
            'start'      => $start,
            'end'        => $end ?: null,
            'schoolYear' => $schoolYear,
            'semester'   => $semester,
        ]);
    }

    private function canAccessEarlyDismissalsNav(): bool
    {
        $sessionRole = session()->get('role');
        $roleNames   = is_array($sessionRole) ? $sessionRole : [$sessionRole];
        $roleNames   = array_values(array_filter(array_map('strval', $roleNames)));

        if (empty($roleNames)) {
            return false;
        }

        $roleIdRows = $this->db->table('roles')
            ->select('id')
            ->whereIn('name', $roleNames)
            ->get()
            ->getResultArray();
        $roleIds = array_map('intval', array_column($roleIdRows, 'id'));

        if (empty($roleIds)) {
            return false;
        }

        $allowed = $this->db->table('role_nav_items AS rni')
            ->select('1')
            ->join('nav_items AS ni', 'ni.id = rni.nav_item_id')
            ->where('ni.url', 'attendance/early-dismissals')
            ->whereIn('rni.role_id', $roleIds)
            ->get(1)
            ->getFirstRow();

        return (bool) $allowed;
    }

    private function canManageEarlyDismissals(): bool
    {
        $allowedRoles = ['teacher', 'teacher_assistant', 'administrator', 'admin'];
        $sessionRole = session()->get('role');
        $roles = is_array($sessionRole) ? $sessionRole : [$sessionRole];

        foreach ($roles as $role) {
            $role = strtolower((string) $role);
            if ($role !== '' && in_array($role, $allowedRoles, true)) {
                return true;
            }
        }

        return false;
    }

    // GET: staff page listing early dismissals by month
    public function earlyDismissals()
    {
        // Authorize via role->nav mapping instead of hardcoded roles
        if (! $this->canAccessEarlyDismissalsNav()) {
            return redirect()->to('/access_denied');
        }

        // Get filters
        $schoolYearParam = $this->request->getGet('school_year');
        $semesterParam   = $this->request->getGet('semester');

        // Default to config values
        $schoolYear = (is_string($schoolYearParam) && $schoolYearParam !== '')
            ? trim($schoolYearParam)
            : (string)$this->configModel->getConfig('school_year');

        $semester = (is_string($semesterParam) && $semesterParam !== '')
            ? trim($semesterParam)
            : '';

        // Query early dismissal reports
        $b = $this->reportModel->builder();
        $b->select('parent_attendance_reports.*, s.firstname, s.lastname, cs.class_section_name')
            ->join('students s', 's.id = parent_attendance_reports.student_id', 'left')
            ->join('classSection cs', 'cs.class_section_id = parent_attendance_reports.class_section_id', 'left')
            ->where('parent_attendance_reports.type', 'early_dismissal');

        // Always filter by school year
        if ($schoolYear !== '') {
            $b->where('parent_attendance_reports.school_year', $schoolYear);
        }

        // Filter by semester only if chosen
        if ($semester !== '') {
            $b->where('parent_attendance_reports.semester', $semester);
        }

        $rows = $b->orderBy('parent_attendance_reports.report_date', 'DESC')
            ->orderBy('parent_attendance_reports.dismiss_time', 'ASC')
            ->get()
            ->getResultArray();

        // Group results by date
        $groups = [];
        foreach ($rows as $r) {
            $d = substr((string)($r['report_date'] ?? ''), 0, 10);
            if ($d === '') {
                $d = 'Unknown';
            }
            $groups[$d][] = $r;
        }
        uksort($groups, static function ($a, $b) {
            if ($a === 'Unknown' && $b === 'Unknown') {
                return 0;
            }
            if ($a === 'Unknown') {
                return 1;
            }
            if ($b === 'Unknown') {
                return -1;
            }
            return strcmp($b, $a); // newest date first (Y-m-d sorts lexicographically)
        });

        $signatureByDate = [];
        if (!empty($groups)) {
            $dates = array_values(array_filter(array_keys($groups), static function ($d) {
                return preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) $d);
            }));
            if (!empty($dates)) {
                $sigQ = $this->earlyDismissalSignatureModel
                    ->whereIn('report_date', $dates);

                if ($schoolYear !== '') {
                    $sigQ->where('school_year', $schoolYear);
                }
                if ($semester !== '') {
                    $sigQ->where('semester', $semester);
                }

                $sigRows = $sigQ->orderBy('report_date', 'ASC')->findAll();
                foreach ($sigRows as $sig) {
                    $d = substr((string) ($sig['report_date'] ?? ''), 0, 10);
                    if ($d !== '') {
                        $signatureByDate[$d] = $sig;
                    }
                }
            }
        }

        return view('attendance/early_dismissals', [
            'groups'     => $groups,
            'schoolYear' => $schoolYear,
            'semester'   => $semester,
            'signatureByDate' => $signatureByDate,
        ]);
    }

    private function ensureUploadSubdir(string $subdir): string
    {
        $base = rtrim(WRITEPATH, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'uploads';
        $dir  = $base . DIRECTORY_SEPARATOR . trim($subdir, '/\\');

        if (!is_dir($dir)) {
            if (!@mkdir($dir, 0755, true) && !is_dir($dir)) {
                log_message('error', 'Failed to create upload directory: ' . $dir);
                throw new \RuntimeException('Upload directory is not available.');
            }
        }
        if (!is_writable($dir)) {
            log_message('error', 'Upload directory not writable: ' . $dir);
            throw new \RuntimeException('Upload directory is not writable.');
        }

        return $dir;
    }

    // POST: upload parent signature document for a specific early dismissal date
    public function uploadEarlyDismissalSignature()
    {
        if (! $this->canAccessEarlyDismissalsNav() || ! $this->canManageEarlyDismissals()) {
            return redirect()->to('/access_denied');
        }
        if (! $this->request->is('post')) {
            return redirect()->to(site_url('attendance/early-dismissals'));
        }

        $rules = [
            'report_date'    => 'required|valid_date[Y-m-d]',
            'signature_file' => 'uploaded[signature_file]'
                . '|max_size[signature_file,4096]'
                . '|ext_in[signature_file,jpg,jpeg,png,webp,gif,pdf]'
                . '|mime_in[signature_file,image/jpg,image/jpeg,image/png,image/webp,image/gif,application/pdf]',
            'school_year'    => 'permit_empty|string',
            'semester'       => 'permit_empty|string',
            'return_url'     => 'permit_empty|string',
        ];

        $messages = [
            'signature_file' => [
                'uploaded' => 'Signature file is required.',
                'max_size' => 'Maximum file size is 4MB.',
                'ext_in'   => 'Allowed formats: JPG, JPEG, PNG, WEBP, GIF, or PDF.',
                'mime_in'  => 'Allowed formats: JPG, JPEG, PNG, WEBP, GIF, or PDF.',
            ],
        ];

        if (! $this->validate($rules, $messages)) {
            return redirect()->back()->withInput()->with('error', $this->validator->listErrors());
        }

        $reportDate = substr((string) $this->request->getPost('report_date'), 0, 10);
        $returnUrl  = (string) ($this->request->getPost('return_url') ?? site_url('attendance/early-dismissals'));
        $schoolYear = (string) ($this->request->getPost('school_year') ?? $this->configModel->getConfig('school_year'));
        $semesterRaw = (string) ($this->request->getPost('semester') ?? '');
        $hasSemesterFilter = ($semesterRaw !== '');
        $semester = $hasSemesterFilter ? $semesterRaw : null;

        $dt = \DateTime::createFromFormat('Y-m-d', $reportDate);
        if (!$dt || (int) $dt->format('w') !== 0) {
            return redirect()->to($returnUrl)->with('error', 'Please select a Sunday date.');
        }

        $existsQ = $this->reportModel->builder()
            ->select('id')
            ->where('type', 'early_dismissal')
            ->where('report_date', $reportDate);
        if ($schoolYear !== '') {
            $existsQ->where('school_year', $schoolYear);
        }
        if ($hasSemesterFilter) {
            $existsQ->where('semester', $semester);
        }
        $exists = $existsQ->get(1)->getRowArray();
        if (!$exists) {
            return redirect()->to($returnUrl)->with('error', 'No early dismissal records found for that date.');
        }

        $file = $this->request->getFile('signature_file');
        if (! $file || ! $file->isValid() || $file->hasMoved()) {
            return redirect()->to($returnUrl)->with('error', 'Invalid signature file.');
        }

        try {
            $this->ensureUploadSubdir('early_dismissal_signatures');
            $stored = $file->store('early_dismissal_signatures');
        } catch (\Throwable $e) {
            log_message('error', 'Signature upload failed: ' . $e->getMessage());
            return redirect()->to($returnUrl)->with('error', 'Unable to save the signature file.');
        }

        if (!$stored) {
            return redirect()->to($returnUrl)->with('error', 'Unable to save the signature file.');
        }

        $filename = basename($stored);
        $payload = [
            'report_date'   => $reportDate,
            'school_year'   => $schoolYear !== '' ? $schoolYear : null,
            'semester'      => $semester,
            'filename'      => $filename,
            'original_name' => $file->getClientName(),
            'mime_type'     => $file->getClientMimeType(),
            'file_size'     => (int) ($file->getSize() ?? 0),
            'uploaded_by'   => (int) (session()->get('user_id') ?? 0),
        ];

        $existingQ = $this->earlyDismissalSignatureModel
            ->where('report_date', $reportDate);
        if ($schoolYear !== '') {
            $existingQ->where('school_year', $schoolYear);
        }
        if ($hasSemesterFilter) {
            $existingQ->where('semester', $semester);
        }
        $existing = $existingQ->first();

        try {
            if ($existing && !empty($existing['id'])) {
                $oldFile = (string) ($existing['filename'] ?? '');
                $this->earlyDismissalSignatureModel->update((int) $existing['id'], $payload);
                if ($oldFile !== '' && $oldFile !== $filename) {
                    $oldPath = WRITEPATH . 'uploads/early_dismissal_signatures/' . $oldFile;
                    if (is_file($oldPath)) {
                        @unlink($oldPath);
                    }
                }
            } else {
                $this->earlyDismissalSignatureModel->insert($payload);
            }
        } catch (\Throwable $e) {
            log_message('error', 'Signature DB save failed: ' . $e->getMessage());
            return redirect()->to($returnUrl)->with('error', 'Unable to record the signature file.');
        }

        return redirect()->to($returnUrl)->with('message', 'Signature uploaded for ' . esc($reportDate) . '.');
    }


    // GET: form for staff to add an early dismissal entry
    public function addEarlyDismissalForm()
    {
        $role = strtolower((string) (session()->get('role') ?? ''));
        if (!in_array($role, ['teacher', 'teacher_assistant', 'administrator', 'admin'], true)) {
            return redirect()->to('/access_denied');
        }

        $schoolYear = (string)$this->configModel->getConfig('school_year');
        $semester   = (string)$this->configModel->getConfig('semester');

        // Fetch students with their current-year class section (if any)
        try {
            $students = $this->db->table('students s')
                ->select('s.id, s.firstname, s.lastname, s.parent_id, cs.class_section_name, sc.class_section_id')
                ->join('student_class sc', 'sc.student_id = s.id AND sc.school_year = ' . $this->db->escape($schoolYear), 'left')
                ->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'left')
                ->orderBy('s.lastname', 'ASC')
                ->orderBy('s.firstname', 'ASC')
                ->get()->getResultArray();
        } catch (\Throwable $e) {
            $students = [];
        }

        // Suggest upcoming Sunday (or today if Sunday)
        [$_, $defaultDate] = $this->computeSundays(0, 26);

        return view('attendance/early_dismissals_add', [
            'students'    => $students,
            'defaultDate' => $defaultDate,
            'schoolYear'  => $schoolYear,
            'semester'    => $semester,
        ]);
    }

    // POST: save staff-created early dismissal
    public function saveEarlyDismissal()
    {
        $role = strtolower((string) (session()->get('role') ?? ''));
        if (!in_array($role, ['teacher', 'teacher_assistant', 'administrator', 'admin'], true)) {
            return redirect()->to('/access_denied');
        }

        $rules = [
            'student_id'   => 'required|is_natural_no_zero',
            'date'         => 'required|valid_date[Y-m-d]',
            'dismiss_time' => 'required|regex_match[/^\d{2}:\d{2}(:\d{2})?$/]',
            'reason'       => 'permit_empty|string',
        ];
        if (! $this->validate($rules)) {
            return redirect()->back()->withInput()->with('error', implode(' ', $this->validator->getErrors()));
        }

        $studentId   = (int) $this->request->getPost('student_id');
        $reportDate  = substr((string) $this->request->getPost('date'), 0, 10);
        $dismissTime = (string) $this->request->getPost('dismiss_time');
        $reason      = (string) ($this->request->getPost('reason') ?? '');

        // Sunday-only enforcement (staff can enter past Sundays if needed)
        $dt = \DateTime::createFromFormat('Y-m-d', $reportDate);
        if (!$dt || (int)$dt->format('w') !== 0) {
            return redirect()->back()->withInput()->with('error', 'Please select a Sunday date.');
        }

        $schoolYear = (string)$this->configModel->getConfig('school_year');
        $semester   = (string)$this->configModel->getConfig('semester');

        // Resolve parent_id of the student
        $stu = $this->studentModel->select('id, parent_id, firstname, lastname')->find($studentId);
        if (!$stu || empty($stu['parent_id'])) {
            return redirect()->back()->withInput()->with('error', 'Selected student not found or has no primary parent assigned.');
        }
        $parentId = (int) $stu['parent_id'];

        // Resolve class_section_id for current school year
        $sectionRow = $this->db->table('student_class')
            ->select('class_section_id')
            ->where('student_id', $studentId)
            ->where('school_year', $schoolYear)
            ->orderBy('updated_at', 'DESC')
            ->limit(1)
            ->get()->getRowArray();
        $classSectionId = isset($sectionRow['class_section_id']) ? (int)$sectionRow['class_section_id'] : null;

        try {
            // Block if Absent/Late exists for this date
            $qb = $this->reportModel->builder();
            $qb->where('student_id', $studentId)
                ->where('report_date', $reportDate)
                ->where('school_year', $schoolYear);

            $existingAL = (clone $qb)
                ->groupStart()
                ->where('type', 'absent')
                ->orWhere('type', 'late')
                ->groupEnd()
                ->get()->getRowArray();

            if ($existingAL) {
                return redirect()->back()->withInput()->with('error', 'Absent/Late already submitted for this student on the selected date.');
            }

            // If early dismissal already exists, update time; else insert new
            $existingED = (clone $qb)->where('type', 'early_dismissal')->get()->getFirstRow('array');
            if ($existingED) {
                $this->reportModel->update((int)$existingED['id'], [
                    'dismiss_time' => $dismissTime ?: $existingED['dismiss_time'],
                    'reason'       => $reason ?: ($existingED['reason'] ?? null),
                    'status'       => 'new',
                ]);
            } else {
                $this->reportModel->insert([
                    'parent_id'        => $parentId,
                    'student_id'       => $studentId,
                    'class_section_id' => $classSectionId,
                    'report_date'      => $reportDate,
                    'type'             => 'early_dismissal',
                    'dismiss_time'     => $dismissTime,
                    'reason'           => ($reason !== '') ? $reason : null,
                    'semester'         => $semester,
                    'school_year'      => $schoolYear,
                    'status'           => 'new',
                ], false);
            }
        } catch (\Throwable $e) {
            log_message('error', 'Admin add early dismissal failed: ' . $e->getMessage());
            return redirect()->back()->withInput()->with('error', 'Could not save early dismissal. Please try again.');
        }

        $name = trim(($stu['firstname'] ?? '') . ' ' . ($stu['lastname'] ?? ''));
        return redirect()->to(site_url('attendance/early-dismissals'))
            ->with('message', 'Early dismissal saved for ' . esc($name) . ' on ' . esc($reportDate) . ' at ' . esc(substr($dismissTime, 0, 5)) . '.');
    }



    // POST (AJAX): check existing submissions for given date and student IDs
    public function checkExisting()
    {
        if (! $this->request->isAJAX()) {
            return $this->response->setStatusCode(405)->setJSON(['ok' => false, 'error' => 'Method not allowed']);
        }

        $datesPost = $this->request->getPost('dates');
        $dateSingle = $this->request->getPost('date');
        $dateValues = [];
        if (is_array($datesPost)) {
            foreach ($datesPost as $d) {
                $dateValues[] = substr((string) $d, 0, 10);
            }
        } elseif ($dateSingle !== null) {
            $dateValues[] = substr((string) $dateSingle, 0, 10);
        }
        $dateValues = array_values(array_unique(array_filter($dateValues, static function ($d) {
            return preg_match('/^\d{4}-\d{2}-\d{2}$/', $d);
        })));
        $type = (string) $this->request->getPost('type');
        $ids  = (array) $this->request->getPost('student_ids');

        // Basic validation
        if (empty($dateValues)) {
            return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid date']);
        }
        if (!in_array($type, ['absent', 'late', 'early_dismissal'], true)) {
            return $this->response->setStatusCode(400)->setJSON(['ok' => false, 'error' => 'Invalid type']);
        }
        $studentIds = array_values(array_unique(array_map('intval', $ids)));
        $studentIds = array_filter($studentIds, static fn($v) => $v > 0);
        if (empty($studentIds)) {
            return $this->response->setJSON(['ok' => true, 'students' => []]);
        }

        // Fetch existing submissions for these students on this date
        $rows = $this->reportModel->builder()
            ->select('parent_attendance_reports.student_id, parent_attendance_reports.type, parent_attendance_reports.report_date, s.firstname, s.lastname')
            ->join('students s', 's.id = parent_attendance_reports.student_id', 'left')
            ->whereIn('parent_attendance_reports.report_date', $dateValues)
            ->whereIn('parent_attendance_reports.student_id', $studentIds)
            ->get()->getResultArray();

        $map = [];
        foreach ($rows as $r) {
            $sid = (int) ($r['student_id'] ?? 0);
            if ($sid <= 0) continue;
            if (!isset($map[$sid])) {
                $map[$sid] = [
                    'student_id' => $sid,
                    'firstname'  => (string) ($r['firstname'] ?? ''),
                    'lastname'   => (string) ($r['lastname'] ?? ''),
                    'dates'      => [],
                ];
            }
            $dVal = substr((string) ($r['report_date'] ?? ''), 0, 10);
            if ($dVal === '') {
                continue;
            }
            if (!isset($map[$sid]['dates'][$dVal])) {
                $map[$sid]['dates'][$dVal] = [];
            }
            $t = (string) ($r['type'] ?? '');
            if ($t !== '' && !in_array($t, $map[$sid]['dates'][$dVal], true)) {
                $map[$sid]['dates'][$dVal][] = $t;
            }
        }

        return $this->response->setJSON([
            'ok'       => true,
            'students' => array_values($map),
        ]);
    }

    // POST: update an existing parent report (limited fields) before 9:00 AM on report date
    public function update()
    {
        $parentId = $this->resolvePrimaryParentIdFromSession();
        if (!$parentId) {
            return redirect()->to('/login');
        }

        $id = (int) ($this->request->getPost('id') ?? 0);
        if ($id <= 0) {
            return redirect()->back()->with('error', 'Invalid report.');
        }

        $row = $this->reportModel->find($id);
        if (!$row || (int)($row['parent_id'] ?? 0) !== (int)$parentId) {
            return redirect()->back()->with('error', 'Report not found.');
        }

        // Only allow editing for 'new' status entries
        $status = (string) ($row['status'] ?? '');
        if ($status !== 'new') {
            return redirect()->back()->with('error', 'This report can no longer be edited.');
        }

        // Enforce 9:00 AM cutoff on the report_date (school timezone)
        try {
            $tzName = (string) (config('School')->attendance['timezone'] ?? user_timezone());
            $tz     = new \DateTimeZone($tzName ?: 'UTC');
            $cutoff = new \DateTime(($row['report_date'] ?? '') . ' 09:00:00', $tz);
            $now    = new \DateTime('now', $tz);
            if ($now >= $cutoff) {
                return redirect()->back()->with('error', 'Editing window closed at 9:00 AM on the report date.');
            }
        } catch (\Throwable $e) {
            log_message('warning', 'Edit cutoff check failed: ' . $e->getMessage());
            // Fail-safe: do not allow edit if cutoff cannot be computed reliably
            return redirect()->back()->with('error', 'Editing is unavailable at this time.');
        }

        // Collect permissible fields
        $type = (string) ($row['type'] ?? '');
        $reason = trim((string) ($this->request->getPost('reason') ?? ''));
        $arrival = (string) ($this->request->getPost('arrival_time') ?? '');
        $dismiss = (string) ($this->request->getPost('dismiss_time') ?? '');

        $update = [];
        if ($reason !== '') {
            $update['reason'] = $reason;
        } else {
            $update['reason'] = null; // allow clearing
        }

        if ($type === 'late') {
            if ($arrival !== '') {
                if (!preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $arrival)) {
                    return redirect()->back()->with('error', 'Invalid arrival time format.');
                }
                $update['arrival_time'] = $arrival;
            }
        } elseif ($type === 'early_dismissal') {
            if ($dismiss !== '') {
                if (!preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $dismiss)) {
                    return redirect()->back()->with('error', 'Invalid dismissal time format.');
                }
                $update['dismiss_time'] = $dismiss;
            }
        }

        if (empty($update)) {
            return redirect()->back()->with('error', 'Nothing to update.');
        }

        try {
            $this->reportModel->update($id, $update);
        } catch (\Throwable $e) {
            log_message('error', 'Parent report update failed: ' . $e->getMessage());
            return redirect()->back()->with('error', 'Could not save changes.');
        }

        return redirect()->to(site_url('parent/report-attendance'))->with('message', 'Changes saved.');
    }
    private function resolvePrimaryParentIdFromSession(): ?int
    {
        $userId   = (int) (session()->get('user_id') ?? 0);
        $userType = (string) (session()->get('user_type') ?? '');
        if ($userId <= 0) return null;

        if ($userType === 'primary' || $userType === '') {
            return $userId;
        }

        if ($userType === 'secondary') {
            $parentData = $this->db->table('parents')
                ->select('parent_id')
                ->where('secondparent_user_id', $userId)
                ->orderBy('updated_at', 'DESC')
                ->limit(1)
                ->get()->getRowArray();
            if (!empty($parentData['parent_id'])) {
                return (int) $parentData['parent_id'];
            }
        }

        if ($userType === 'tertiary') {
            $authUserData = $this->db->table('authorized_users')
                ->select('user_id as parent_id')
                ->where('authorized_user_id', $userId)
                ->orderBy('updated_at', 'DESC')
                ->limit(1)
                ->get()->getRowArray();
            if (!empty($authUserData['parent_id'])) {
                return (int) $authUserData['parent_id'];
            }
        }

        return $userId; // fallback
    }

    private function normalizeCutoffTime(string $raw, string $default = '09:00'): string
    {
        $raw = trim($raw);
        if ($raw === '') {
            return $default;
        }
        if (preg_match('/^(\d{1,2}):(\d{2})$/', $raw, $m)) {
            $hh = str_pad((string) ((int) $m[1]), 2, '0', STR_PAD_LEFT);
            $mm = $m[2];
            return $hh . ':' . $mm;
        }
        return $default;
    }

    /**
     * Send confirmation/notification emails for parent attendance submissions.
     */
    private function sendParentReportNotifications(array $context): void
    {
        $parentId    = (int) ($context['parent_id'] ?? 0);
        $submitterId = (int) ($context['submitter_id'] ?? 0);
        $students    = $context['students'] ?? [];
        if ($parentId <= 0 || empty($students) || !is_array($students)) {
            return;
        }

        $primaryParent = $this->getUserContactRow($parentId);
        $submitter     = $submitterId > 0 ? $this->getUserContactRow($submitterId) : null;

        if (!$primaryParent && !$submitter) {
            return;
        }
        if (!$submitter) {
            $submitter = $primaryParent;
        }

        $type          = (string) ($context['type'] ?? 'absent');
        $typeLabel     = $this->formatParentReportType($type);
        $reportDate    = (string) ($context['report_date'] ?? '');
        $dateCodesRaw  = (array) ($context['dates'] ?? []);
        $dateCodes     = [];
        foreach ($dateCodesRaw as $code) {
            $ymd = substr((string) $code, 0, 10);
            if ($ymd === '' || isset($dateCodes[$ymd])) {
                continue;
            }
            $dateCodes[$ymd] = $ymd;
        }
        if (empty($dateCodes) && $reportDate !== '') {
            $dateCodes[$reportDate] = $reportDate;
        }
        $dateCodes = array_values($dateCodes);
        $effectiveDate = $dateCodes[0] ?? $reportDate;
        $reportDateFmt = $this->formatReportDate($effectiveDate);
        $dateLabels = array_map(function ($d) {
            return $this->formatReportDate($d) ?? $d;
        }, $dateCodes);
        $reason        = trim((string) ($context['reason'] ?? ''));
        $semester      = (string) ($context['semester'] ?? '');
        $schoolYear    = (string) ($context['school_year'] ?? '');

        $payload = [
            'recipient_parent' => $submitter,
            'primary_parent'   => $primaryParent,
            'students'         => $students,
            'student_count'    => count($students),
            'type'             => $type,
            'type_label'       => $typeLabel,
            'report_date'      => $effectiveDate,
            'report_date_fmt'  => $reportDateFmt,
            'dates'            => $dateCodes,
            'date_labels'      => $dateLabels,
            'date_summary'     => $this->formatDateList($dateCodes),
            'reason'           => $reason,
            'semester'         => $semester,
            'school_year'      => $schoolYear,
            'submitted_at'     => local_date(utc_now(), 'm-d-Y g:i A'),
        ];

        $mailer = new EmailController();

        $recipientEmail = trim((string) ($submitter['email'] ?? ''));
        if ($recipientEmail === '' && $primaryParent && $primaryParent !== $submitter) {
            $recipientEmail = trim((string) ($primaryParent['email'] ?? ''));
        }

        $subjectType = $typeLabel ?: 'Attendance';
        $subjectTypeClean = ucfirst(strtolower($subjectType));
        $subjectDateSuffix = $reportDateFmt ?: ($effectiveDate ?: '');

        if ($recipientEmail !== '') {
            $subjectParent = $subjectTypeClean . ' Request Received';
            $parentBody = view('emails/parent_attendance_parent', $payload + ['subject' => $subjectParent]);
            if (! $mailer->sendEmail($recipientEmail, $subjectParent, $parentBody)) {
                log_message('error', 'Failed to send parent attendance confirmation to ' . $recipientEmail);
            }
        }

        $subjectAdmin = $subjectTypeClean . ' Request';
        $adminBody = view('emails/parent_attendance_admin', $payload + ['subject' => $subjectAdmin]);
        $adminRecipient = $this->adminParentReportEmail();
        if ($adminRecipient === '' || ! $mailer->sendEmail($adminRecipient, $subjectAdmin, $adminBody)) {
            log_message('error', 'Failed to send parent attendance alert to admin inbox.');
        }
    }

    private function getUserContactRow(?int $userId): ?array
    {
        $uid = (int) ($userId ?? 0);
        if ($uid <= 0) {
            return null;
        }

        $row = $this->db->table('users')
            ->select('id, firstname, lastname, email, cellphone')
            ->where('id', $uid)
            ->limit(1)
            ->get()
            ->getRowArray();

        return $row ?: null;
    }

    private function resolveClassSectionLabel(?int $classSectionId): string
    {
        $cid = (int) ($classSectionId ?? 0);
        if ($cid <= 0) {
            return 'Not Assigned';
        }
        try {
            $name = $this->classSectionModel->getClassSectionNameBySectionId($cid);
            if (is_string($name) && $name !== '') {
                return $name;
            }
        } catch (\Throwable $e) {
            log_message('debug', 'Failed to resolve class section label: ' . $e->getMessage());
        }
        return 'Not Assigned';
    }

    private function formatParentReportType(string $type): string
    {
        return match ($type) {
            'late'            => 'Late Arrival',
            'early_dismissal' => 'Early Dismissal',
            default           => 'Absence',
        };
    }

    private function formatReportDate(?string $ymd): ?string
    {
        if (!$ymd) {
            return null;
        }
        try {
            $dt = new \DateTime($ymd);
            return $dt->format('m-d-Y');
        } catch (\Throwable $e) {
            return $ymd;
        }
    }

    private function formatTimeDisplay(?string $time): ?string
    {
        $t = trim((string) ($time ?? ''));
        if ($t === '') {
            return null;
        }
        $dt = \DateTime::createFromFormat('H:i:s', $t)
            ?: \DateTime::createFromFormat('H:i', $t);
        if ($dt instanceof \DateTime) {
            return $dt->format('g:i A');
        }
        return $t;
    }

    private function formatDateList(array $dates): string
    {
        $seen = [];
        $labels = [];
        foreach ($dates as $d) {
            $ymd = substr((string) $d, 0, 10);
            if ($ymd === '' || isset($seen[$ymd])) {
                continue;
            }
            $seen[$ymd] = true;
            $labels[] = $this->formatReportDate($ymd) ?? $ymd;
        }
        return implode(', ', array_filter($labels, static function ($v) {
            return $v !== null && $v !== '';
        }));
    }

    private function adminParentReportEmail(): string
    {
        $candidates = [
            (string) env('MAIL_PARENT_REPORT_TO', ''),
            (string) env('MAIL_DEFAULT_REPLY_TO', ''),
            (string) env('MAIL_FROM_ADDRESS', ''),
        ];

        foreach ($candidates as $value) {
            $trimmed = trim($value);
            if ($trimmed !== '') {
                return $trimmed;
            }
        }

        $emailConfig = config('Email');
        if ($emailConfig && !empty($emailConfig->fromEmail)) {
            return $emailConfig->fromEmail;
        }

        return '';
    }
}
