<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Api\Core\BaseApiController;
use App\Models\AttendanceData;
use App\Models\AttendanceDay;
use App\Models\AttendanceRecord;
use App\Models\Configuration;
use App\Models\CurrentFlag;
use App\Models\LateSlipLog;
use App\Models\Payment;
use App\Models\SemesterScore;
use App\Models\StaffAttendance;
use App\Models\Student;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

class ScannerController extends BaseApiController
{
    private const LATE_CUTOFF_CONFIG_KEY = 'attendance_late_cutoff_time';
    private const DEFAULT_LATE_CUTOFF = '10:00 AM';

    public function process(Request $request): JsonResponse
    {
        $data = $request->json()->all();
        if (!is_array($data) || $data === []) {
            $data = $request->all();
        }

        $badgeCode = trim((string) ($data['badgeCode'] ?? $data['BadgeCode'] ?? $data['badge_code'] ?? $data['barcode'] ?? $data['Barcode'] ?? ''));
        $stationId = trim((string) ($data['stationId'] ?? $data['StationId'] ?? $data['station_id'] ?? $data['deviceId'] ?? $data['DeviceId'] ?? ''));
        $action = strtolower(trim((string) ($data['action'] ?? 'attendance')));
        $scanStatus = strtolower(trim((string) ($data['status'] ?? 'present')));
        $scannedAt = $this->normalizeScannedAt((string) ($data['scannedAt'] ?? $data['ScannedAt'] ?? $data['scanned_at'] ?? ''));
        $operatorId = (int) (auth()->id() ?? 0);

        if ($badgeCode === '') {
            $this->logScan($badgeCode, null, null, $action, 'rejected', $operatorId, $stationId, 'Badge code is required.', $scannedAt);
            return response()->json($this->failure('Badge code is required.'), 400);
        }

        if (!in_array($scanStatus, ['present', 'late'], true)) {
            $scanStatus = 'present';
        }

        $student = $this->findStudentByBadge($badgeCode);
        if ($student) {
            return $this->processStudentScan($student, $badgeCode, $stationId, $action, $scanStatus, $scannedAt, $operatorId);
        }

        $staffUser = $this->findStaffUserByBadge($badgeCode);
        if ($staffUser) {
            return $this->processStaffScan($staffUser, $badgeCode, $stationId, $action, $scanStatus, $scannedAt, $operatorId);
        }

        $this->logScan($badgeCode, null, null, $action, 'not_found', $operatorId, $stationId, 'Badge not found.', $scannedAt);
        return response()->json($this->failure('Badge not found.'), 404);
    }

