File "ReportsController.php"

Full Path: /home/trinadezambia/public_html/admin_panel/app/Http/Controllers/ReportsController.php
File size: 120.26 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace App\Http\Controllers;

use App\Repositories\ClassSection\ClassSectionInterface;
use App\Repositories\FormField\FormFieldsInterface;
use App\Repositories\SessionYear\SessionYearInterface;
use App\Repositories\Student\StudentInterface;
use App\Repositories\User\UserInterface;
use App\Repositories\Fees\FeesInterface;
use App\Repositories\FeesPaid\FeesPaidInterface;
use App\Repositories\ExpenseCategory\ExpenseCategoryInterface;
use App\Repositories\Expense\ExpenseInterface;
use App\Repositories\Exam\ExamInterface;
use App\Repositories\Attendance\AttendanceInterface;
use App\Repositories\OnlineExam\OnlineExamInterface;
use App\Repositories\OnlineExamQuestion\OnlineExamQuestionInterface;
use App\Repositories\OnlineExamQuestionChoice\OnlineExamQuestionChoiceInterface;
use App\Repositories\OnlineExamQuestionOption\OnlineExamQuestionOptionInterface;
use App\Repositories\OnlineExamStudentAnswer\OnlineExamStudentAnswerInterface;
use App\Repositories\StudentOnlineExamStatus\StudentOnlineExamStatusInterface;
use App\Repositories\ClassSchool\ClassSchoolInterface;
use App\Repositories\Subject\SubjectInterface;
use App\Repositories\Medium\MediumInterface;
use App\Repositories\Grades\GradesInterface;
use App\Repositories\PromoteStudent\PromoteStudentInterface;
use App\Repositories\Timetable\TimetableInterface;
use App\Repositories\SchoolSetting\SchoolSettingInterface;
use App\Models\TransportationPayment;
use App\Models\RoutePickupPoint;
use App\Models\ClassSection;
use App\Models\Holiday;
use App\Models\StaffAttendance;
use App\Models\Leave;
use App\Models\LeaveMaster;
use App\Models\TransportationAttendance;
use App\Models\OnlineExamStudentAnswer;
use App\Models\StaffSalary;
use App\Services\FeaturesService;
use App\Services\ResponseService;
use App\Services\CachingService;
use Illuminate\Support\Facades\Auth;
use App\Repositories\Holiday\HolidayInterface;
use App\Services\BootstrapTableService;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Request;
use Carbon\Carbon;
use \App\Repositories\ExamResult\ExamResultInterface;
use App\Services\GeneralService;
use Illuminate\Support\Facades\DB;
use App\Models\Vehicle;
use Throwable;

class ReportsController extends Controller
{
    private ClassSectionInterface $classSection;
    private FormFieldsInterface $formFields;
    private SessionYearInterface $sessionYear;
    private StudentInterface $student;
    private FeesInterface $fees;
    private FeesPaidInterface $feesPaid;
    private UserInterface $user;
    private AttendanceInterface $attendance;
    private CachingService $cache;
    private HolidayInterface $holiday;
    private ExamResultInterface $examResult;
    private ExamInterface $exam;
    private OnlineExamInterface $onlineExam;
    private OnlineExamQuestionChoiceInterface $onlineExamQuestionChoice;
    private OnlineExamQuestionInterface $onlineExamQuestion;
    private OnlineExamQuestionOptionInterface $onlineExamQuestionOption;
    private OnlineExamStudentAnswerInterface $onlineExamStudentAnswer;
    private StudentOnlineExamStatusInterface $studentOnlineExamStatus;
    private ClassSchoolInterface $class;
    private SubjectInterface $subject;
    private MediumInterface $mediums;
    private GradesInterface $grade;
    private GeneralService $generalService;
    private StudentInterface $students;
    private PromoteStudentInterface $promoteStudent;
    private ExpenseCategoryInterface $expenseCategory;
    private ExpenseInterface $expense;
    private TimetableInterface $timetable;
    private SchoolSettingInterface $schoolSettings;

    public function __construct(
        ClassSectionInterface $classSection,
        FormFieldsInterface $formFields,
        SessionYearInterface $sessionYear,
        StudentInterface $student,
        FeesInterface $fees,
        FeesPaidInterface $feesPaid,
        UserInterface $user,
        AttendanceInterface $attendance,
        CachingService $cachingService,
        HolidayInterface $holiday,
        ExamResultInterface $examResult,
        ExamInterface $exam,
        OnlineExamInterface $onlineExam,
        OnlineExamQuestionChoiceInterface $onlineExamQuestionChoice,
        OnlineExamQuestionInterface $onlineExamQuestion,
        OnlineExamQuestionOptionInterface $onlineExamQuestionOption,
        OnlineExamStudentAnswerInterface $onlineExamStudentAnswer,
        StudentOnlineExamStatusInterface $studentOnlineExamStatus,
        ClassSchoolInterface $class,
        SubjectInterface $subject,
        MediumInterface $mediums,
        GradesInterface $grade,
        GeneralService $generalService,
        StudentInterface $students,
        PromoteStudentInterface $promoteStudent,
        ExpenseCategoryInterface $expenseCategory,
        ExpenseInterface $expense,
        TimetableInterface $timetable,
        SchoolSettingInterface $schoolSettings
    ) {
        $this->student = $student;
        $this->user = $user;
        $this->classSection = $classSection;
        $this->formFields = $formFields;
        $this->sessionYear = $sessionYear;
        $this->fees = $fees;
        $this->feesPaid = $feesPaid;
        $this->attendance = $attendance;
        $this->cache = $cachingService;
        $this->holiday = $holiday;
        $this->examResult = $examResult;
        $this->exam = $exam;
        $this->onlineExam = $onlineExam;
        $this->onlineExamQuestionChoice = $onlineExamQuestionChoice;
        $this->onlineExamQuestion = $onlineExamQuestion;
        $this->onlineExamQuestionOption = $onlineExamQuestionOption;
        $this->onlineExamStudentAnswer = $onlineExamStudentAnswer;
        $this->studentOnlineExamStatus = $studentOnlineExamStatus;
        $this->students = $students;
        $this->promoteStudent = $promoteStudent;

        $this->class = $class;
        $this->subject = $subject;
        $this->mediums = $mediums;
        $this->grade = $grade;
        $this->generalService = $generalService;
        $this->expenseCategory = $expenseCategory;
        $this->expense = $expense;
        $this->timetable = $timetable;
        $this->schoolSettings = $schoolSettings;
    }

    public function student_reports()
    {
        ResponseService::noPermissionThenRedirect('reports-student');
        // ResponseService::noFeatureThenRedirect('Reports Management');
        // ResponseService::noPermissionThenRedirect('student-list');
        $class_sections = $this->classSection->all(['*'], ['class', 'class.stream', 'class.shift', 'section', 'medium']);

        if (Auth::user()->school_id) {
            $extraFields = $this->formFields->defaultModel()->where('user_type', 1)->orderBy('rank')->get();
        } else {
            $extraFields = $this->formFields->defaultModel()->orderBy('rank')->get();
        }

        $features = FeaturesService::getFeatures();

        return view('reports.student.student-reports', compact('class_sections', 'extraFields', 'features'));
    }

    public function student_reports_show(Request $request)
    {
        ResponseService::noPermissionThenRedirect('reports-student');

        $offset = request('offset', 0);
        $limit = request('limit', 10);
        $sort = request('sort', 'id');
        $order = request('order', 'ASC');
        $search = request('search');
        $classId = request('class_id');
        $sessionYearID = $this->cache->getSessionYear()->id ?? null;

        // Get promoted students if both filters are applied
        $promotedMap = collect();
        if ($classId && $sessionYearID) {
            $promotedMap = $this->promoteStudent->builder()->where('class_section_id', $classId)
                ->where('session_year_id', $sessionYearID)
                ->get(['student_id', 'class_section_id', 'session_year_id'])
                ->keyBy('student_id');
        }

        // Main student query
        $sql = $this->student->builder()
            ->where(function ($query) {
                $query->where('application_type', 'offline')
                    ->orWhere(function ($q) {
                        $q->where('application_type', 'online')
                            ->where('application_status', 1);
                    });
            })
            ->with([
                'user.extra_student_details.form_field',
                'guardian',
                'class_section.class.stream',
                'class_section.class.shift',
                'class_section.section',
                'class_section.medium'
            ])
            ->where(function ($query) use ($search) {
                $query->when($search, function ($query) use ($search) {
                    $query->where(function ($query) use ($search) {
                        $query->where('user_id', 'LIKE', "%$search%")
                            ->orWhere('class_section_id', 'LIKE', "%$search%")
                            ->orWhere('admission_no', 'LIKE', "%$search%")
                            ->orWhere('roll_number', 'LIKE', "%$search%")
                            ->orWhere('admission_date', 'LIKE', date('Y-m-d', strtotime("%$search%")))
                            ->orWhereHas('user', function ($q) use ($search) {
                                $q->where('first_name', 'LIKE', "%$search%")
                                    ->orWhere('last_name', 'LIKE', "%$search%")
                                    ->orWhere('email', 'LIKE', "%$search%")
                                    ->orWhere('dob', 'LIKE', "%$search%")
                                    ->orWhereRaw("concat(first_name,' ',last_name) LIKE '%" . $search . "%'");
                            })->orWhereHas('guardian', function ($q) use ($search) {
                                $q->where('first_name', 'LIKE', "%$search%")
                                    ->orWhere('last_name', 'LIKE', "%$search%")
                                    ->orWhere('email', 'LIKE', "%$search%")
                                    ->orWhere('dob', 'LIKE', "%$search%")
                                    ->orWhereRaw("concat(first_name,' ',last_name) LIKE '%" . $search . "%'");
                            });
                    });
                });
            })
            ->when($classId, function ($query) use ($classId, $promotedMap) {
                $query->where(function ($q) use ($classId, $promotedMap) {
                    $q->where('class_section_id', $classId);
                    if ($promotedMap->isNotEmpty()) {
                        $q->orWhereIn('user_id', $promotedMap->keys());
                    }
                });
            })
            ->when($sessionYearID, function ($query) use ($sessionYearID, $promotedMap) {
                $query->where(function ($q) use ($sessionYearID, $promotedMap) {
                    $q->where('session_year_id', $sessionYearID);
                    if ($promotedMap->isNotEmpty()) {
                        $q->orWhereIn('user_id', $promotedMap->keys());
                    }
                });
            });

        $total = $sql->count();

        if (!empty($request->class_id)) {
            $sql = $sql->orderBy('roll_number', 'ASC');
        } else {
            $sql = $sql->orderBy($sort, $order);
        }
        if ($offset >= $total && $total > 0) {
            $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset
            $offset = $lastPage;
        }
        $sql->skip($offset)->take($limit);
        $res = $sql->get();

        $bulkData = [];
        $bulkData['total'] = $total;
        $rows = [];
        $no = 1;

        foreach ($res as $row) {
            $userId = $row->user_id;

            // Override class_section_id and session_year_id from promotion if available
            if ($promotedMap->has($userId)) {
                $promoted = $promotedMap->get($userId);
                $row->class_section_id = $promoted->class_section_id;
                $row->session_year_id = $promoted->session_year_id;

                $classSection = $this->classSection->builder()->with('class.stream', 'class.shift', 'section', 'medium')
                    ->find($promoted->class_section_id);

                // ⚠️ Replace loaded relation
                $row->setRelation('class_section', $classSection);
            }

            $operate = BootstrapTableService::viewButton(
                route('reports.student.student-view-reports', [$row->user->id]),
                [],
                [
                    'title' => trans('View Student Details'),
                    'target' => ''
                ]
            );

            $student_gender = $row->user->gender;
            $guardian_gender = $row->guardian->gender ?? '';

            $row->user->gender = trans(strtolower($row->user->gender));
            $row->guardian->gender = trans(strtolower($row->guardian->gender ?? ''));

            $tempRow = $row->toArray();
            $tempRow['no'] = $no++;
            $tempRow['eng_student_gender'] = $student_gender;
            $tempRow['eng_guardian_gender'] = $guardian_gender;
            $tempRow['extra_fields'] = $row->user->extra_student_details;

            foreach ($row->user->extra_student_details as $field) {
                $data = '';
                if ($field->form_field->type == 'checkbox') {
                    $data = json_decode($field->data);
                } elseif ($field->form_field->type == 'file') {
                    $data = '<a href="' . Storage::url($field->data) . '" target="_blank">DOC</a>';
                } elseif ($field->form_field->type == 'dropdown') {
                    $data = $field->data ?? '';
                } else {
                    $data = $field->data;
                }

                $tempRow[$field->form_field->name] = $data;
            }

            $tempRow['operate'] = $operate;
            $rows[] = $tempRow;
        }

        $bulkData['rows'] = $rows;
        return response()->json($bulkData);
    }



    public function student_view_reports($id)
    {
        ResponseService::noPermissionThenRedirect('reports-student');
        $schoolSettings = $this->cache->getSchoolSettings();
        $session_year_id = $this->cache->getSessionYear()->id ?? null;
        // Check if the student has a promotion for the requested session_year_id
        $promoted = \App\Models\PromoteStudent::where('student_id', $id)
            ->where('session_year_id', $session_year_id)
            ->first();

        if ($promoted) {
            // Load the promoted class_section with relations
            $classSection = $this->classSection->builder()->with('class.stream', 'class.shift', 'section', 'medium')
                ->find($promoted->class_section_id);

            // Load the promoted session year
            $promotedSessionYear = $this->sessionYear->builder()->select('id', 'name', 'start_date', 'end_date')
                ->find($promoted->session_year_id);

            // Load the student with basic relations (excluding original session_year and class_section)
            $student = $this->student->builder()->where('user_id', $id)
                ->with([
                    'user',
                    'guardian',
                ])
                ->first();

            // Override class_section_id and session_year_id
            $student->class_section_id = $promoted->class_section_id;
            $student->session_year_id = $promoted->session_year_id;

            // Override the relations
            $student->setRelation('class_section', $classSection);
            $student->setRelation('session_year', $promotedSessionYear);
        } else {
            // No promotion: load full relations normally
            $student = $this->student->builder()->where('user_id', $id)
                ->with([
                    'user',
                    'guardian',
                    'class_section.class.stream',
                    'class_section.class.shift',
                    'class_section.section',
                    'class_section.medium',
                    'session_year:id,name,start_date,end_date'
                ])
                ->first();
        }

        $transportation = null;

        $transportationPayments = TransportationPayment::with(['transportationFee', 'paymentTransaction', 'pickupPoint', 'routeVehicle', 'shift'])
            ->where('user_id', $id)
            ->where('status', 'paid')
            ->orderBy('id', 'desc')
            ->first();

        if ($transportationPayments) {
            $routePickupPoint = null;

            if ($transportationPayments->pickupPoint && $transportationPayments->routeVehicle?->route) {
                $routePickupPoint = RoutePickupPoint::where('pickup_point_id', $transportationPayments->pickupPoint->id)
                    ->where('route_id', $transportationPayments->routeVehicle->route->id)
                    ->first();
            }

            $transportation = [
                'plan' => [
                    'plan_status' => ($transportationPayments->expiry_date && $transportationPayments->expiry_date >= date('Y-m-d')) ? 'Active' : 'Expired',
                    'vehicle_assignment' => ($transportationPayments->routeVehicle) ? 'Assigned' : 'Pending',
                    'expiry_date' => $transportationPayments->expiry_date,
                    'date' => $transportationPayments->date,
                    'paid_amount' => $schoolSettings['currency_symbol'] . " " . $transportationPayments->amount,
                    'payment_mode' => $transportationPayments->paymentTransaction->payment_gateway ?? null,
                ],
                'shift' => [
                    'name' => $transportationPayments->shift->name ?? null,
                    'start_time' => Carbon::parse($transportationPayments->shift->start_time)->format($schoolSettings['time_format']) ?? null,
                    'end_time' => Carbon::parse($transportationPayments->shift->end_time)->format($schoolSettings['time_format']) ?? null,
                ],
                'fee' => [
                    'duration' => $transportationPayments->transportationFee->duration ?? null,
                    'amount' => $schoolSettings['currency_symbol'] . " " . $transportationPayments->transportationFee->fee_amount ?? null,
                ],
                'route' => [
                    'route_name' => $transportationPayments->routeVehicle->route->name ?? null,
                    'pickup_point_name' => $transportationPayments->pickupPoint->name ?? null,
                    'pickup_time' => $routePickupPoint?->pickup_time
                        ? Carbon::parse($routePickupPoint->pickup_time)->format($schoolSettings['time_format'])
                        : null,
                    'drop_time' => $routePickupPoint?->drop_time
                        ? Carbon::parse($routePickupPoint->drop_time)->format($schoolSettings['time_format'])
                        : null,
                ],
                'vehicle' => [
                    'name' => $transportationPayments->routeVehicle->vehicle->name ?? null,
                    'number' => $transportationPayments->routeVehicle->vehicle->vehicle_number ?? null,
                    'capacity' => $transportationPayments->routeVehicle->vehicle->capacity ?? null,
                    'driver' => $transportationPayments->routeVehicle->driver ?? null,
                    'helper' => $transportationPayments->routeVehicle->helper ?? null
                ],
            ];
        }

        // Get all fees for this student
        $studentFees = $this->getStudentFees($id, $session_year_id);

        // Get all session years for exam tab
        $sessionYears = $this->sessionYear->all();
        $sessionYear = $this->cache->getSessionYear();

        // Generate months for attendance tab based on session year range
        $attendanceMonths = [];
        if ($sessionYear) {
            try {
                $settings = $this->cache->getSchoolSettings();
                $start = Carbon::createFromFormat($settings['date_format'], $sessionYear->start_date)->startOfMonth();
                $end = Carbon::createFromFormat($settings['date_format'], $sessionYear->end_date)->startOfMonth();

                // Clone to avoid modifying the original carbon object in the loop
                $current = $start->copy();
                while ($current <= $end) {
                    $attendanceMonths[] = (object)[
                        'id' => $current->month,
                        'year' => $current->year,
                        'name' => $current->format('F Y')
                    ];
                    $current->addMonth();
                }
            } catch (\Exception $e) {
                // Fallback or ignore if date format issue
            }
        }

        // return $attendanceMonths;

        return view('reports.student.student-view-reports', compact('student', 'studentFees', 'sessionYears', 'sessionYear', 'session_year_id', 'transportation', 'attendanceMonths'));
    }



    public function getStudentFees($student_id, $session_year_id)
    {
        ResponseService::noPermissionThenRedirect('reports-student');
        // Check if promoted for the session year, else fallback to original
        $promoted = null;
        if ($session_year_id) {
            $promoted = $this->promoteStudent->builder()->where('student_id', $student_id)
                ->where('session_year_id', $session_year_id)
                ->first();
        }

        if ($promoted) {
            // Get promoted class_section
            $classSectionId = $promoted->class_section_id;
        } else {
            // Load student to get original class_section
            $student = $this->student->builder()
                ->where('user_id', $student_id)
                ->with('class_section:id,class_id')
                ->first();

            if (!$student) {
                return collect([]);
            }

            $classSectionId = $student->class_section_id;
        }

        // Get the class_id from class_section
        $classSection = $this->classSection->builder()->find($classSectionId);
        if (!$classSection) {
            return collect([]);
        }
        $classId = $classSection->class_id;

        // Now get all fees assigned to this class
        $allClassFees = $this->fees->builder()
            ->where('class_id', $classId)
            ->with([
                'fees_class_type.fees_type',
                'installments:id,name,due_date,due_charges,fees_id'
            ])
            ->get();

        // Get paid fees for student (session_year filter can be added if relevant)
        $paidFees = $this->feesPaid->builder()
            ->where('student_id', $student_id)
            ->with([
                'fees.fees_class_type.fees_type',
                'compulsory_fee.installment_fee:id,name',
                'optional_fee' => function ($q) {
                    $q->with([
                        'fees_class_type' => function ($q) {
                            $q->select('id', 'fees_type_id')->with('fees_type:id,name');
                        }
                    ]);
                }
            ])
            ->get();

        $paidFeesIds = $paidFees->pluck('fees_id')->toArray();

        $paidFees->transform(function ($paidFee) {
            $paidFee->status = $paidFee->is_fully_paid ? 'paid' : 'partial';
            return $paidFee;
        });

        $unpaidFees = $allClassFees->filter(function ($fee) use ($paidFeesIds) {
            return !in_array($fee->id, $paidFeesIds);
        })->map(function ($fee) use ($student_id) {
            $virtualRecord = new \stdClass();
            $virtualRecord->id = null;
            $virtualRecord->fees_id = $fee->id;
            $virtualRecord->student_id = $student_id;
            $virtualRecord->is_fully_paid = 0;
            $virtualRecord->is_used_installment = 0;
            $virtualRecord->amount = $fee->total_compulsory_fees;
            $virtualRecord->date = null;
            $virtualRecord->fees = $fee;
            $virtualRecord->compulsory_fee = [];
            $virtualRecord->optional_fee = [];
            $virtualRecord->status = 'unpaid';

            if (isset($fee->due_date)) {
                try {
                    $today = \Carbon\Carbon::now()->startOfDay();
                    $dueDate = \Carbon\Carbon::createFromFormat('d-m-Y', $fee->due_date)->startOfDay();
                    if ($dueDate->lt($today)) {
                        $virtualRecord->status = 'overdue';
                    }
                } catch (\Exception $e) {
                    // ignore parse errors, keep status as unpaid
                }
            }
            return $virtualRecord;
        });

        $allFees = $paidFees->concat($unpaidFees);

        return $allFees->sortBy(function ($fee) {
            return match ($fee->status) {
                'overdue' => 1,
                'unpaid' => 2,
                'partial' => 3,
                'paid' => 4,
                default => 5,
            };
        });
    }