    private function processStudentScan(Student $student, string $badgeCode, string $stationId, string $action, string $status, string $scannedAt, int $operatorId): JsonResponse
    {
        $term = $this->currentTerm($student->toArray());
        $schoolNow = $this->schoolLocalDateTime($scannedAt);
        $date = $schoolNow->format('Y-m-d');
        $lateCutoff = $this->lateCutoffForDate($schoolNow);
        $autoLate = $this->isAfterLateCutoff($schoolNow, $lateCutoff);
        if ($autoLate) {
            $status = 'late';
        }

        $assignment = $this->studentAssignment((int) $student->id, $term['semester'], $term['school_year']);
        if (!$assignment) {
            $this->logScan($badgeCode, 'student', (int) $student->id, $action, 'rejected', $operatorId, $stationId, 'Student has no class assignment for current term.', $scannedAt);
            return response()->json($this->failure('Student has no class assignment for current term.'), 409);
        }

        $classSectionId = (int) $assignment['class_section_id'];
        $classId = (int) ($assignment['class_id'] ?? 0);
        $existing = $this->findAttendanceRow((int) $student->id, (string) ($student->school_id ?? ''), $date, $term['semester'], $term['school_year']);
        $oldStatus = $existing?->status;
        $now = utc_now();

        $row = [
            'class_id' => $classId,
            'class_section_id' => $classSectionId,
            'student_id' => (int) $student->id,
            'school_id' => (string) ($student->school_id ?? ''),
            'date' => $date,
            'status' => $status,
            'reason' => null,
            'semester' => $term['semester'],
            'school_year' => $term['school_year'],
            'modified_by' => $operatorId,
            'updated_at' => $now,
        ];

        if (Schema::hasColumn('attendance_data', 'is_reported')) {
            $row['is_reported'] = 'no';
        }
        if (Schema::hasColumn('attendance_data', 'reported')) {
            $row['reported'] = 0;
        }
        if (Schema::hasColumn('attendance_data', 'is_notified')) {
            $row['is_notified'] = 'no';
        }

        if ($existing) {
            $existing->fill($row);
            $existing->save();

            if (strtolower((string) $oldStatus) !== $status) {
                $this->adjustAttendanceRecord(
                    $classSectionId,
                    (int) $student->id,
                    (string) ($student->school_id ?? ''),
                    $status,
                    $term['semester'],
                    $term['school_year'],
                    $oldStatus,
                    $operatorId
                );
            }
        } else {
            $row['created_at'] = $now;
            AttendanceData::query()->create($row);
            $this->addAttendanceRecord($classSectionId, (int) $student->id, (string) ($student->school_id ?? ''), $status, $term['semester'], $term['school_year'], $operatorId);
        }

        $this->ensureAttendanceDay($classSectionId, $date, $term['semester'], $term['school_year']);
        $message = $status === 'late' ? 'Student late attendance recorded.' : 'Student attendance recorded.';
        $this->logScan($badgeCode, 'student', (int) $student->id, $action, 'accepted', $operatorId, $stationId, $message, $scannedAt);

        $studentSummary = $this->studentSummary((int) $student->id, (int) ($student->parent_id ?? 0), $term['semester'], $term['school_year']);
        $person = $this->studentPayload($student->toArray(), $assignment);
        $lateSlip = null;
        $warnings = [];

        if ($autoLate) {
            $lateSlip = $this->createLateSlipPayload(
                $student->toArray(),
                $assignment,
                $term,
                $schoolNow,
                $lateCutoff,
                $operatorId,
                $existing ? (string) ($oldStatus ?? '') : null
            );
            $warnings[] = 'Student arrived after ' . $lateCutoff->format('h:i A') . '. Late slip receipt generated.';
        }

        return response()->json($this->successPayload([
            'personType' => 'student',
            'person' => $person,
            'Person' => $this->clientPersonPayload($person, 'student', true),
            'attendance' => [
                'status' => $status,
                'date' => $date,
                'scannedAt' => $scannedAt,
                'duplicate' => (bool) $existing,
            ],
            'Attendance' => $this->clientAttendancePayload($status, $message, $scannedAt),
            'studentSummary' => $studentSummary,
            'StudentSummary' => $this->clientStudentSummaryPayload($studentSummary),
            'lateSlip' => $lateSlip,
            'LateSlip' => $lateSlip,
            'warnings' => $warnings,
            'Warnings' => $warnings,
        ], $message));
    }

    private function processStaffScan(User $user, string $badgeCode, string $stationId, string $action, string $status, string $scannedAt, int $operatorId): JsonResponse
    {
        $term = $this->currentTerm($user->toArray());
        $date = substr($scannedAt, 0, 10);
        $roleName = $this->primaryRoleName((int) $user->id);

        StaffAttendance::upsertOne(
            (int) $user->id,
            $roleName,
            $date,
            $term['semester'],
            $term['school_year'],
            $status,
            null,
            $operatorId
        );

        $this->logScan($badgeCode, 'staff', (int) $user->id, $action, 'accepted', $operatorId, $stationId, 'Staff attendance recorded.', $scannedAt);

        $person = [
            'id' => (int) $user->id,
            'name' => trim((string) ($user->firstname ?? '') . ' ' . (string) ($user->lastname ?? '')),
            'email' => $user->email,
            'role' => $roleName,
            'badgeCode' => $badgeCode,
        ];

        return response()->json($this->successPayload([
            'personType' => 'staff',
            'person' => $person,
            'Person' => $this->clientPersonPayload($person, 'staff', true),
            'attendance' => [
                'status' => $status,
                'date' => $date,
                'scannedAt' => $scannedAt,
            ],
            'Attendance' => $this->clientAttendancePayload($status, 'Staff attendance recorded.', $scannedAt),
            'studentSummary' => null,
            'StudentSummary' => null,
            'warnings' => [],
            'Warnings' => [],
        ], 'Staff attendance recorded.'));
    }

    private function findStudentByBadge(string $badgeCode): ?Student
    {
        return Student::query()
            ->where(function ($query) use ($badgeCode) {
                $query->where('rfid_tag', $badgeCode)
                    ->orWhere('school_id', $badgeCode);
            })
            ->first();
    }

    private function findStaffUserByBadge(string $badgeCode): ?User
    {
        $user = User::query()
            ->where(function ($query) use ($badgeCode) {
                $query->where('rfid_tag', $badgeCode);
                if (ctype_digit($badgeCode)) {
                    $query->orWhere('school_id', (int) $badgeCode);
                }
            })
            ->first();

        if (!$user || !$this->isStaffUser((int) $user->id)) {
            return null;
        }

        return $user;
    }

    private function isStaffUser(int $userId): bool
    {
        $roles = DB::table('user_roles as ur')
            ->selectRaw('LOWER(r.name) AS role_name')
            ->leftJoin('roles as r', 'r.id', '=', 'ur.role_id')
            ->where('ur.user_id', $userId)
            ->get();

        foreach ($roles as $role) {
            $name = trim((string) ($role->role_name ?? ''));
            if ($name !== '' && !in_array($name, ['parent', 'guest', 'student'], true)) {
                return true;
            }
        }

        return false;
    }

    private function primaryRoleName(int $userId): ?string
    {
        return DB::table('user_roles as ur')
            ->leftJoin('roles as r', 'r.id', '=', 'ur.role_id')
            ->where('ur.user_id', $userId)
            ->orderBy('r.name')
            ->value('r.name');
    }

    private function studentAssignment(int $studentId, string $semester, string $schoolYear): ?array
    {
        $builder = DB::table('student_class as sc')
            ->select('sc.class_section_id', 'cs.class_id', 'cs.class_section_name')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
            ->where('sc.student_id', $studentId);

        if (Schema::hasColumn('student_class', 'school_year')) {
            $builder->where('sc.school_year', $schoolYear);
        }
        if (Schema::hasColumn('student_class', 'semester')) {
            $builder->where('sc.semester', $semester);
        }

        $row = $builder->orderByDesc('sc.id')->first();
        if (!$row && Schema::hasColumn('student_class', 'school_year')) {
            $row = DB::table('student_class as sc')
                ->select('sc.class_section_id', 'cs.class_id', 'cs.class_section_name')
                ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
                ->where('sc.student_id', $studentId)
                ->where('sc.school_year', $schoolYear)
                ->orderByDesc('sc.id')
                ->first();
        }

        return $row ? (array) $row : null;
    }

    private function findAttendanceRow(int $studentId, string $schoolId, string $date, string $semester, string $schoolYear): ?AttendanceData
    {
        $row = AttendanceData::query()
            ->where('student_id', $studentId)
            ->whereDate('date', $date)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->first();

        if ($row) {
            return $row;
        }

        return AttendanceData::query()
            ->where('school_id', $schoolId)
            ->whereDate('date', $date)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->first();
    }

    private function adjustAttendanceRecord(int $classSectionId, int $studentId, string $schoolId, string $newStatus, string $semester, string $schoolYear, ?string $oldStatus, int $operatorId): void
    {
        $record = AttendanceRecord::query()
            ->where('student_id', $studentId)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->first();

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

        $presence = (int) ($record->total_presence ?? 0);
        $absence = (int) ($record->total_absence ?? 0);
        $late = (int) ($record->total_late ?? 0);

        if ($oldStatus === 'present') {
            $presence--;
        } elseif ($oldStatus === 'absent') {
            $absence--;
        } elseif ($oldStatus === 'late') {
            $late--;
        }

        if ($newStatus === 'present') {
            $presence++;
        } elseif ($newStatus === 'absent') {
            $absence++;
        } elseif ($newStatus === 'late') {
            $late++;
        }

        $record->fill([
            'class_section_id' => $classSectionId,
            'total_presence' => max(0, $presence),
            'total_absence' => max(0, $absence),
            'total_late' => max(0, $late),
            'modified_by' => $operatorId,
            'updated_at' => utc_now(),
        ]);
        $record->save();
    }