    public function getStudentAttendanceReport(Request $request)
    {
        ResponseService::noPermissionThenRedirect('reports-student');
        // Validate request parameters
        $request->validate([
            'month' => 'required|numeric|between:1,12',
            'attendance_year' => 'required|numeric',
            'student_id' => 'required|exists:users,id',
            'session_year_id' => 'required|exists:session_years,id',
        ]);

        // Get session year from request
        $sessionYear = $this->sessionYear->findById($request->session_year_id);
        $settings = $this->cache->getSchoolSettings();

        // Get student information including class section
        $student = $this->student->builder()
            ->where('user_id', $request->student_id)
            ->with('class_section')
            ->first();

        if (!$student) {
            return response()->json([
                'success' => false,
                'message' => 'Student not found'
            ], 404);
        }

        $year = $request->attendance_year;
        $startDate = Carbon::createFromDate($year, $request->month, 1)->startOfMonth();
        $endDate = Carbon::createFromDate($year, $request->month, 1)->endOfMonth();

        // Get attendance records for this student in the specified month
        $attendanceRecords = $this->attendance->builder()
            ->where('student_id', $request->student_id)
            ->where('class_section_id', $student->class_section_id)
            ->where('session_year_id', $sessionYear->id)
            ->whereBetween('date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
            ->get();

        $holidayAttendance = [];

        $holidays = Holiday::whereDate('date', '>=', $startDate->format('Y-m-d'))
            ->whereDate('date', '<=', $endDate->format('Y-m-d'))
            ->get();

        foreach ($holidays as $holiday) {
            $holidayAttendance[] = [
                'date' => $holiday->getRawOriginal('date'),
                'title' => $holiday->title
            ];
        }

        $leaveMaster = $this->cache->getDefaultSessionYearLeaveMaster();
        $holiday_days = $leaveMaster && $leaveMaster->holiday ? explode(',', $leaveMaster->holiday) : [];
        if ($leaveMaster) {
            $period = Carbon::parse($startDate)->daysUntil(Carbon::parse($endDate)->addDay());
            foreach ($period as $day) {
                if (in_array($day->format('l'), $holiday_days)) {
                    $holidayAttendance[] = [
                        'date' => $day->format('Y-m-d'),
                        'title' => "Weekly Holiday"
                    ];
                }
            }
        }

        // Count present, absent and holiday days
        $presentCount = $attendanceRecords->where('type', 1)->count();
        $absentCount = $attendanceRecords->where('type', 0)->count();
        $holidayCount = $attendanceRecords->where('type', 3)->count();

        // Calculate attendance percentage
        $totalDays = $presentCount + $absentCount;
        $attendancePercentage = $totalDays > 0 ? round(($presentCount / $totalDays) * 100) : 0;

        // Prepare the response data
        $responseData = [
            'success' => true,
            'attendance' => $attendanceRecords,
            'holiday' => $holidayAttendance,
            'summary' => [
                'present_count' => $presentCount,
                'absent_count' => $absentCount,
                'holiday_count' => $holidayCount,
                'attendance_percentage' => $attendancePercentage,
                'total_days' => $totalDays
            ]
        ];

        return response()->json($responseData);
    }

    public function getStudentTransportationAttendanceReport(Request $request)
    {
        ResponseService::noPermissionThenRedirect('reports-student');

        $request->validate([
            'month' => 'required|numeric|between:1,12',
            'attendance_year' => 'required|numeric',
            'student_id' => 'required|exists:users,id',
            'session_year_id' => 'required|exists:session_years,id',
            'type' => 'nullable|in:pickup,drop'
        ]);

        $typeValue = $request->type === 'drop' ? 1 : 0;

        $sessionYear = $this->sessionYear->findById($request->session_year_id);
        $settings = $this->cache->getSchoolSettings();

        // Get student information
        $student = $this->student->builder()
            ->where('user_id', $request->student_id)
            ->first();

        if (!$student) {
            return response()->json([
                'success' => false,
                'message' => 'Student not found'
            ], 404);
        }

        $year = $request->attendance_year;
        $startDate = Carbon::createFromDate($year, $request->month, 1)->startOfMonth();
        $endDate = Carbon::createFromDate($year, $request->month, 1)->endOfMonth();

        // Get transportation attendance records
        $attendanceRecords = TransportationAttendance::with(['routeVehicle', 'pickupPoint', 'shift'])
            ->where('user_id', $request->student_id)
            ->where('pickup_drop', $typeValue)
            ->whereBetween('date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
            ->get();

        // Handle holiday attendance
        $holidayAttendance = array();

        // Use regular holidays
        $holidays = $this->holiday->builder()
            ->where('date', '>=', $startDate->format('Y-m-d'))
            ->where('date', '<=', $endDate->format('Y-m-d'))
            ->get();

        foreach ($holidays as $h) {
            $holidayAttendance[] = [
                'date' => Carbon::createFromFormat($settings['date_format'], $h->date)->format('Y-m-d'),
                'title' => $h->title,
            ];
        }

        // Handle leave master weekly holidays
        $leaveMaster = $this->cache->getDefaultSessionYearLeaveMaster();
        $holiday_days = $leaveMaster && $leaveMaster->holiday ? explode(',', $leaveMaster->holiday) : [];
        if ($leaveMaster) {
            $period = Carbon::parse($startDate)->daysUntil(Carbon::parse($endDate)->addDay());
            foreach ($period as $day) {
                if (in_array($day->format('l'), $holiday_days)) {
                    $holidayAttendance[] = [
                        'date' => $day->format('Y-m-d'),
                        'title' => "Weekly Holiday"
                    ];
                }
            }
        }

        $holidayAttendanceCollection = collect($holidayAttendance);

        // Count logic: 
        // We only consider dates up to MIN(today, endDate) as working days.
        $cutOffDate = Carbon::now()->endOfDay() < $endDate ? Carbon::now()->endOfDay() : $endDate;
        $workingDaysCount = 0;

        $periodCounter = Carbon::parse($startDate)->daysUntil($cutOffDate->copy());

        // foreach ($periodCounter as $day) {
        // $dayFormatted = $day->format('Y-m-d');
        // $isHoliday = $holidayAttendanceCollection->contains('date', $dayFormatted);
        // if (!$isHoliday) {
        //     $workingDaysCount++;
        // }
        // }

        $workingDaysCount = $attendanceRecords->count();

        // Present count: Total distinct dates with at least one record (Pickup or Drop)
        $presentDates = $attendanceRecords->pluck('date')->unique();

        // Remove holidays from present dates if we want to be strict, but usually present trumps holiday.
        // Let's count present days which are actually in the working days or just all present dates.
        $presentCount = $presentDates->count();
        $holidayCount = $holidayAttendanceCollection->unique('date')->count();

        // Absent count
        $absentCount = max(0, $workingDaysCount - $presentCount);

        $attendancePercentage = $workingDaysCount > 0 ? round(($presentCount / $workingDaysCount) * 100) : 0;

        // Process data for UI
        $processedAttendance = $attendanceRecords->groupBy('date')->map(function ($dayRecords, $date) {
            return [
                'date' => $date,
                'records' => $dayRecords->map(function ($r) {
                    return [
                        'type' => $r->pickup_drop == 0 ? 'Pickup' : 'Drop',
                        'time' => $r->created_at->format('H:i'), // Adjust as per need, using created_at for time
                        // 'status' => 'Present' // For now, if record exists, it's present
                        'status' => $r->status // For now, if record exists, it's present
                    ];
                })
            ];
        })->values();

        // The UI needs get_date_original mapped exactly like attendance, so we add type = 1 for present
        // $mappedAttendance = $presentDates->map(function ($date) {
        //     return [
        //         'get_date_original' => $date,
        //         'type' => 1 // Present
        //     ];
        // })->values();

        $mappedAttendance = [];
        $presentCount = 0;
        foreach ($attendanceRecords as $key => $row) {
            $mappedAttendance[] = [
                'get_date_original' => $row->getRawOriginal('date'),
                'type' => $row->status == 'present' ? 1 : 0
            ];

            if ($row->status == 'present') {
                $presentCount++;
            }
        }

        $absentCount = max(0, $workingDaysCount - $presentCount);
        $attendancePercentage = $workingDaysCount > 0 ? round(($presentCount / $workingDaysCount) * 100) : 0;


        $responseData = [
            'success' => true,
            'attendance' => $mappedAttendance, // Simple mapping for calendar UI
            'details' => $processedAttendance, // More details if needed
            'holiday' => $holidayAttendance,
            'summary' => [
                'present_count' => $presentCount,
                'absent_count' => $absentCount,
                'holiday_count' => $holidayCount,
                'attendance_percentage' => $attendancePercentage,
                'total_days' => $workingDaysCount
            ]
        ];

        return response()->json($responseData);
    }

    public function getStudentExamReport(Request $request)
    {
        ResponseService::noPermissionThenRedirect('reports-student');
        // Validate request parameters
        $request->validate([
            'student_id' => 'required|exists:users,id',
            'session_year_id' => 'nullable|exists:session_years,id'
        ]);

        // Get current session year if not provided
        $sessionYearId = $this->cache->getSessionYear()->id;

        // Get student information including class section
        $student = $this->student->builder()
            ->where('user_id', $request->student_id)
            ->with(['class_section.class', 'class_section.class.shift', 'class_section.section', 'user'])
            ->first();

        if (!$student) {
            return response()->json([
                'success' => false,
                'message' => 'Student not found'
            ], 404);
        }

        // Get offline exam results
        $offlineExamResults = $this->getOfflineExamResults($request->student_id, $sessionYearId);

        // Get online exam results
        $onlineExamResults = $this->getOnlineExamResults($request->student_id, $sessionYearId);

        // Get all available exams for this class section
        $availableExams = $this->exam->builder()
            ->with('timetable.class_subject', 'timetable.exam_marks.user.student')
            ->where('publish', 1)
            ->where('session_year_id', $sessionYearId)
            ->get();

        // Check if there are any exams available
        if ($availableExams->isEmpty() && empty($offlineExamResults) && empty($onlineExamResults)) {
            return response()->json([
                'success' => true,
                'message' => 'No exams available for this student',
                'student' => [
                    'id' => $student->user_id,
                    'name' => $student->user->full_name,
                    'admission_no' => $student->admission_no,
                    'roll_number' => $student->roll_number,
                    'class' => $student->class_section->class->name ?? 'N/A',
                    'section' => $student->class_section->section->name ?? 'N/A'
                ],
                'exams' => []
            ]);
        }

        // Process offline exam results
        $offlineExams = collect($offlineExamResults)->map(function ($result) use ($student) {
            // Make sure result is an object
            if (!is_object($result)) {
                return null;
            }

            try {
                // Get subjects for this exam
                $subjects = $this->getExamSubjectsWithMarks($result->exam_id, $student->user_id);

                // Get rank in class
                $classRank = $this->getStudentExamRank($result->exam_id, $student->class_section_id, $student->user_id);

                // Determine division based on percentage
                $division = null;
                if ($result->percentage >= 75) {
                    $division = 'First';
                } elseif ($result->percentage >= 60) {
                    $division = 'Second';
                } elseif ($result->percentage >= 33) {
                    $division = 'Third';
                }

                return [
                    'id' => $result->exam_id,
                    'name' => $result->exam->name,
                    'description' => $result->exam->description,
                    'published' => (bool) $result->exam->publish,
                    'subjects' => $subjects,
                    'exam_type' => 'Offline Exam',
                    'summary' => [
                        'max_marks' => $result->total_marks,
                        'obtained_marks' => $result->obtained_marks,
                        'percentage' => $result->percentage,
                        'result' => $result->status ? 'Pass' : 'Fail',
                        'division' => $division,
                        'rank' => $classRank,
                    ]
                ];
            } catch (\Exception $e) {
                \Log::error('Error processing exam result: ' . $e->getMessage());
                return null;
            }
        })->filter()->values(); // Remove any null values and reindex

        // Process online exam results
        $onlineExams = collect($onlineExamResults)->map(function ($result) {
            return [
                'id' => $result['id'] ?? null,
                'name' => $result['exam_title'] ?? 'Online Exam',
                'subject_name' => $result['subject_name'] ?? null,
                'total_marks' => $result['total_marks'] ?? 0,
                'exam_total_marks' => $result['exam_total_marks'] ?? 0,
                'total_obtained_marks' => $result['total_obtained_marks'] ?? 0,
                'percentage' => $result['percentage'] ?? 0,
                'status' => $result['status'] ?? 'Not Available',
                'exam_type' => 'Online Exam',
                'subject_type' => $result['subjects'][0]['type'] ?? null,
                'created_at' => $result['created_at'] ?? null,
                'grade' => $result['grade'] ?? null,
            ];
        })->values();

        // Add missing exams with not attempted status
        $attemptedExamIds = $offlineExams->pluck('id')->toArray();
        $missingExams = $availableExams->filter(function ($exam) use ($attemptedExamIds) {
            return !in_array($exam->id, $attemptedExamIds);
        })->map(function ($exam) use ($student) {
            // Get subjects for this exam even though it wasn't attempted
            $subjects = $this->getExamSubjectsWithMarks($exam->id, $student->user_id);

            if (!$subjects) {
                return [];
            }

            return [
                'id' => $exam->id,
                'name' => $exam->name,
                'description' => $exam->description,
                'published' => true,
                'subjects' => $subjects,
                'exam_type' => 'Offline Exam',
                'summary' => [
                    'max_marks' => $subjects->sum('max_marks') ?? 0,
                    'obtained_marks' => 0,
                    'percentage' => 0,
                    'result' => 'Not Attempted',
                    'division' => null,
                    'rank' => 'N/A',
                    'pdf_url' => null,
                ]
            ];
        })->values();

        // Combine all exam results
        $allExams = $offlineExams->concat($missingExams)->concat($onlineExams);

        // Prepare response
        $responseData = [
            'success' => true,
            'student' => [
                'id' => $student->user_id,
                'name' => $student->user->full_name,
                'admission_no' => $student->admission_no,
                'roll_number' => $student->roll_number,
                'class' => $student->class_section->class->name ?? 'N/A',
                'section' => $student->class_section->section->name ?? 'N/A',
                'photo' => $this->formatImageUrl($student->user->image)
            ],
            'exams' => $allExams->sortByDesc('id')->values()
        ];

        return response()->json($responseData);
    }

    // get online exam results
    private function getOnlineExamResults($studentId, $sessionYearId)
    {
        ResponseService::noPermissionThenRedirect('reports-student');

        $attempts = $this->studentOnlineExamStatus->builder()
            ->with([
                'online_exam' => function ($q) {
                    $q->with(['class_subject.subject']);
                },
                'student_data',
            ])
            ->where([
                'status' => 2,
                'student_id' => $studentId
            ])
            ->whereHas('online_exam', function ($q) use ($sessionYearId) {
                $q->where('session_year_id', $sessionYearId);
            })
            ->get();

        $rows = [];
        $grades = $this->grade->builder()->orderBy('starting_range', 'ASC')->get();

        foreach ($attempts as $student_attempt) {
            // Calculate Obtained Marks: Sum of question_marks where true_answer is 1
            $total_obtained_marks = OnlineExamStudentAnswer::where('student_id', $student_attempt->student_id)
                ->where('online_exam_id', $student_attempt->online_exam_id)
                ->where('true_answer', 1)
                ->sum('question_marks');

            // Get Total Marks for the exam
            $total_marks_data = $this->onlineExamQuestionChoice->builder()
                ->select(DB::raw("sum(marks)"))
                ->where('online_exam_id', $student_attempt->online_exam_id)
                ->first();
            $total_marks = $total_marks_data['sum(marks)'] ?? 0;

            // Calculate percentage
            $percentage = $total_marks > 0 ? ($total_obtained_marks / $total_marks) * 100 : 0;

            // Calculate grade
            $grade = $this->generalService->getGradeByPercentage($percentage, $grades);



            $rows[] = [
                'id' => $student_attempt->online_exam_id,
                'exam_title' => $student_attempt->online_exam->title,
                'subject_name' => $student_attempt->online_exam->class_subject->subject->name ?? '',
                'subject_code' => $student_attempt->online_exam->class_subject->subject->code ?? '',
                'subject_type' => $student_attempt->online_exam->class_subject->type ?? '',
                'total_marks' => $total_marks,
                'exam_total_marks' => $total_marks,
                'total_obtained_marks' => $total_obtained_marks,
                'percentage' => $percentage,
                'created_at' => $student_attempt->created_at, // This uses the accessor from the model which formats the date
                'grade' => $grade,
                'exam_type' => 'Online Exam'
            ];
        }

        // dd($rows);

        return $rows;
    }

    // get offline exam results
    private function getOfflineExamResults($studentId, $sessionYearId)
    {
        ResponseService::noPermissionThenRedirect('reports-student');
        // Get exam results for this student directly from exam_results table
        $examResults = $this->examResult->builder()
            ->with(['exam:id,name,description,publish', 'user:id,first_name,last_name,image'])
            ->where('student_id', $studentId)
            ->where('session_year_id', $sessionYearId)
            ->get();

        return $examResults;
    }
    /**
     * Get subjects with marks for a specific exam and student
     * 
     * @param int $examId
     * @param int $studentId
     * @return \Illuminate\Support\Collection
     */
    private function getExamSubjectsWithMarks($examId, $studentId)
    {
        try {
            // Get student information first to have access to class_section_id
            $student = $this->student->builder()
                ->where('user_id', $studentId)
                ->select('class_section_id')
                ->first();

            if (!$student) {
                return collect([]);
            }

            // First attempt: Get exam marks with subject information
            $examMarks = DB::table('exam_marks')
                ->join('exam_timetables', 'exam_timetables.id', '=', 'exam_marks.exam_timetable_id')
                ->join('class_subjects', 'class_subjects.id', '=', 'exam_timetables.class_subject_id')
                ->join('subjects', 'subjects.id', '=', 'class_subjects.subject_id')
                ->where('exam_timetables.exam_id', $examId)
                ->where('exam_marks.student_id', $studentId)
                ->select(
                    'subjects.id as subject_id',
                    'subjects.name as subject_name',
                    'subjects.type as subject_type',
                    'subjects.code as subject_code',
                    'exam_timetables.total_marks as max_marks',
                    'exam_timetables.passing_marks as min_marks',
                    'exam_marks.obtained_marks',
                    'exam_marks.grade',
                    'exam_marks.passing_status as is_pass'
                )
                ->get();

            // If no subjects are found, try another approach
            if ($examMarks->isEmpty()) {
                // Second attempt: Get subjects from exam timetables
                $examMarks = DB::table('exam_timetables')
                    ->join('class_subjects', 'class_subjects.id', '=', 'exam_timetables.class_subject_id')
                    ->join('subjects', 'subjects.id', '=', 'class_subjects.subject_id')
                    ->where('exam_timetables.exam_id', $examId)
                    ->where(function ($query) use ($student) {
                        // Either match by class_section_id or class_id
                        $query->where('class_subjects.class_section_id', $student->class_section_id)
                            ->orWhereExists(function ($subquery) use ($student) {
                                $subquery->select(DB::raw(1))
                                    ->from('class_sections')
                                    ->join('classes', 'classes.id', '=', 'class_sections.class_id')
                                    ->whereRaw('class_sections.id = ' . $student->class_section_id)
                                    ->whereRaw('classes.id = class_subjects.class_id');
                            });
                    })
                    ->select(
                        'subjects.id as subject_id',
                        'subjects.name as subject_name',
                        'subjects.type as subject_type',
                        'subjects.code as subject_code',
                        'exam_timetables.total_marks as max_marks',
                        'exam_timetables.passing_marks as min_marks',
                        DB::raw('0 as obtained_marks'),
                        DB::raw('NULL as grade'),
                        DB::raw('0 as is_pass')
                    )
                    ->get();
            }

            // If still no subjects, try a third approach with just subjects from the class
            if ($examMarks->isEmpty()) {
                // Third attempt: Get subjects from class_subjects directly
                $examMarks = DB::table('class_subjects')
                    ->join('subjects', 'subjects.id', '=', 'class_subjects.subject_id')
                    ->where('class_subjects.class_section_id', $student->class_section_id)
                    ->select(
                        'subjects.id as subject_id',
                        'subjects.name as subject_name',
                        'subjects.type as subject_type',
                        'subjects.code as subject_code',
                        DB::raw('100 as max_marks'), // Default value
                        DB::raw('33 as min_marks'),  // Default value
                        DB::raw('0 as obtained_marks'),
                        DB::raw('NULL as grade'),
                        DB::raw('0 as is_pass')
                    )
                    ->get();
            }

            // If still no subjects, create some dummy data based on the exam results
            if ($examMarks->isEmpty()) {
                // Get exam result to at least show summary
                $examResult = DB::table('exam_results')
                    ->where('exam_id', $examId)
                    ->where('student_id', $studentId)
                    ->first();

                if ($examResult) {
                    // Create a single dummy subject as placeholder
                    $examMarks = collect([
                        (object) [
                            'subject_id' => 0,
                            'subject_name' => 'Overall Result',
                            'subject_type' => 'General',
                            'subject_code' => 'ALL',
                            'max_marks' => $examResult->total_marks,
                            'min_marks' => $examResult->total_marks * 0.33, // Assuming 33% passing
                            'obtained_marks' => $examResult->obtained_marks,
                            'grade' => null,
                            'is_pass' => $examResult->percentage >= 33
                        ]
                    ]);
                }
            }

            if ($examMarks->isEmpty()) {
                return collect([]);
            }

            // Format and return the results
            return $examMarks->map(function ($mark) {
                return (object) [
                    'id' => $mark->subject_id,
                    'name' => $mark->subject_name,
                    'code' => $mark->subject_code ?? '',
                    'type' => $mark->subject_type,
                    'max_marks' => $mark->max_marks,
                    'min_marks' => $mark->min_marks,
                    'obtained_marks' => $mark->obtained_marks,
                    'grade' => $mark->grade,
                    'is_pass' => (bool) $mark->is_pass
                ];
            });
        } catch (\Exception $e) {
            // Log the error and return empty collection to prevent breaking the response
            \Log::error('Error fetching exam subject marks: ' . $e->getMessage());
            \Log::error('Stack trace: ' . $e->getTraceAsString());
            return collect([]);
        }
    }

    /**
     * Get student's rank in the class for a specific exam
     * 
     * @param int $examId
     * @param int $classSectionId
     * @param int $studentId
     * @return string Rank (e.g. "1", "2", "N/A")
     */
    private function getStudentExamRank($examId, $classSectionId, $studentId)
    {
        // Get all students' total marks for this exam in this class
        $results = $this->examResult->builder()
            ->select(
                'student_id',
                DB::raw('SUM(obtained_marks) as total_marks')
            )
            ->where('exam_id', $examId)
            ->where('class_section_id', $classSectionId)
            ->where('school_id', Auth::user()->school_id)
            ->groupBy('student_id')
            ->orderByDesc('total_marks')
            ->get();

        if ($results->isEmpty()) {
            return 'N/A';
        }

        // Find the student's position
        $studentIndex = $results->search(function ($result) use ($studentId) {
            return $result->student_id == $studentId;
        });

        if ($studentIndex === false) {
            return 'N/A';
        }

        // Return position (1-based index)
        return ($studentIndex + 1);
    }

    /**
     * Format image URL correctly
     * 
     * @param string|null $imagePath
     * @return string
     */
    private function formatImageUrl($imagePath)
    {
        if (empty($imagePath)) {
            return url('images/default-user.png');
        }

        // Check if the image already contains a URL
        if (filter_var($imagePath, FILTER_VALIDATE_URL)) {
            return $imagePath;
        }

        // Check if it's already prefixed with storage
        if (strpos($imagePath, 'storage/') === 0) {
            return url($imagePath);
        }

        // Standard path, prepend storage
        return url('storage/' . $imagePath);
    }

    /**
     * Get the division based on percentage
     * 
     * @param float $percentage
     * @return string|null
     */
    private function getDivisionByPercentage($percentage)
    {
        if ($percentage >= 75) {
            return 'First';
        } elseif ($percentage >= 60) {
            return 'Second';
        } elseif ($percentage >= 33) {
            return 'Third';
        }

        return null;
    }

    // get exam reports
    public function exam_reports()
    {
        ResponseService::noPermissionThenRedirect('reports-exam');
        $sessionYear = $this->cache->getSessionYear();

        $classes = $this->class->all(['*'], ['stream', 'medium', 'stream']);
        $subjects = $this->subject->builder()->orderBy('id', 'DESC')->get();
        $mediums = $this->mediums->builder()->pluck('name', 'id');

        $sessionYears = $this->sessionYear->all();

        $exams = $this->exam->builder()->where('publish', 1)->where('session_year_id', $sessionYear->id)->get();
        $classSections = $this->classSection->builder()->with('class.stream', 'class.shift', 'section', 'medium')->get();

        // total exam count
        $totalExamCount = $exams->count();

        return view('reports.exam.exam-reports', compact('classes', 'subjects', 'mediums', 'sessionYears', 'exams', 'classSections', 'totalExamCount'));
    }

    // get exam reports show
    public function exam_reports_show(Request $request)
    {
        // ResponseService::noFeatureThenRedirect('Reports Management');
        ResponseService::noPermissionThenRedirect('reports-exam');
        $class_sections = $this->classSection->all(['*'], ['class', 'class.stream', 'class.shift', 'section', 'medium']);
        return view('reports.exam.exam-reports-show', compact('class_sections'));
    }

    // get exam reports view
    public function exam_view_reports($id)
    {
        // ResponseService::noFeatureThenRedirect('Reports Management');
        ResponseService::noPermissionThenRedirect('reports-exam');
        return view('reports.exam.exam-view-reports', compact('id'));
    }

    // get yearly result show
    public function yearlyResultShow(Request $request)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenSendJson('exam-result');
        $offset = request('offset', 0);
        $limit = request('limit', 10);
        $sort = request('sort', 'id');
        $order = request('order', 'DESC');
        $search = request('search');

        $sessionYears = $this->cache->getSessionYear()->id;
        $class_section_id = request('class_section_id');

        $sql = $this->students->builder()
            ->has('exam_result')
            ->with([
                'user:id,first_name,last_name,school_id',
                'user.exam_marks' => function ($q) {
                    $q->with(['timetable', 'subject']);
                }
            ])
            ->whereHas('exam_result.exam', function ($q) {
                $q->where('publish', 1);
            })
            ->whereHas('exam_result', function ($q) use ($sessionYears) {
                $q->where('session_year_id', $sessionYears);
            })
            ->when($search, function ($q) use ($search) {
                $q->where(function ($q) use ($search) {
                    $q->whereHas('user', function ($q) use ($search) {
                        $q->whereRaw("concat(first_name,' ',last_name) LIKE '%" . $search . "%'");
                    });
                });
            })
            ->withSum('exam_result', 'obtained_marks')
            ->withSum('exam_result', 'total_marks');


        if ($class_section_id) {
            $sql = $sql->whereHas('exam_result', function ($q) use ($class_section_id) {
                $q->where('class_section_id', $class_section_id);
            });
        }

        $total = $sql->count();
        if ($offset >= $total && $total > 0) {
            $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset
            $offset = $lastPage;
        }
        $sql->orderBy($sort, $order)->skip($offset)->take($limit);
        $res = $sql->get();

        $bulkData = array();
        $bulkData['total'] = $total;
        $rows = array();
        $no = 1;
        $grades = $this->grade->builder()->orderBy('starting_range', 'ASC')->get();
        foreach ($res as $row) {
            // Calculate total marks across all exams
            $totalMarks = $row->exam_result_sum_total_marks;
            $obtainedMarks = $row->exam_result_sum_obtained_marks;

            // Calculate percentage
            $percentage = $totalMarks > 0 ? ($obtainedMarks / $totalMarks) * 100 : 0;

            // Calculate grade
            $grade = '';
            $grade = $this->generalService->getGradeByPercentage($percentage, $grades);

            $operate = '';
            if (Auth::user()->can('exam-result-edit')) {
                $operate .= BootstrapTableService::button(
                    'fa fa-file-pdf-o',
                    url('reports/exam/rank-wise-result/' . $row->user_id . '/' . $sessionYears),
                    ['btn-gradient-info', 'btn-xs', 'btn-rounded', 'btn-icon'],
                    ['title' => __('view_result'), 'target' => '_blank']
                );
            }

            $tempRow = $row->toArray();
            $tempRow['no'] = $no++;
            $tempRow['total_marks'] = number_format($totalMarks, 2);
            $tempRow['obtained_marks'] = number_format($obtainedMarks, 2);
            $tempRow['percentage'] = number_format($percentage, 2);
            $tempRow['grade'] = $grade;
            $tempRow['operate'] = $operate;
            $rows[] = $tempRow;
        }

        $bulkData['rows'] = $rows;
        return response()->json($bulkData);
    }

    public function yearlyExamResultPdf($student_id)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenRedirect('exam-result');
        try {
            // get school settings
            $settings = $this->cache->getSchoolSettings();

            $schoolName = $settings['school_name'];
            $schoolLogo = $settings['horizontal_logo'];

            // get exams
            $exams = $this->exam->builder()
                ->with([
                    'timetable' => function ($q) {
                        $q->with('exam_marks');
                    }
                ])
                ->where('publish', 1)
                ->get();

            $results = $this->examResult->builder()->with([
                'exam',
                'session_year',
                'user' => function ($q) use ($student_id) {
                    $q->with([
                        'student' => function ($q) {
                            $q->with([
                                'guardian',
                                'class_section.class.stream',
                                'class_section.class.shift',
                                'class_section.section',
                                'class_section.medium'
                            ]);
                        },
                        'exam_marks' => function ($q) use ($student_id) {
                            $q->whereHas('timetable', function ($q) use ($student_id) {
                                $q->where('student_id', $student_id);
                            })
                                ->with([
                                    'class_subject' => function ($q) {
                                        $q->withTrashed()->with('subject:id,name,type');
                                    },
                                    'timetable'
                                ]);
                        }
                    ]);
                }
            ])
                ->where('student_id', $student_id)
                ->select('exam_results.*')
                ->get();

            // Convert the results to a collection
            $results = collect($results);

            // Add rank calculation to each item in the collection
            $results = $results->map(function ($result) {
                $rank = DB::table('exam_results as er2')
                    ->where('er2.class_section_id', $result->class_section_id)
                    ->where('er2.obtained_marks', '>', $result->obtained_marks)
                    ->where('er2.exam_id', $result->exam_id)
                    ->where('er2.status', 1)
                    ->distinct('er2.obtained_marks')
                    ->count() + 1;

                $result->rank = $rank;
                return $result;
            });

            // Filter the collection based on student ID
            $result = $results->where('student_id', $student_id)->first();



            // $result->rank = $rank;

            // ====================================================================
            if (!$result) {
                return redirect()->back()->with('error', trans('no_records_found'));
            }

            $grades = $this->grade->builder()->orderBy('starting_range', 'ASC')->get();

            // get student attendance count
            $studentAttendanceCount = $this->attendance->builder()->where('student_id', $student_id)->where('type', 1)->where('session_year_id', $result->session_year_id)->count();

            $attendanceTotal = $this->attendance->builder()->where('student_id', $student_id)->where('type', 1)->where('session_year_id', $result->session_year_id)->count();


            return view('reports.exam.yearly-exam-result-pdf', compact('settings', 'result', 'grades', 'exams', 'studentAttendanceCount', 'attendanceTotal'));
        } catch (Throwable $e) {
            ResponseService::logErrorResponse($e);
            ResponseService::errorResponse();
        }
    }