    private function addAttendanceRecord(int $classSectionId, int $studentId, string $schoolId, string $status, string $semester, string $schoolYear, int $operatorId): void
    {
        $record = AttendanceRecord::query()
            ->where('student_id', $studentId)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->first();

        $field = $status === 'late' ? 'total_late' : ($status === 'absent' ? 'total_absence' : 'total_presence');
        if ($record) {
            $record->fill([
                'class_section_id' => $classSectionId,
                $field => (int) ($record->{$field} ?? 0) + 1,
                'total_attendance' => (int) ($record->total_attendance ?? 0) + 1,
                'modified_by' => $operatorId,
                'updated_at' => utc_now(),
            ]);
            $record->save();
            return;
        }

        AttendanceRecord::query()->create([
            'class_section_id' => $classSectionId,
            'student_id' => $studentId,
            'school_id' => $schoolId,
            'total_presence' => $status === 'present' ? 1 : 0,
            'total_absence' => $status === 'absent' ? 1 : 0,
            'total_late' => $status === 'late' ? 1 : 0,
            'total_attendance' => 1,
            'semester' => $semester,
            'school_year' => $schoolYear,
            'modified_by' => $operatorId,
            'created_at' => utc_now(),
            'updated_at' => utc_now(),
        ]);
    }

    private function ensureAttendanceDay(int $classSectionId, string $date, string $semester, string $schoolYear): void
    {
        if (AttendanceDay::findBySectionDate($classSectionId, $date, $semester, $schoolYear)) {
            return;
        }

        AttendanceDay::query()->create([
            'class_section_id' => $classSectionId,
            'date' => $date,
            'status' => 'draft',
            'semester' => $semester,
            'school_year' => $schoolYear,
            'created_at' => utc_now(),
            'updated_at' => utc_now(),
        ]);
    }