    public function yearlyResultStatistics(Request $request)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenSendJson('exam-result');
        $sessionYearId = $this->cache->getSessionYear()->id;
        $classSectionId = $request->get('class_section_id');

        try {
            // Base query for exam results
            $query = $this->examResult->builder()
                ->where('session_year_id', $sessionYearId);

            // Filter by class section if provided
            if ($classSectionId) {
                $query->where('class_section_id', $classSectionId);
            }

            // Get all results for calculations
            $results = $query->get();

            // Calculate statistics
            $totalStudents = $results->count();
            $totalPass = $results->where('status', 1)->count();
            $totalFail = $results->where('status', 0)->count();
            $passPercentage = $totalStudents > 0 ? round(($totalPass / $totalStudents) * 100, 2) : 0;

            return response()->json([
                'success' => true,
                'total_students' => $totalStudents,
                'total_pass' => $totalPass,
                'total_fail' => $totalFail,
                'pass_percentage' => $passPercentage
            ]);
        } catch (\Exception $e) {
            \Log::error('Error fetching yearly result statistics: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Error fetching statistics',
                'total_students' => 0,
                'total_pass' => 0,
                'total_fail' => 0,
                'pass_percentage' => 0
            ]);
        }
    }

    public function subjectWiseResultShow(Request $request)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenSendJson('exam-result');
        $offset = request('offset', 0);
        $limit = request('limit', 10);
        $sort = request('sort', 'id');
        $order = request('order', 'DESC');
        $search = request('search');

        $sessionYearId = $this->cache->getSessionYear()->id;
        $classSectionId = request('class_section_id');
        $examId = request('exam_id');

        if (!$sessionYearId) {
            return response()->json([
                'total' => 0,
                'rows' => []
            ]);
        }

        // 2. Subquery: total obtained marks per student, with class_id and class_section_id
        $marksSub = DB::table('students')
            ->join('exam_results', function ($join) use ($sessionYearId, $examId) {
                $join->on('students.user_id', '=', 'exam_results.student_id')
                    ->where('exam_results.session_year_id', $sessionYearId);
                if ($examId) {
                    $join->where('exam_results.exam_id', $examId);
                }
            })
            ->join('class_sections', 'students.class_section_id', '=', 'class_sections.id')
            ->select(
                'students.user_id',
                'students.class_section_id',
                'class_sections.class_id',
                DB::raw('SUM(exam_results.obtained_marks) as total_obtained_marks')
            )
            ->groupBy('students.user_id', 'students.class_section_id', 'class_sections.class_id');

        // Fetch and sort the data for ranking
        $rankedData = DB::table(DB::raw("({$marksSub->toSql()}) as ranked"))
            ->mergeBindings($marksSub)
            ->orderBy('class_id')
            ->orderByDesc('total_obtained_marks')
            ->get();

        // Calculate ranks in PHP
        $classRanks = [];
        $sectionRanks = [];
        $lastClassMarks = [];
        $lastSectionMarks = [];
        $classRankValue = [];
        $sectionRankValue = [];

        foreach ($rankedData as $row) {
            // Class rank
            if (!isset($classRanks[$row->class_id])) {
                $classRanks[$row->class_id] = 1;
                $lastClassMarks[$row->class_id] = $row->total_obtained_marks;
                $classRankValue[$row->class_id] = 1;
            } else {
                if ($lastClassMarks[$row->class_id] != $row->total_obtained_marks) {
                    $classRankValue[$row->class_id]++;
                }
                $lastClassMarks[$row->class_id] = $row->total_obtained_marks;
            }
            $row->class_rank = $classRankValue[$row->class_id];

            // Section rank
            if (!isset($sectionRanks[$row->class_section_id])) {
                $sectionRanks[$row->class_section_id] = 1;
                $lastSectionMarks[$row->class_section_id] = $row->total_obtained_marks;
                $sectionRankValue[$row->class_section_id] = 1;
            } else {
                if ($lastSectionMarks[$row->class_section_id] != $row->total_obtained_marks) {
                    $sectionRankValue[$row->class_section_id]++;
                }
                $lastSectionMarks[$row->class_section_id] = $row->total_obtained_marks;
            }
            $row->section_rank = $sectionRankValue[$row->class_section_id];
        }

        // Now $rankedData contains class_rank and section_rank for each user_id
        $ranks = collect($rankedData)->keyBy('user_id');

        // 5. Main query: fetch students with marks (withSum as before)
        $sql = $this->students->builder()
            ->whereHas('exam_result.exam', function ($q) {
                $q->where('publish', 1);
            })
            ->with([
                'user:id,first_name,last_name,school_id',
                'user.exam_marks' => function ($q) {
                    $q->with([
                        'timetable.class_subject.subject:id,name,type,code',
                        'class_subject.subject:id,name,type,code'
                    ]);
                }
            ])
            ->whereHas('exam_result', function ($q) use ($sessionYearId) {
                $q->where('session_year_id', $sessionYearId);
            })
            ->when($classSectionId, function ($q) use ($classSectionId) {
                $q->whereHas('exam_result', function ($q) use ($classSectionId) {
                    $q->where('class_section_id', $classSectionId);
                });
            })
            ->when($examId, function ($q) use ($examId) {
                $q->whereHas('exam_result', function ($q) use ($examId) {
                    $q->where('exam_id', $examId);
                });
            })
            ->when($search, function ($q) use ($search) {
                $q->where(function ($q) use ($search) {
                    $q->whereHas('user', function ($q) use ($search) {
                        $q->whereRaw("concat(first_name,' ',last_name) LIKE '%" . $search . "%'");
                    });
                });
            });

        if ($examId) {
            $sql = $sql->withSum([
                'exam_result' => function ($q) use ($examId) {
                    $q->where('exam_id', $examId);
                }
            ], 'obtained_marks')
                ->withSum([
                    'exam_result' => function ($q) use ($examId) {
                        $q->where('exam_id', $examId);
                    }
                ], 'total_marks');
        } else {
            $sql = $sql->withSum('exam_result', 'obtained_marks')
                ->withSum('exam_result', 'total_marks');
        }

        $total = $sql->count();
        if ($offset >= $total && $total > 0) {
            $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset
            $offset = $lastPage;
        }
        $sql->orderBy($sort, $order)->skip($offset)->take($limit);
        $res = $sql->get();

        $bulkData = array();
        $bulkData['total'] = $total;
        $rows = array();
        $no = 1;
        $grades = $this->grade->builder()->orderBy('starting_range', 'ASC')->get();

        foreach ($res as $row) {
            $totalMarks = $row->exam_result_sum_total_marks;
            $obtainedMarks = $row->exam_result_sum_obtained_marks;
            $percentage = $totalMarks > 0 ? ($obtainedMarks / $totalMarks) * 100 : 0;
            $grade = $this->generalService->getGradeByPercentage($percentage, $grades);

            // Lookup ranks
            $classRank = $ranks[$row->user_id]->class_rank ?? null;
            $sectionRank = $ranks[$row->user_id]->section_rank ?? null;

            $operate = '';
            if (Auth::user()->can('exam-result-edit')) {
                $operate .= BootstrapTableService::button(
                    'fa fa-file-pdf-o',
                    url('reports/exam/rank-wise-result/' . $row->user_id . '/' . $sessionYearId),
                    ['btn-gradient-info', 'btn-xs', 'btn-rounded', 'btn-icon'],
                    ['title' => __('view_result'), 'target' => '_blank']
                );
            }

            $tempRow = $row->toArray();
            $tempRow['no'] = $no++;
            $tempRow['total_marks'] = number_format($totalMarks, 2);
            $tempRow['obtained_marks'] = number_format($obtainedMarks, 2);
            $tempRow['percentage'] = number_format($percentage, 2);
            $tempRow['grade'] = $grade;
            $tempRow['class_rank'] = $classRank;
            $tempRow['section_rank'] = $sectionRank;
            $tempRow['operate'] = $operate;
            $rows[] = $tempRow;
        }

        $bulkData['rows'] = $rows;
        return response()->json($bulkData);
    }

    public function subjectWiseResultStatistics(Request $request)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenSendJson('exam-result');
        $sessionYearId = $this->cache->getSessionYear()->id;
        $classSectionId = $request->get('class_section_id');
        $subjectId = $request->get('subject_id');

        try {
            // Base query to get all subjects for the class section
            $query = DB::table('class_subjects')
                ->join('subjects', 'subjects.id', '=', 'class_subjects.subject_id')
                ->join('exam_timetables', 'exam_timetables.class_subject_id', '=', 'class_subjects.id')
                ->join('exam_marks', 'exam_marks.exam_timetable_id', '=', 'exam_timetables.id')
                ->join('exam_results', 'exam_results.student_id', '=', 'exam_marks.student_id')
                ->where('exam_results.session_year_id', $sessionYearId);

            // Apply class section filter if provided
            if ($classSectionId) {
                $query->where('class_subjects.class_section_id', $classSectionId);
            }

            // Apply subject filter if provided
            if ($subjectId) {
                $query->where('subjects.id', $subjectId);
            }

            // Get total unique subjects
            $totalSubjects = $query->distinct('subjects.id')->count('subjects.id');

            if ($totalSubjects == 0) {
                return response()->json([
                    'success' => true,
                    'total_subjects' => 0,
                    'subjects_passed' => 0,
                    'subjects_failed' => 0,
                    'pass_percentage' => 0
                ]);
            }

            // Get subject results with passing criteria
            $subjectResults = $query->select(
                'subjects.id as subject_id',
                'subjects.name as subject_name',
                'exam_marks.obtained_marks',
                'exam_timetables.passing_marks',
                'exam_timetables.total_marks'
            )->get();

            // Group results by subject
            $subjectStats = collect($subjectResults)->groupBy('subject_id')->map(function ($attempts) {
                $totalAttempts = $attempts->count();
                $passedAttempts = $attempts->filter(function ($attempt) {
                    return $attempt->obtained_marks >= $attempt->passing_marks;
                })->count();

                return [
                    'total_attempts' => $totalAttempts,
                    'passed_attempts' => $passedAttempts,
                    'is_pass' => ($passedAttempts / $totalAttempts) >= 0.33 // Consider subject passed if 33% or more students passed
                ];
            });

            // Calculate overall statistics
            $subjectsPassed = $subjectStats->filter(function ($stat) {
                return $stat['is_pass'];
            })->count();

            $subjectsFailed = $totalSubjects - $subjectsPassed;

            // Calculate pass percentage - ensure it cannot exceed 100%
            $passPercentage = min(100, ($totalSubjects > 0) ? round(($subjectsPassed / $totalSubjects) * 100, 2) : 0);

            return response()->json([
                'success' => true,
                'total_subjects' => $totalSubjects,
                'subjects_passed' => $subjectsPassed,
                'subjects_failed' => $subjectsFailed,
                'pass_percentage' => $passPercentage
            ]);
        } catch (\Exception $e) {
            \Log::error('Error fetching subject wise result statistics: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Error fetching subject wise result statistics',
                'total_subjects' => 0,
                'subjects_passed' => 0,
                'subjects_failed' => 0,
                'pass_percentage' => 0
            ]);
        }
    }

    public function subjectWiseResultPdf($student_id)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenRedirect('exam-result');
        try {
            // get school settings
            $settings = $this->cache->getSchoolSettings();

            $schoolName = $settings['school_name'];
            $schoolLogo = $settings['horizontal_logo'];

            // get exams
            $exams = $this->exam->builder()
                ->with([
                    'timetable' => function ($q) {
                        $q->with('exam_marks');
                    }
                ])
                ->where('publish', 1)
                ->get();

            $results = $this->examResult->builder()->with([
                'exam',
                'session_year',
                'user' => function ($q) use ($student_id) {
                    $q->with([
                        'student' => function ($q) {
                            $q->with([
                                'guardian',
                                'class_section.class.stream',
                                'class_section.class.shift',
                                'class_section.section',
                                'class_section.medium'
                            ]);
                        },
                        'exam_marks' => function ($q) use ($student_id) {
                            $q->whereHas('timetable', function ($q) use ($student_id) {
                                $q->where('student_id', $student_id);
                            })
                                ->with([
                                    'class_subject' => function ($q) {
                                        $q->withTrashed()->with('subject:id,name,type');
                                    },
                                    'timetable'
                                ]);
                        }
                    ]);
                }
            ])
                ->where('student_id', $student_id)
                ->select('exam_results.*')
                ->get();

            // Convert the results to a collection
            $results = collect($results);

            // Add rank calculation to each item in the collection
            $results = $results->map(function ($result) {
                $rank = DB::table('exam_results as er2')
                    ->where('er2.class_section_id', $result->class_section_id)
                    ->where('er2.obtained_marks', '>', $result->obtained_marks)
                    ->where('er2.exam_id', $result->exam_id)
                    ->where('er2.status', 1)
                    ->distinct('er2.obtained_marks')
                    ->count() + 1;

                $result->rank = $rank;
                return $result;
            });

            // Filter the collection based on student ID
            $result = $results->where('student_id', $student_id)->first();

            // ====================================================================
            if (!$result) {
                return redirect()->back()->with('error', trans('no_records_found'));
            }

            $grades = $this->grade->builder()->orderBy('starting_range', 'ASC')->get();

            // get student attendance count
            $studentAttendanceCount = $this->attendance->builder()->where('student_id', $student_id)->where('type', 1)->where('session_year_id', $result->session_year_id)->count();

            $attendanceTotal = $this->attendance->builder()->where('student_id', $student_id)->where('type', 1)->where('session_year_id', $result->session_year_id)->count();

            // dd($exams->toArray());
            return view('reports.exam.subject-wise-exam-result-pdf', compact('settings', 'result', 'grades', 'exams', 'studentAttendanceCount', 'attendanceTotal'));
        } catch (\Exception $e) {
            return redirect()->back()->with('error', $e->getMessage());
        }
    }

    public function rankWiseResultShow(Request $request)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenSendJson('exam-result');
        $offset = request('offset', 0);
        $limit = request('limit', 10);
        $sort = request('sort', 'percentage');
        $order = request('order', 'DESC');
        $search = request('search');

        $sessionYearId = $this->cache->getSessionYear()->id;
        $classSectionId = request('class_section_id');
        $subjectId = request('subject_id');

        if (!$sessionYearId) {
            return response()->json([
                'total' => 0,
                'rows' => []
            ]);
        }

        // 1. Build the base query for all students (with rank)
        $baseQuery = $this->examResult->builder()
            ->whereHas('exam', function ($q) {
                $q->where('publish', 1);
            })
            ->where('session_year_id', $sessionYearId)
            ->when($classSectionId, function ($q) use ($classSectionId) {
                $q->where('class_section_id', $classSectionId);
            })
            ->when($subjectId, function ($q) use ($subjectId) {
                $q->whereHas('user.exam_marks', function ($q) use ($subjectId) {
                    $q->whereHas('class_subject', function ($q) use ($subjectId) {
                        $q->where('subject_id', $subjectId);
                    });
                });
            })
            ->select([
                'exam_results.*',
                DB::raw("RANK() OVER (ORDER BY obtained_marks DESC) AS rank")
            ])->with('user');

        // 2. Use the base query as a subquery
        $rankedSub = DB::table(DB::raw("({$baseQuery->toSql()}) as ranked"))
            ->mergeBindings($baseQuery->getQuery());

        // 3. Apply search and pagination on the outer query
        if ($search) {
            $rankedSub->where(function ($q) use ($search) {
                $q->where('id', 'LIKE', "%$search%")
                    ->orWhere('total_marks', 'LIKE', "%$search%")
                    ->orWhere('grade', 'LIKE', "%$search%")
                    ->orWhere('obtained_marks', 'LIKE', "%$search%")
                    ->orWhere('percentage', 'LIKE', "%$search%")
                    ->orWhere('rank', 'LIKE', "%$search%")
                    ->orWhereExists(function ($sub) use ($search) {
                        $sub->select(DB::raw(1))
                            ->from('users')
                            ->whereRaw('users.id = ranked.student_id')
                            ->where(function ($q) use ($search) {
                                $q->where('users.first_name', 'LIKE', "%$search%")
                                    ->orWhere('users.last_name', 'LIKE', "%$search%");
                            });
                    });
            });
        }

        $total = $rankedSub->count();
        if ($offset >= $total && $total > 0) {
            $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset
            $offset = $lastPage;
        }
        $results = $rankedSub
            ->orderBy($sort, $order)
            ->skip($offset)
            ->take($limit)
            ->get();

        // Hydrate Eloquent models for relationships
        $examResultIds = $results->pluck('id')->toArray();
        $examResults = $this->examResult->builder()
            ->with([
                'user:id,first_name,last_name,school_id',
                'user.exam_marks' => function ($q) use ($subjectId) {
                    $q->with([
                        'timetable.class_subject.subject:id,name,type,code',
                        'class_subject.subject:id,name,type,code'
                    ]);
                    if ($subjectId) {
                        $q->whereHas('class_subject', function ($q) use ($subjectId) {
                            $q->where('subject_id', $subjectId);
                        });
                    }
                }
            ])
            ->whereIn('id', $examResultIds)
            ->get()
            ->keyBy('id');

        $bulkData = array();
        $bulkData['total'] = $total;
        $rows = array();
        $no = 1;
        $grades = $this->grade->builder()->orderBy('starting_range', 'ASC')->get();

        foreach ($results as $row) {
            $eloRow = $examResults[$row->id] ?? null;
            if (!$eloRow)
                continue;
            // Calculate marks and percentage based on subject filter
            if ($subjectId) {
                $examMarks = collect($eloRow->user->exam_marks)->filter(function ($mark) use ($subjectId) {
                    return $mark->class_subject->subject_id == $subjectId;
                });
                $totalMarks = $examMarks->sum(function ($mark) {
                    return $mark->timetable->total_marks;
                });
                $obtainedMarks = $examMarks->sum('obtained_marks');
                $percentage = $totalMarks > 0 ? ($obtainedMarks / $totalMarks) * 100 : 0;
            } else {
                $totalMarks = $eloRow->total_marks;
                $obtainedMarks = $eloRow->obtained_marks;
                $percentage = $eloRow->percentage;
            }
            // Calculate grade
            $grade = $this->generalService->getGradeByPercentage($percentage, $grades);
            // Add PDF view button if user has permission
            $operate = '';
            if (Auth::user()->can('exam-result-edit')) {
                $operate .= BootstrapTableService::button(
                    'fa fa-file-pdf-o',
                    url('reports/exam/rank-wise-result/' . $eloRow->student_id . '/' . $sessionYearId),
                    ['btn-gradient-info', 'btn-xs', 'btn-rounded', 'btn-icon'],
                    ['title' => __('view_result'), 'target' => '_blank']
                );
            }
            $tempRow = $eloRow->toArray();
            $tempRow['no'] = $no++;
            $tempRow['total_marks'] = number_format($totalMarks, 2);
            $tempRow['obtained_marks'] = number_format($obtainedMarks, 2);
            $tempRow['percentage'] = number_format($percentage, 2);
            $tempRow['grade'] = $grade;
            $tempRow['rank'] = $row->rank ?? '-';
            $tempRow['operate'] = $operate;
            $rows[] = $tempRow;
        }

        $bulkData['rows'] = $rows;
        return response()->json($bulkData);
    }

    public function rankWiseResultStatistics(Request $request)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenSendJson('exam-result');
        $sessionYearId = $this->cache->getSessionYear()->id;
        $classSectionId = $request->get('class_section_id');
        $subjectId = $request->get('subject_id');

        try {
            // Base query for exam marks
            $query = DB::table('exam_marks')
                ->join('exam_timetables', 'exam_timetables.id', '=', 'exam_marks.exam_timetable_id')
                ->join('class_subjects', 'class_subjects.id', '=', 'exam_timetables.class_subject_id')
                ->join('exam_results', 'exam_results.student_id', '=', 'exam_marks.student_id')
                ->where('exam_results.session_year_id', $sessionYearId);

            // Apply class section filter if provided
            if ($classSectionId) {
                $query->where('exam_results.class_section_id', $classSectionId);
            }

            // Apply subject filter if provided
            if ($subjectId) {
                $query->where('class_subjects.subject_id', $subjectId);
            }

            // Get total unique students
            $totalStudents = $query->distinct('exam_marks.student_id')->count('exam_marks.student_id');

            if ($totalStudents == 0) {
                return response()->json([
                    'success' => true,
                    'total_students' => 0,
                    'total_pass' => 0,
                    'total_fail' => 0,
                    'pass_percentage' => 0
                ]);
            }

            // Get passing marks and total marks from timetable
            $results = $query->select(
                'exam_marks.student_id',
                'exam_marks.obtained_marks',
                'exam_timetables.passing_marks',
                'exam_timetables.total_marks'
            )->get();

            // Group results by student for subject-specific calculations
            $studentResults = collect($results)->groupBy('student_id')->map(function ($marks) {
                $totalMarks = $marks->sum('total_marks');
                $obtainedMarks = $marks->sum('obtained_marks');
                $passingMarks = $marks->sum('passing_marks');

                return [
                    'total_marks' => $totalMarks,
                    'obtained_marks' => $obtainedMarks,
                    'passing_marks' => $passingMarks,
                    'is_pass' => $obtainedMarks >= $passingMarks
                ];
            });

            $totalPass = $studentResults->where('is_pass', true)->count();
            $totalFail = $studentResults->where('is_pass', false)->count();

            // Calculate pass percentage - ensure it cannot exceed 100%
            $passPercentage = min(100, ($totalStudents > 0 ? round(($totalPass / $totalStudents) * 100, 2) : 0));

            return response()->json([
                'success' => true,
                'total_students' => $totalStudents,
                'total_pass' => $totalPass,
                'total_fail' => $totalFail,
                'pass_percentage' => $passPercentage
            ]);
        } catch (\Exception $e) {
            \Log::error('Error fetching rank wise result statistics: ' . $e->getMessage());
            return response()->json([
                'success' => false,
                'message' => 'Error fetching rank wise result statistics',
                'total_students' => 0,
                'total_pass' => 0,
                'total_fail' => 0,
                'pass_percentage' => 0
            ]);
        }
    }

    public function rankWiseResultPdf($student_id, $session_year_id)
    {
        ResponseService::noFeatureThenRedirect('Exam Management');
        ResponseService::noPermissionThenRedirect('exam-result');
        try {
            // get school settings
            $settings = $this->cache->getSchoolSettings();

            $schoolName = $settings['school_name'];
            $schoolLogo = $settings['horizontal_logo'];

            $results = $this->examResult->builder()
                ->with([
                    'session_year',
                    'class_section.class.stream',
                    'class_section.class.shift',
                    'class_section.section',
                    'class_section.medium',

                    'user' => function ($q) use ($student_id) {
                        $q->with([
                            'student.guardian',
                            'exam_marks' => function ($q) use ($student_id) {
                                $q->whereHas('timetable', function ($q) use ($student_id) {
                                    $q->where('student_id', $student_id);
                                })
                                    ->with([
                                        'class_subject' => function ($q) {
                                            $q->withTrashed()->with('subject:id,name,type');
                                        },
                                        'timetable'
                                    ])
                                    ->withSum('timetable', 'total_marks');
                            }
                        ]);
                    }
                ])
                ->with(['exam' => function ($q) use ($session_year_id) {
                    $q->where('publish', 1)->where('session_year_id', $session_year_id);
                }])
                ->whereHas('exam', function ($q) {
                    $q->where('publish', 1);
                })
                ->where('session_year_id', $session_year_id)
                ->where('student_id', $student_id)
                ->select('exam_results.*')
                ->get();
            // return $results->sum('total_marks');
            // Convert the results to a collection
            $results = collect($results);

            // Calculate Overall Rank based on total obtained marks for the session using SQL
            // 1. Calculate total marks for each student in the class/section for the given session year
            $studentTotalMarks = DB::table('exam_results')
                ->select('student_id', DB::raw('SUM(obtained_marks) as total_obtained'))
                ->join('exams', 'exam_results.exam_id', '=', 'exams.id')
                ->where('exam_results.class_section_id', $results->first()->class_section_id)
                ->where('exam_results.session_year_id', $session_year_id)
                ->where('exams.publish', 1)
                ->groupBy('student_id')
                ->orderByDesc('total_obtained')
                ->get();

            // 2. Find the rank of the current student
            $rank = $studentTotalMarks->search(function ($item) use ($student_id) {
                return $item->student_id == $student_id;
            });

            // 3. Assign overall rank (rank + 1 because search returns 0-based index)
            $overallRank = ($rank !== false) ? $rank + 1 : 'N/A';

            // Assign this single overall rank to the student's result object (which is used for the header)
            // We just need one instance to pass to the view, so we pick the match for the current student
            $result = $results->where('student_id', $student_id)->first();
            if ($result) {
                $result->rank = $overallRank;
            }


            $examIds = $result->pluck('exam_id')->toArray();
            $examIds = array_unique($examIds);

            $classIds = $results->pluck('class_section.class_id')->toArray();
            $classIds = array_unique($classIds);

            $exams = $this->exam->builder()
                ->with([
                    'timetable' => function ($q) {
                        $q->with('exam_marks');
                    }
                ])
                ->where('session_year_id', $session_year_id)
                ->whereIn('class_id', $classIds)
                ->whereIn('id', $examIds)
                ->where('publish', 1)
                ->get();

            // ====================================================================
            if (!$result) {
                return redirect()->back()->with('error', trans('no_records_found'));
            }

            $grades = $this->grade->builder()->orderBy('starting_range', 'ASC')->get();

            // get student attendance count
            $studentAttendanceCount = $this->attendance->builder()->where('student_id', $student_id)->where('type', 1)->where('session_year_id', $result->session_year_id)->count();

            $attendanceTotal = $this->attendance->builder()->where('student_id', $student_id)->where('session_year_id', $result->session_year_id)->count();


            return view('reports.exam.rank-wise-exam-result-pdf', compact('settings', 'result', 'grades', 'exams', 'studentAttendanceCount', 'attendanceTotal', 'results'));
        } catch (\Exception $e) {
            return $e;
            return redirect()->back()->with('error', $e->getMessage());
        }

        return response()->json($results);
    }

    public function expenseReport()
    {
        ResponseService::noAnyPermissionThenRedirect(['reports-expense']);

        $expenseCategory = $this->expenseCategory->builder()->pluck('name', 'id')->toArray();
        $vehicles = Vehicle::where('status', 1)->get(['name', 'id', 'vehicle_number']);
        $months = sessionYearWiseMonthYear();
        return view('reports.expense.index', compact('expenseCategory', 'vehicles', 'months'));
    }

    public function expenseReportShow()
    {
        ResponseService::noPermissionThenRedirect('transportationexpense-list');
        $offset = request('offset', 0);
        $limit = request('limit', 10);
        $sort = request('sort', 'date');
        $order = request('order', 'DESC');
        $search = request('search');
        $category_id = request('category_id');
        $vehicle_id = request('vehicle_id');
        $month = request('month');
        $schoolSettings = $this->cache->getSchoolSettings();

        $sessionYearId = $this->cache->getSessionYear()->id;
        $sql = $this->expense->builder()->where('session_year_id', $sessionYearId)->with('category', 'vehicle', 'created_by')->where(function ($query) use ($search) {
            $query->when($search, function ($query) use ($search) {
                $query->where(function ($query) use ($search) {
                    $query->where('title', 'LIKE', "%$search%")->orWhere('ref_no', 'LIKE', "%$search%")->orWhere('amount', 'LIKE', "%$search%")->orWhere('date', 'LIKE', "%$search%")->orWhere('description', 'LIKE', "%$search%")->orWhereHas('category', function ($q) use ($search) {
                        $q->Where('name', 'LIKE', "%$search%");
                    });
                });
            });
        });

        if ($category_id) {
            if ($category_id != 'salary' && $category_id != 'transportation') {
                $sql->where('category_id', $category_id)->whereNull('staff_id');
            } else if ($category_id == 'transportation') {
                $sql->whereNotNull('vehicle_id');
            } else {
                $sql->whereNotNull('staff_id');
            }
        }

        if ($vehicle_id) {
            $sql->where('vehicle_id', $vehicle_id);
        }

        if ($month) {
            $monthData = explode('_', $month);
            if (count($monthData) == 2) {
                $sql->whereMonth('date', $monthData[0])->whereYear('date', $monthData[1]);
            } else {
                $sql->whereMonth('date', $month);
            }
        }

        $total = $sql->count();
        if ($offset >= $total && $total > 0) {
            $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset
            $offset = $lastPage;
        }
        $sql->orderBy($sort, $order)->skip($offset)->take($limit);
        $res = $sql->get();
        $bulkData = array();
        $bulkData['total'] = $total;
        $rows = array();
        $no = $offset + 1;

        foreach ($res as $row) {
            $operate = '';
            if (!$row->month) {
                $operate .= BootstrapTableService::editButton(route('transportation-expense.update', $row->id));
                $operate .= BootstrapTableService::deleteButton(route('expense.destroy', $row->id));
            }

            $tempRow = $row->toArray();
            $tempRow['no'] = $no++;
            $tempRow['amount'] = $row->amount;
            if ($row->category_id != null) {
                $tempRow['category_name'] = $row->category->name;
            } else {
                $tempRow['category_name'] = "Salary";
            }
            if (isset($row->vehicle->name) && isset($row->vehicle->vehicle_number)) {
                $tempRow['vehicle'] = $row->vehicle->name . " (" . $row->vehicle->vehicle_number . ")";

                $fileUrl = $row->file ?? null;
                $fileExtension = '';
                if (!empty($fileUrl)) {
                    $fileExtension = strtolower(pathinfo($fileUrl, PATHINFO_EXTENSION));
                }
                $previewHtml = '';
                if ($fileExtension) {
                    if (in_array($fileExtension, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'])) {
                        $previewHtml = '
                    <a href="' . $fileUrl . '" target="_blank" class="btn btn-sm btn-outline-info w-100 mt-2">
                        View Image
                    </a>';
                    } elseif ($fileExtension === 'pdf') {
                        $previewHtml = '
                    <a href="' . $fileUrl . '" target="_blank" class="btn btn-sm btn-outline-info w-100 mt-2">
                        View PDF
                    </a>';
                    } else {
                        $previewHtml = '<span class="text-danger">Unsupported file type</span>';
                    }
                }
                $tempRow['file'] = $previewHtml;
            }
            $tempRow['date'] = Carbon::parse($row->getRawOriginal('created_at'))->format($schoolSettings['date_format']);
            $tempRow['operate'] = $operate;
            $rows[] = $tempRow;
        }

        $bulkData['rows'] = $rows;
        return response()->json($bulkData);
    }

    public function teacher_reports()
    {
        ResponseService::noPermissionThenRedirect('reports-teacher');

        $class_sections = $this->classSection->all(['*'], ['class', 'class.stream', 'class.shift', 'section', 'medium']);

        if (Auth::user()->school_id) {
            $extraFields = $this->formFields->defaultModel()->where('user_type', 2)->orderBy('rank')->get();
        } else {
            $extraFields = $this->formFields->defaultModel()->orderBy('rank')->get();
        }

        return view('reports.teacher.teacher-reports', compact('class_sections', 'extraFields'));
    }

    public function teacher_reports_show(Request $request)
    {
        ResponseService::noPermissionThenRedirect('reports-teacher');

        $offset = request('offset', 0);
        $limit = request('limit', 10);
        $sort = request('sort', 'id');
        $order = request('order', 'ASC');
        $search = request('search');

        // Main student query
        $sql = $this->user->builder()->withTrashed()
            ->role('Teacher')
            ->with('staff', 'staff.staffSalary', 'extra_student_details.form_field')
            ->where(function ($query) use ($search) {

                $query->when($search, function ($query) use ($search) {
                    $query->where('id', 'LIKE', "%{$search}%")
                        ->orWhere('first_name', 'LIKE', "%{$search}%")
                        ->orWhere('last_name', 'LIKE', "%{$search}%")
                        ->orWhere(DB::raw("CONCAT(first_name,' ',last_name)"), 'LIKE', "%{$search}%")  // FIXED
                        ->orWhere('gender', 'LIKE', "%{$search}%")
                        ->orWhere('email', 'LIKE', "%{$search}%")
                        ->orWhere('current_address', 'LIKE', "%{$search}%")
                        ->orWhere('permanent_address', 'LIKE', "%{$search}%");
                });
                // staff conditions in SAME group, not separate
                $query->orWhereHas('staff', function ($q) use ($search) {
                    $q->where('staffs.qualification', 'LIKE', "%{$search}%");
                });
            });

        $total = $sql->count();
        if ($offset >= $total && $total > 0) {
            $lastPage = floor(($total - 1) / $limit) * $limit; // calculate last page offset
            $offset = $lastPage;
        }
        $sql->orderBy($sort, $order)->skip($offset)->take($limit);
        $res = $sql->get();
        $bulkData = array();
        $bulkData['total'] = $total;
        $rows = array();
        $no = 1;

        foreach ($res as $row) {
            $userId = $row->user_id;

            $operate = BootstrapTableService::viewButton(
                route('reports.teacher.teacher-view-reports', [$row->id]),
                [],
                [
                    'title' => trans('View Teacher Details'),
                    'target' => ''
                ]
            );

            $tempRow = $row->toArray();
            $tempRow['no'] = $no++;
            $tempRow['dob_org'] = $row->getRawOriginal('dob');
            $tempRow['joining_date_org'] = $row->staff->getRawOriginal('joining_date');
            $tempRow['extra_fields'] = $row->extra_student_details;

            foreach ($row->extra_student_details as $field) {
                $data = '';
                if ($field->form_field->type == 'checkbox') {
                    $data = json_decode($field->data);
                } elseif ($field->form_field->type == 'file') {
                    $data = '<a href="' . Storage::url($field->data) . '" target="_blank">DOC</a>';
                } elseif ($field->form_field->type == 'dropdown') {
                    $data = $field->data ?? '';
                } else {
                    $data = $field->data;
                }

                $tempRow[$field->form_field->name] = $data;
            }
            $tempRow['operate'] = $operate;
            $rows[] = $tempRow;
        }

        $bulkData['rows'] = $rows;
        return response()->json($bulkData);
    }

    public function teacher_view_reports($id)
    {
        ResponseService::noPermissionThenRedirect('reports-teacher');
        $schoolSettings = $this->cache->getSchoolSettings();
        $teacher = $this->user->builder()
            ->where('id', $id)
            ->with([
                'staff',
                'staff.staffSalary',
                'extra_student_details.form_field',
                'session_year:id,name',

                'staff.subjects:id,class_section_id,teacher_id,subject_id',
                'staff.subjects.subject:id,name,type',

                'staff.class_teacher:id,class_section_id,teacher_id',
            ])
            ->withTrashed()
            ->first();

        if (!$teacher || !$teacher->staff) {
            return back()->with('error', 'Teacher not found');
        }

        $classSectionIds = collect([
            ...$teacher->staff->subjects->pluck('class_section_id')->toArray(),
            ...$teacher->staff->class_teacher->pluck('class_section_id')->toArray(),
        ])
            ->unique()
            ->values();

        $classSections = ClassSection::whereIn('id', $classSectionIds)
            ->with([
                'class',
                'class.stream',
                'class.shift',
                'section',
                'medium',
            ])
            ->get()
            ->keyBy('id');
        $teacher->staff->subjects->each(function ($item) use ($classSections) {
            $item->setRelation(
                'class_section',
                $classSections[$item->class_section_id] ?? null
            );
        });
        $teacher->staff->class_teacher->each(function ($item) use ($classSections) {
            $item->setRelation(
                'class_section',
                $classSections[$item->class_section_id] ?? null
            );
        });

        $transportation = null;

        $transportationPayments = TransportationPayment::with(['transportationFee', 'paymentTransaction', 'pickupPoint', 'routeVehicle', 'shift'])
            ->where('user_id', $id)
            ->where('status', 'paid')
            ->orderBy('id', 'desc')
            ->first();

        if ($transportationPayments) {
            $routePickupPoint = null;

            if ($transportationPayments->pickupPoint && $transportationPayments->routeVehicle?->route) {
                $routePickupPoint = RoutePickupPoint::where('pickup_point_id', $transportationPayments->pickupPoint->id)
                    ->where('route_id', $transportationPayments->routeVehicle->route->id)
                    ->first();
            }

            $transportation = [
                'plan' => [
                    'plan_status' => ($transportationPayments->expiry_date && $transportationPayments->expiry_date >= date('Y-m-d')) ? 'Active' : 'Expired',
                    'vehicle_assignment' => ($transportationPayments->routeVehicle) ? 'Assigned' : 'Pending',
                    'expiry_date' => $transportationPayments->expiry_date,
                    'paid_amount' => ($schoolSettings['currency_symbol'] ?? '$') . " " . $transportationPayments->amount,
                    'payment_mode' => $transportationPayments->paymentTransaction->payment_gateway ?? null,
                ],
                'shift' => [
                    'name' => isset($transportationPayments->shift->name) ? $transportationPayments->shift->name : null,
                    'start_time' => isset($transportationPayments->shift->start_time) ? Carbon::parse($transportationPayments->shift->start_time)->format($schoolSettings['time_format'] ?? 'H:i A') : null,
                    'end_time' => isset($transportationPayments->shift->end_time) ? Carbon::parse($transportationPayments->shift->end_time)->format($schoolSettings['time_format'] ?? 'H:i A') : null,
                ],
                'fee' => [
                    'duration' => optional($transportationPayments->transportationFee)->duration ?? null,
                    'amount' => ($schoolSettings['currency_symbol'] ?? '$') . " " . optional($transportationPayments->transportationFee)->fee_amount ?? null,
                ],
                'route' => [
                    'route_name' => $transportationPayments->routeVehicle->route->name ?? null,
                    'pickup_point_name' => $transportationPayments->pickupPoint->name ?? null,
                    'pickup_time' => $routePickupPoint?->pickup_time
                        ? Carbon::parse($routePickupPoint->pickup_time)->format($schoolSettings['time_format'] ?? 'H:i A')
                        : null,
                    'drop_time' => $routePickupPoint?->drop_time
                        ? Carbon::parse($routePickupPoint->drop_time)->format($schoolSettings['time_format'] ?? 'H:i A')
                        : null,
                ],
                'vehicle' => [
                    'name' => $transportationPayments->routeVehicle->vehicle->name ?? null,
                    'number' => $transportationPayments->routeVehicle->vehicle->vehicle_number ?? null,
                    'capacity' => $transportationPayments->routeVehicle->vehicle->capacity ?? null,
                    'driver' => $transportationPayments->routeVehicle->driver ?? null,
                    'helper' => $transportationPayments->routeVehicle->helper ?? null
                ],
            ];
        }
        $teacherID = $teacher->id;
        $timetables = $this->timetable->builder()->whereHas('subject_teacher', function ($q) use ($teacherID) {
            $q->where('teacher_id', $teacherID);
        })->with('subject:id,name,bg_color,type', 'class_section.class', 'class_section.class.shift', 'class_section.section', 'class_section.medium')->get();

        // Get Timetable Settings Data
        $timetableSettingsData = $this->schoolSettings->getBulkData([
            'timetable_start_time',
            'timetable_end_time',
            'timetable_duration'
        ]);

        $salaries = StaffSalary::where('staff_id', $teacher->staff->id)
            ->with('payrollSetting')
            ->get();

        $basic_salary = $teacher->staff->salary;

        $total_allowance = 0;
        $total_deduction = 0;

        $allowance_details = [];
        $deduction_details = [];

        foreach ($salaries as $salary) {

            $setting = $salary->payrollSetting;
            if (!$setting)
                continue;

            // Calculate value (amount or percentage)
            $value = $salary->amount ?? 0;

            if ($setting->percentage) {
                $value = ($basic_salary * $setting->percentage) / 100;
            }

            if ($setting->type === 'allowance') {

                $total_allowance += $value;

                $allowance_details[] = [
                    'name' => $setting->name,
                    'amount' => $value,
                    'type' => 'Allowance',
                ];
            } elseif ($setting->type === 'deduction') {

                $total_deduction += $value;

                $deduction_details[] = [
                    'name' => $setting->name,
                    'amount' => $value,
                    'type' => 'Deduction',
                ];
            }
        }

        $net_salary = $basic_salary + $total_allowance - $total_deduction;

        $salary_structure = [
            'basic_salary' => $basic_salary,
            'total_allowance' => $total_allowance,
            'total_deduction' => $total_deduction,
            'net_salary' => $net_salary,

            'allowances' => $allowance_details,
            'deductions' => $deduction_details,
        ];

        $leaves = Leave::where('user_id', $teacher->id)
            ->where('status', 1)
            ->whereYear('from_date', date('Y'))
            ->with('leave_detail')
            ->get();

        // Get all session years for exam tab
        $sessionYears = $this->sessionYear->all();
        $sessionYear = $this->cache->getSessionYear();

        // Generate months for attendance tab based on session year range
        $attendanceMonths = [];
        if ($sessionYear) {
            try {
                $settings = $this->cache->getSchoolSettings();
                $start = Carbon::createFromFormat($settings['date_format'], $sessionYear->start_date)->startOfMonth();
                $end = Carbon::createFromFormat($settings['date_format'], $sessionYear->end_date)->startOfMonth();

                $current = $start->copy();
                while ($current <= $end) {
                    $attendanceMonths[] = (object)[
                        'id' => $current->month,
                        'year' => $current->year,
                        'name' => $current->format('F Y')
                    ];
                    $current->addMonth();
                }
            } catch (\Exception $e) {
            }
        }

        return view('reports.teacher.teacher-view-reports', compact('teacher', 'transportation', 'timetables', 'timetableSettingsData', 'salary_structure', 'sessionYear', 'attendanceMonths'));
    }

    public function getTeacherAttendanceReport(Request $request)
    {
        ResponseService::noPermissionThenRedirect('reports-teacher');
        // Validate request parameters
        $request->validate([
            'month' => 'required|numeric|between:1,12',
            'attendance_year' => 'required|numeric',
            'teacher_id' => 'required|exists:users,id',
            'session_year_id' => 'required|exists:session_years,id',
        ]);

        // Get session year from request
        $sessionYear = $this->sessionYear->findById($request->session_year_id);
        $schoolSettings = $this->cache->getSchoolSettings();

        $year = $request->attendance_year;
        // Create a Carbon date for the first day of the month
        $startDate = Carbon::createFromDate($year, $request->month, 1)->startOfMonth();
        $endDate = Carbon::createFromDate($year, $request->month, 1)->endOfMonth();

        // Get attendance records for this student in the specified month
        $attendanceRecords = StaffAttendance::where('staff_id', $request->teacher_id)
            ->whereBetween('date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
            ->get();

        $leaves = Leave::where('user_id', $request->teacher_id)->with('leave_detail')
            ->where('status', 1)
            ->where(function ($q) use ($startDate, $endDate) {
                $q->whereBetween('from_date', [$startDate, $endDate])
                    ->orWhereBetween('to_date', [$startDate, $endDate])
                    ->orWhere(function ($sub) use ($startDate, $endDate) {
                        $sub->where('from_date', '<=', $startDate)
                            ->where('to_date', '>=', $endDate);
                    });
            })
            ->whereHas('leave_detail', function ($q) {
                $q->where('type', 'Full');
            })
            ->get();
        // Convert leaves to a Y-m-d array for fast lookup
        $leaveDays = [];
        $finalFormat = $schoolSettings['date_format'] . ' ' . $schoolSettings['time_format'];
        foreach ($leaves as $leave) {
            // $from = Carbon::createFromFormat($finalFormat, $leave->from_date)->startOfDay();
            // $to = Carbon::createFromFormat($finalFormat, $leave->to_date)->startOfDay();

            // $period = $from->daysUntil($to->copy()->addDay());

            foreach ($leave->leave_detail as $leaveDetail) {
                $leaveDays[$leaveDetail->date] = true;
            }
        }

        // handle holiday attendance
        $holidayAttendance = $this->holiday->builder()
            ->whereBetween('date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
            ->get()
            ->map(function ($h) {
                return Carbon::parse($h->dmyFormat)->format('Y-m-d');
            });

        $leaveMaster = $this->cache->getDefaultSessionYearLeaveMaster();
        $holiday_days = $leaveMaster && $leaveMaster->holiday
            ? explode(',', $leaveMaster->holiday)
            : [];
        if ($leaveMaster) {
            $period = Carbon::parse($startDate)->daysUntil(Carbon::parse($endDate)->addDay());

            foreach ($period as $day) {
                if (in_array($day->format('l'), $holiday_days)) {
                    $holidayAttendance->push($day->format('Y-m-d'));
                }
            }
        }


        // Count present, absent and holiday days
        $presentCount = $attendanceRecords->where('type', 1)->count();
        $absentCount = $attendanceRecords->where('type', 0)->count();
        $holidayCount = $attendanceRecords->where('type', 3)->count();
        $holidayCount += $holidayAttendance->count();
        $halfCount = $attendanceRecords->where('type', 4)->count();
        $halfCount += $attendanceRecords->where('type', 5)->count();

        // Calculate attendance percentage
        $totalDays = $presentCount + $absentCount;
        $attendancePercentage = $totalDays > 0 ? round(($presentCount / $totalDays) * 100) : 0;

        // Prepare the response data
        $responseData = [
            'success' => true,
            'attendance' => $attendanceRecords,
            'leaves' => $leaveDays,
            'holiday' => $holidayAttendance,
            'summary' => [
                'present_count' => $presentCount,
                'absent_count' => $absentCount,
                'holiday_count' => $holidayCount,
                'half_count' => $halfCount,
                'attendance_percentage' => $attendancePercentage,
                'total_days' => $totalDays
            ]
        ];

        return response()->json($responseData);
    }

    public function teacherLeaves(Request $request)
    {
        ResponseService::noPermissionThenRedirect('reports-teacher');
        $teacherId = $request->teacher_id;
        $month = (int) $request->month;
        $year = (int) $request->year;

        $startDate = Carbon::create($year, $month, 1)->startOfMonth();
        $endDate = Carbon::create($year, $month, 1)->endOfMonth();

        // STEP 1 — Get leaves overlapping the month
        $leaves = Leave::where('user_id', $teacherId)
            ->where(function ($q) use ($startDate, $endDate) {
                $q->whereBetween('from_date', [$startDate, $endDate])
                    ->orWhereBetween('to_date', [$startDate, $endDate])
                    ->orWhere(function ($q2) use ($startDate, $endDate) {
                        $q2->where('from_date', '<=', $startDate)
                            ->where('to_date', '>=', $endDate);
                    });
            })
            ->with(['leave_detail']) // load leave type if needed
            ->orderBy('from_date', 'ASC')
            ->get();

        // STEP 2 — Transform for frontend
        $final = [];

        foreach ($leaves as $leave) {

            // Filter leave_detail rows belonging to selected month
            $details = $leave->leave_detail
                ->filter(
                    fn($d) =>
                    Carbon::parse($d->date)->month == $month &&
                        Carbon::parse($d->date)->year == $year
                )
                ->map(fn($d) => [
                    'date' => $d->date,
                    'date_formatted' => Carbon::parse($d->date)->format('d M Y'),
                    'type' => $d->type,    // Full, First Half, Second Half
                ])
                ->values();

            foreach ($details as $day) {
                $final[] = [
                    'leave_id' => $leave->id,
                    'status' => $leave->status,
                    'date' => $day['date'],
                    'date_formatted' => $day['date_formatted'],
                    'type' => $day['type'],
                    'reason' => $leave->reason,
                ];
            }
        }

        return response()->json([
            'success' => true,
            'total' => count($final),
            'leaves' => $final,
        ]);
    }
}