    private function studentPayload(array $student, array $assignment): array
    {
        return [
            'id' => (int) ($student['id'] ?? 0),
            'schoolId' => $student['school_id'] ?? null,
            'name' => trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? '')),
            'firstName' => $student['firstname'] ?? null,
            'lastName' => $student['lastname'] ?? null,
            'grade' => $student['registration_grade'] ?? null,
            'classSectionId' => isset($assignment['class_section_id']) ? (int) $assignment['class_section_id'] : null,
            'classSectionName' => $assignment['class_section_name'] ?? null,
            'badgeCode' => !empty($student['rfid_tag']) ? $student['rfid_tag'] : ($student['school_id'] ?? null),
        ];
    }

    private function studentSummary(int $studentId, int $parentId, string $semester, string $schoolYear): array
    {
        $attendance = AttendanceRecord::query()
            ->where('student_id', $studentId)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->first();

        $score = SemesterScore::query()
            ->where('student_id', $studentId)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->orderByDesc('id')
            ->first();

        $flags = CurrentFlag::query()
            ->where('student_id', $studentId)
            ->where('school_year', $schoolYear)
            ->whereIn('flag_state', ['open', 'Opened', 'Open'])
            ->orderByDesc('flag_datetime')
            ->limit(10)
            ->get();

        $payments = Payment::query()
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->orderByDesc('payment_date')
            ->limit(5)
            ->get();

        return [
            'attendance' => [
                'present' => (int) ($attendance->total_presence ?? 0),
                'late' => (int) ($attendance->total_late ?? 0),
                'absent' => (int) ($attendance->total_absence ?? 0),
                'total' => (int) ($attendance->total_attendance ?? 0),
                'score' => isset($score?->attendance_score) ? (float) $score->attendance_score : null,
            ],
            'grading' => [
                'homeworkAvg' => isset($score?->homework_avg) ? (float) $score->homework_avg : null,
                'quizAvg' => isset($score?->quiz_avg) ? (float) $score->quiz_avg : null,
                'projectAvg' => isset($score?->project_avg) ? (float) $score->project_avg : null,
                'midtermExamScore' => isset($score?->midterm_exam_score) ? (float) $score->midterm_exam_score : null,
                'finalExamScore' => isset($score?->final_exam_score) ? (float) $score->final_exam_score : null,
                'semesterScore' => isset($score?->semester_score) ? (float) $score->semester_score : null,
            ],
            'discipline' => $flags->map(static fn (CurrentFlag $flag): array => [
                'id' => (int) $flag->id,
                'flag' => $flag->flag,
                'state' => $flag->flag_state,
                'description' => $flag->open_description,
                'date' => $flag->flag_datetime?->toDateTimeString(),
            ])->all(),
            'payments' => $payments->map(static fn (Payment $payment): array => [
                'id' => (int) $payment->id,
                'invoiceId' => isset($payment->invoice_id) ? (int) $payment->invoice_id : null,
                'totalAmount' => isset($payment->total_amount) ? (float) $payment->total_amount : null,
                'paidAmount' => isset($payment->paid_amount) ? (float) $payment->paid_amount : null,
                'balance' => isset($payment->balance) ? (float) $payment->balance : null,
                'status' => $payment->status,
                'date' => $payment->payment_date?->toDateTimeString(),
            ])->all(),
        ];
    }

    private function clientPersonPayload(array $person, string $type, bool $active): array
    {
        return [
            'id' => (int) ($person['id'] ?? 0),
            'name' => (string) ($person['name'] ?? ''),
            'type' => $type,
            'barcode' => (string) ($person['badgeCode'] ?? $person['schoolId'] ?? ''),
            'active' => $active,
        ];
    }

    private function clientAttendancePayload(string $status, string $message, string $scannedAt): array
    {
        return [
            'status' => $status,
            'message' => $message,
            'time' => gmdate('c', strtotime($scannedAt) ?: time()),
        ];
    }

    private function clientStudentSummaryPayload(array $summary): array
    {
        $attendance = $summary['attendance'] ?? [];
        $payments = $summary['payments'] ?? [];
        $lastAttendance = $attendance['lastAttendance'] ?? null;
        $outstandingBalance = 0.0;
        $paymentStatus = 'Unknown';

        if (!empty($payments)) {
            $latest = $payments[0];
            $paymentStatus = (string) ($latest['status'] ?? 'Unknown');
            $outstandingBalance = (float) ($latest['balance'] ?? 0);
        }

        $issues = $this->scanIssues($summary);

        return [
            'lastAttendance' => $lastAttendance,
            'attendanceCount' => (int) ($attendance['total'] ?? 0),
            'paymentStatus' => $paymentStatus,
            'outstandingBalance' => $outstandingBalance,
            'packageStatus' => $outstandingBalance > 0 ? 'Balance due' : 'Clear',
            'issueLevel' => $this->highestIssueLevel($issues),
            'issues' => $issues,
        ];
    }

    private function scanIssues(array $summary): array
    {
        $issues = [];
        $payments = $summary['payments'] ?? [];
        if (!empty($payments)) {
            $latest = $payments[0];
            $balance = (float) ($latest['balance'] ?? 0);
            $status = strtolower(trim((string) ($latest['status'] ?? '')));
            if ($balance > 0) {
                $issues[] = [
                    'category' => 'payment',
                    'severity' => 'critical',
                    'title' => 'Payment balance due',
                    'detail' => '$' . number_format($balance, 2) . ' outstanding',
                ];
            } elseif ($status !== '' && !in_array($status, ['paid', 'full', 'full payment', 'complete', 'completed'], true)) {
                $issues[] = [
                    'category' => 'payment',
                    'severity' => 'warning',
                    'title' => 'Payment status needs review',
                    'detail' => (string) ($latest['status'] ?? ''),
                ];
            }
        }

        $grading = $summary['grading'] ?? [];
        $semesterScore = $grading['semesterScore'] ?? null;
        if ($semesterScore !== null && (float) $semesterScore < 70) {
            $issues[] = [
                'category' => 'grades',
                'severity' => (float) $semesterScore < 60 ? 'critical' : 'warning',
                'title' => 'Grade concern',
                'detail' => 'Semester score ' . number_format((float) $semesterScore, 1),
            ];
        }

        foreach (['homeworkAvg' => 'Homework', 'quizAvg' => 'Quiz', 'projectAvg' => 'Project'] as $key => $label) {
            if (isset($grading[$key]) && $grading[$key] !== null && (float) $grading[$key] < 70) {
                $issues[] = [
                    'category' => 'grades',
                    'severity' => 'warning',
                    'title' => $label . ' average below target',
                    'detail' => number_format((float) $grading[$key], 1),
                ];
            }
        }

        $attendance = $summary['attendance'] ?? [];
        if ((int) ($attendance['late'] ?? 0) >= 3) {
            $issues[] = [
                'category' => 'attendance',
                'severity' => 'warning',
                'title' => 'Repeated lateness',
                'detail' => (int) $attendance['late'] . ' late attendance records',
            ];
        }

        foreach (($summary['discipline'] ?? []) as $flag) {
            $flagType = strtolower(trim((string) ($flag['flag'] ?? '')));
            $category = $this->scanIssueCategory($flagType);
            $issues[] = [
                'category' => $category,
                'severity' => in_array($category, ['behavior', 'discipline'], true) ? 'critical' : 'warning',
                'title' => $this->scanIssueTitle($flagType, $category),
                'detail' => trim((string) ($flag['description'] ?? '')) ?: null,
            ];
        }

        return $issues;
    }

    private function scanIssueCategory(string $flagType): string
    {
        if (str_contains($flagType, 'payment')) {
            return 'payment';
        }
        if (str_contains($flagType, 'grade')) {
            return 'grades';
        }
        if (str_contains($flagType, 'attendance') || str_contains($flagType, 'tard')) {
            return 'attendance';
        }
        if (
            str_contains($flagType, 'behavior')
            || str_contains($flagType, 'defiance')
            || str_contains($flagType, 'disrespect')
            || str_contains($flagType, 'bullying')
            || str_contains($flagType, 'fighting')
            || str_contains($flagType, 'threat')
            || str_contains($flagType, 'harassment')
        ) {
            return 'behavior';
        }

        return 'discipline';
    }

    private function scanIssueTitle(string $flagType, string $category): string
    {
        $label = trim(str_replace('_', ' ', $flagType));
        if ($label !== '') {
            return ucwords($label) . ' flag';
        }

        return match ($category) {
            'payment' => 'Payment flag',
            'grades' => 'Grade flag',
            'attendance' => 'Attendance flag',
            'behavior' => 'Behavior flag',
            default => 'Discipline flag',
        };
    }

    private function highestIssueLevel(array $issues): string
    {
        $level = 'clear';
        foreach ($issues as $issue) {
            $severity = strtolower((string) ($issue['severity'] ?? ''));
            if ($severity === 'critical') {
                return 'critical';
            }
            if ($severity === 'warning') {
                $level = 'warning';
            } elseif ($severity === 'info' && $level === 'clear') {
                $level = 'info';
            }
        }

        return $level;
    }

    private function createLateSlipPayload(array $student, array $assignment, array $term, \DateTimeImmutable $schoolNow, \DateTimeImmutable $lateCutoff, int $operatorId, ?string $previousStatus): array
    {
        $studentName = trim((string) ($student['firstname'] ?? '') . ' ' . (string) ($student['lastname'] ?? ''));
        $grade = (string) ($student['registration_grade'] ?? $assignment['class_section_name'] ?? '');
        $cutoffLabel = $lateCutoff->format('h:i A');
        $reason = 'Arrived after ' . $cutoffLabel;
        $adminName = $this->operatorName($operatorId);
        $slip = [
            'school_year' => $term['school_year'],
            'semester' => $term['semester'],
            'student_name' => $studentName,
            'slip_date' => $schoolNow->format('Y-m-d'),
            'time_in' => $schoolNow->format('H:i:s'),
            'grade' => $grade,
            'reason' => $reason,
            'admin_name' => $adminName,
        ];

        $logId = null;
        $logged = false;
        if ($previousStatus !== 'late') {
            try {
                $created = LateSlipLog::query()->create([
                    'school_year' => $slip['school_year'],
                    'semester' => $slip['semester'],
                    'student_name' => $slip['student_name'],
                    'slip_date' => $slip['slip_date'],
                    'time_in' => $slip['time_in'],
                    'grade' => $slip['grade'],
                    'reason' => $slip['reason'],
                    'admin_name' => $slip['admin_name'],
                    'printed_by' => $operatorId ?: null,
                    'printed_at' => utc_now(),
                ]);
                $logId = (int) ($created->id ?? 0);
                $logged = $logId > 0;
            } catch (\Throwable $e) {
                Log::error('Badge scan late slip log failed: ' . $e->getMessage());
            }
        }

        $displayDate = $schoolNow->format('m/d/Y');
        $displayTime = $schoolNow->format('h:i A');

        return [
            'required' => true,
            'logged' => $logged,
            'logId' => $logId ?: null,
            'logUrl' => url('/administrator/late_slips'),
            'schoolYear' => $slip['school_year'],
            'semester' => $slip['semester'],
            'studentName' => $studentName,
            'date' => $displayDate,
            'timeIn' => $displayTime,
            'grade' => $grade,
            'reason' => $reason,
            'adminName' => $adminName,
            'cutoffTime' => $cutoffLabel,
            'message' => 'Late slip receipt generated for arrival after ' . $cutoffLabel . '.',
            'text' => implode("\n", [
                'Al Rahma School at ISGL',
                'School Year: ' . $slip['school_year'],
                'Student Name: ' . $studentName,
                'Date: ' . $displayDate . '  Time In: ' . $displayTime,
                'Grade: ' . $grade,
                'Reason: ' . $reason,
                'Admin: ' . $adminName,
            ]),
        ];
    }

    private function isAfterLateCutoff(\DateTimeImmutable $schoolNow, \DateTimeImmutable $cutoff): bool
    {
        return $schoolNow >= $cutoff;
    }

    private function lateCutoffForDate(\DateTimeImmutable $schoolNow): \DateTimeImmutable
    {
        $configured = trim((string) (Configuration::getConfig(self::LATE_CUTOFF_CONFIG_KEY) ?: self::DEFAULT_LATE_CUTOFF));
        $timezone = $schoolNow->getTimezone();
        $date = $schoolNow->format('Y-m-d');

        foreach (['H:i:s', 'H:i', 'g:i A', 'h:i A', 'g:i a', 'h:i a'] as $format) {
            $parsed = \DateTimeImmutable::createFromFormat('!' . $format, $configured, $timezone);
            if ($parsed instanceof \DateTimeImmutable) {
                return $schoolNow->setTime((int) $parsed->format('H'), (int) $parsed->format('i'), (int) $parsed->format('s'));
            }
        }

        $timestamp = strtotime($date . ' ' . $configured);
        if ($timestamp !== false) {
            return (new \DateTimeImmutable('@' . $timestamp))->setTimezone($timezone);
        }

        Log::warning(self::LATE_CUTOFF_CONFIG_KEY . ' has invalid value "' . $configured . '"; using ' . self::DEFAULT_LATE_CUTOFF);
        return $schoolNow->setTime(10, 0, 0);
    }

    private function schoolLocalDateTime(string $scannedAt): \DateTimeImmutable
    {
        $timezone = new \DateTimeZone('America/New_York');
        try {
            $utc = new \DateTimeZone('UTC');
            return (new \DateTimeImmutable($scannedAt, $utc))->setTimezone($timezone);
        } catch (\Throwable) {
            return new \DateTimeImmutable('now', $timezone);
        }
    }

    private function operatorName(int $operatorId): string
    {
        if ($operatorId > 0) {
            try {
                $user = User::query()->select('firstname', 'lastname', 'email')->find($operatorId);
                if ($user) {
                    $name = trim((string) ($user->firstname ?? '') . ' ' . (string) ($user->lastname ?? ''));
                    if ($name !== '') {
                        return $name;
                    }

                    return (string) ($user->email ?? '');
                }
            } catch (\Throwable $e) {
                Log::error('Badge scan operator lookup failed: ' . $e->getMessage());
            }
        }

        return '';
    }

    private function currentTerm(array $row): array
    {
        $semester = (string) (Configuration::getConfig('semester') ?: ($row['semester'] ?? ''));
        $schoolYear = (string) (Configuration::getConfig('school_year') ?: ($row['school_year'] ?? ''));

        return [
            'semester' => $semester,
            'school_year' => $schoolYear,
        ];
    }

    private function normalizeScannedAt(string $value): string
    {
        if ($value !== '') {
            $timestamp = strtotime($value);
            if ($timestamp !== false) {
                return date('Y-m-d H:i:s', $timestamp);
            }
        }

        return utc_now();
    }

    private function logScan(string $badgeCode, ?string $personType, ?int $personId, string $action, string $status, int $operatorId, string $stationId, string $message, string $scannedAt): void
    {
        try {
            if (!Schema::hasTable('badge_scan_logs')) {
                return;
            }

            DB::table('badge_scan_logs')->insert([
                'badge_code' => $badgeCode,
                'person_type' => $personType,
                'person_id' => $personId,
                'scan_action' => $action,
                'scan_status' => $status,
                'operator_user_id' => $operatorId ?: null,
                'station_id' => $stationId ?: null,
                'message' => $message,
                'scanned_at' => $scannedAt,
                'created_at' => utc_now(),
            ]);
        } catch (\Throwable $e) {
            Log::error('Badge scan log failed: ' . $e->getMessage());
        }
    }

    private function successPayload(array $payload, string $message): array
    {
        return [
            'ok' => true,
            'status' => true,
            'success' => true,
            'message' => $message,
        ] + $payload;
    }

    private function failure(string $message): array
    {
        return [
            'ok' => false,
            'status' => false,
            'success' => false,
            'message' => $message,
        ];
    }
}
