<?php
namespace App\Http\Controllers\Api\V1;
use DateTime;
use Carbon\Carbon;
use App\Constants\Status;
use Illuminate\Http\Request;
use App\Constants\Accounting;
use App\Models\Academic\Branch;
use App\Models\Accounting\Invoice;
use Illuminate\Support\Facades\DB;
use App\Models\Accounting\ClassFee;
use App\Models\Student\StudentInfo;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;
use App\Models\Academic\AcademicYear;
use App\Models\Accounting\StudentFee;
use App\Models\Accounting\InvoiceItem;
use App\Models\Scopes\BranchFilterScope;
use App\Models\Accounting\StudentDiscount;

class IBBLPaymentController extends Controller
{
    private $userId;
    private $iid;
    private $password;

    public function __construct()
    {
        $this->userId = 'ibbl_jabalunnur_59753';
        $this->iid = '25_JAB';
        $this->password = '23232323'; 
    }
    
    
    
    
    public function getFeeInfo(Request $request)
    {
        try {
            // Validate incoming request
            $this->validateRequest($request);
            $studentId = $request->referenceId;
            $monthShort = Carbon::now()->format('M');
            // Find student by reference ID (student ID) without BranchFilterGlobalScope
            $student = StudentInfo::withoutGlobalScope(BranchFilterScope::class)
                ->with(['studentClass', 'section'])
                ->where('student_id_no', $studentId)
                ->first();
            if (!$student) {
                return $this->errorResponse('Student not found');
            }

            $months = $request->month ? range($request->month, $request->month) : range(1, date('n'));
            $feeSummary = $this->digitalDueFeesSummary($studentId, $months);
            $defaultBranchId = Branch::orderBy('id', 'ASC')->first()?->id ?? 1;

            return response()->json([
                'referenceId' => $request->referenceId,
                'dateTime' => now()->format('d/m/Y h:i A'),
                'responseCode' => '00',
                'responseMsg' => 'SUCCESS',
                'feeDetails' => [
                    'studentId' => $student->student_id_no,
                    'instituteName' =>  "Jabalun Nur Al Jamiatul Islamia", 
                    'branchName' => $student->branch?->branch_name ?? '',
                    'shift' => $student->shift?->shift_name ?? '',
                    'className' => $student->studentClass->class_name,
                    'sectionName' => $student->section?->section_name ?? '',
                    'invoiceNo' => $feeSummary['invoice_no'] ?? null,
                    'studentName' => $student->first_name . ' ' . $student->last_name,
                    'fatherName' => $student->father_name,
                    'month' => $monthShort,
                    'academicYear' => getDefaultAcademicYearValue(),
                    'fee' => $feeSummary['grand_total_amount'] ?? number_format(0, 2, '.', ''),
                    'waiver' => $feeSummary['grand_discount_amount'] ?? number_format(0, 2, '.', ''),
                    'totalDue' => $feeSummary['grand_total_amount'] - $feeSummary['grand_discount_amount'] ?? number_format(0, 2, '.', ''),

                    // 'all' => $feeSummary,
                    // 'months' => $months,
                ]
            ]);
        } catch (\Exception $e) {
            Log::error('IBBL Payment Error: ' . $e->getMessage());
            return $this->errorResponse('Internal server error');
        }
    }

        // new due fees summary function
    public function digitalDueFeesSummary($studentId, $months = null)
    {
        // Step 1: Load student
        $student = StudentInfo::withoutGlobalScope(BranchFilterScope::class)
            ->with(['studentClass', 'section'])
            ->where('student_id_no', $studentId)
            ->first();
    
        if (!$student) {
            throw new \Exception("Student not found for ID: {$studentId}");
        }
    
        $branch_id = $student->branch_id;
        $class_id = $student->student_class_id;
        $section_id = $student->section_id;
        $academicYear = getDefaultAcademicYearValue();
        $year_id = AcademicYear::where('year', $academicYear)->value('id');
        $months = $months ?? range(1, date('n'));
    
        // ðŸ§© Step 2: Exclude already paid months
        $paidMonths = DB::table('student_fees')
            ->where('branch_id', $branch_id)
            ->where('academic_year_id', $year_id)
            ->where('student_id', $student->id)
            ->where('status', Status::ACTIVE)
            ->distinct()
            ->pluck('month')
            ->filter()
            ->toArray();
    
        if (!empty($paidMonths)) {
            $months = array_diff($months, $paidMonths);
            $checkpaidMonths = 'Paid Months: ';
        } else {
            // no paid months, keep original request months
            $months = $months;
        }
    
        $months = array_values($months); // reindex cleanly
    
    
        // âœ… Reset array keys (optional)
        $months = array_values($months);
    
        if (empty($months)) {
            return [
                'message' => 'All selected months are already paid.',
                'grand_total_amount' => 0,
                'grand_discount_amount' => 0,
                'grand_due_amount' => 0,
                'invoice_no' => null,
                'months' => [],
            ];
        }
    
        // ðŸ§© Step 3: Fetch class fees
        $class_fees = ClassFee::withoutGlobalScope(BranchFilterScope::class)
            ->join('fee_types as ft', 'class_fees.fee_type_id', '=', 'ft.id')
            ->where('class_fees.branch_id', $branch_id)
            ->where('class_fees.academic_year_id', $year_id)
            ->where('class_fees.student_class_id', $class_id)
            ->where(function ($q) use ($months) {
                $q->whereIn('month', $months)
                  ->orWhereNull('month');
            })
            ->select([
                'ft.code',
                'ft.id as fee_type_primary_id',
                'class_fees.fee_type_id',
                'class_fees.month',
                'class_fees.amount as assigned_fee',
            ])
            ->get();
    
        // ðŸ§© Step 4: Existing due invoice
        $exist_invoice = Invoice::withoutGlobalScope(BranchFilterScope::class)
            ->where('student_info_id', $student->id)
            ->where('academic_year_id', $year_id)
            ->where('branch_id', $branch_id)
            ->where('status', Status::DUE)
            ->where(function ($query) use ($months) {
                $query->whereHas('invoiceItems', function ($q) use ($months) {
                    $q->whereIn('month', $months)
                      ->orWhereNull('month');
                });
            })
            ->with(['invoiceItems' => function ($q) use ($months) {
                $q->whereIn('month', $months)
                  ->orWhereNull('month');
            }])
            ->orderBy('id', 'desc')
            ->first();
    
        // ðŸ§© Step 5: Fee Summary
        $feeSummary = collect();
        
        foreach ($months as $month) {
            $months3 = $months;
            $fee_type_ids = DB::table('class_fees')
                ->where('branch_id', $branch_id)
                ->where('academic_year_id', $year_id)
                ->where('student_class_id', $class_id)
                ->where('month', $month)
                ->pluck('fee_type_id');
    
            $class_fees_amounts = DB::table('class_fees')
                ->where('branch_id', $branch_id)
                ->where('academic_year_id', $year_id)
                ->where('student_class_id', $class_id)
                ->where('month', $month)
                ->pluck('amount');
    
            $total = DB::table('class_fees')
                ->where('branch_id', $branch_id)
                ->where('academic_year_id', $year_id)
                ->where('student_class_id', $class_id)
                ->where('month', $month)
                ->sum('amount');
    
            $paid = DB::table('student_fees')
                ->where('branch_id', $branch_id)
                ->where('academic_year_id', $year_id)
                ->where('student_id', $student->id)
                ->where('month', $month)
                ->where('status', Status::ACTIVE)
                ->sum('total_amount');
    
            if ($total == 0 || $paid >= $total) continue; // Skip fully paid months
    
            $feeSummary->push((object) [
                'student_id' => $student->id,
                'name' => $student->name,
                'student_id_no' => $student->student_id_no,
                'class_name' => $student->studentClass?->class_name,
                'fee_type_id' => $fee_type_ids,
                'class_fees_amount' => $class_fees_amounts,
                'month' => $month,
                'total_amount' => $total,
                'paid_amount' => $paid,
            ]);
        }
    
        if ($feeSummary->isEmpty()) {
            return [
                'message' => 'No due found. all months fully paid.',
                'grand_total_amount' => 0,
                'grand_discount_amount' => 0,
                'grand_due_amount' => 0,
                'invoice_no' => null,
                'months' => $months,
            ];
        }
    
        // ðŸ§© Step 6: Totals
        $grand_total = $feeSummary->sum('total_amount');
        $grand_paid = $feeSummary->sum('paid_amount');
        $grand_due_before_discount = $grand_total - $grand_paid;
    
        // ðŸ§© Step 7: Discount
        $studentDiscount = StudentDiscount::withoutGlobalScope(BranchFilterScope::class)
            ->where('student_id', $student->id)
            ->first();
    
        $grand_discount = 0.00;
        if ($studentDiscount) {
            $grand_discount = calculateDiscount(
                $grand_due_before_discount,
                $studentDiscount->discount_type,
                $studentDiscount->amount,
                count($months)
            );
        }
    
        $grand_due = $grand_due_before_discount - $grand_discount;
    
        // ðŸ§© Step 8: Prepare invoice data
        $invoice_data = null;
        $invoiceData = [
            'student_info_id' => $student->id,
            'academic_year_id' => $year_id,
            'branch_id' => $branch_id,
            'shift_id' => $student->shift_id ?? null,
            'student_class_id' => $class_id,
            'section_id' => $section_id,
            'total_amount' => (float) $grand_total,
            'total_discount' => (float) $grand_discount,
            'late_fine' => (float) (lateFineCalculation() ?? 0),
            'paid_amount' => (float) $grand_paid,
            'payment_date' => now(),
            'invoice_date' => now()->format('Y-m-d'),
            'due_date' => now()->format('Y-m-d'),
            'payment_method' => '',
            'note' => 'payment completed through CELLFIN',
            'status' => Status::DUE,
        ];
    
        // ðŸ§© Step 9: Create invoice if not exist
        if (!$exist_invoice && $grand_due > 0) {
            DB::transaction(function () use ($feeSummary, $studentDiscount, $invoiceData, &$invoice_data, $branch_id, $year_id, $student) {
                $invoice_data = Invoice::create($invoiceData);
                $invoiceItemData = [];
                $total_amount = 0;
                $total_discount = 0;
                $paid_amount = 0;
    
                foreach ($feeSummary as $fee) {
                    $month = $fee->month;
                    $fee_total = (float) $fee->total_amount;
                    $fee_paid = (float) $fee->paid_amount;
    
                    $individual_discount = 0.00;
                    if ($studentDiscount) {
                        $individual_discount = calculateDiscount(
                            $fee_total - $fee_paid,
                            $studentDiscount->discount_type ?? null,
                            $studentDiscount->amount ?? 0
                        );
                    }
    
                    $studentFee = StudentFee::create([
                        'invoice_id' => $invoice_data->id,
                        'branch_id' => $branch_id,
                        'academic_year_id' => $year_id,
                        'student_class_id' => $student->student_class_id,
                        'student_id' => $student->id,
                        'total_amount' => $fee_total,
                        'total_discount' => $individual_discount,
                        'grand_total' => $fee_total - $individual_discount,
                        'month' => $month,
                        'start_date' => now()->startOfMonth(),
                        'end_date' => now()->endOfMonth(),
                        'status' => Status::INACTIVE,
                    ]);
    
                    $feeTypeIds = $fee->fee_type_id ?? [];
                    $classFeeAmounts = $fee->class_fees_amount ?? [];
    
                    foreach ($feeTypeIds as $i => $feeTypeId) {
                        $amount = isset($classFeeAmounts[$i]) ? (float) $classFeeAmounts[$i] : 0.00;
    
                        $invoiceItemData[] = [
                            'invoice_id' => $invoice_data->id,
                            'student_id' => $student->id,
                            'fee_type_id' => $feeTypeId,
                            'amount' => $amount,
                            'month' => $month,
                            'academic_year_id' => $year_id,
                            'student_fee_id' => $studentFee->id,
                            'payment_date' => now()->format('Y-m-d'),
                            'created_at' => now(),
                            'updated_at' => now(),
                        ];
                    }
    
                    $total_amount += $fee_total;
                    $total_discount += $individual_discount;
                    $paid_amount += $fee_paid;
                }
    
                if (!empty($invoiceItemData)) {
                    InvoiceItem::insert($invoiceItemData);
                }
    
                $invoice_data->update([
                    'total_amount' => number_format($total_amount, 2, '.', ''),
                    'total_discount' => number_format($total_discount, 2, '.', ''),
                    'late_fine' => lateFineCalculation() ?? 0.00,
                    'paid_amount' => number_format($paid_amount, 2, '.', ''),
                ]);
            });
        }
    
        // ðŸ§© Step 10: Prepare final return
        $invoice = $exist_invoice ?? $invoice_data;
        $grand_total_return = (float) ($invoice->total_amount ?? $grand_total);
        $grand_discount_return = (float) ($invoice->total_discount ?? $grand_discount);
        $grand_due_return = $grand_total_return - $grand_discount_return;
        $invoice_no = $invoice->invoice_no ?? null;
    
        return [
            'grand_total_amount' => $grand_total_return,
            'grand_discount_amount' => $grand_discount_return,
            'grand_due_amount' => $grand_due_return,
            'invoice_no' => $invoice_no,
            'months' => array_values($months), // only unpaid months
        ];
    }



    public function digitalDueFeesSummaryOLD($studentId, $months)
    {
        $student_id_no = $studentId;

        $student = StudentInfo::withoutGlobalScope(BranchFilterScope::class)
            ->with(['studentClass', 'section'])
            ->where('student_id_no', $student_id_no)
            ->first();

        $branch_id = $student->branch_id;
        $class_id = $student->student_class_id;
        $section = $student->section_id;
        $academicYear = getDefaultAcademicYearValue();
        $year_id = AcademicYear::where('year', $academicYear)->value('id');

        $months = $months ?? range(1, date('n'));
        $late_fine = 0;
        // âœ… get class_fees
        $class_fees = ClassFee::withoutGlobalScope(BranchFilterScope::class)
            ->join('fee_types as ft', 'class_fees.fee_type_id', '=', 'ft.id')
            ->where('class_fees.branch_id', $branch_id)
            ->where('class_fees.academic_year_id', $year_id)
            ->where('class_fees.student_class_id', $class_id)
            ->where(function($query) use ($months) {
                $query->whereIn('month', $months)
                      ->orWhereNull('month');
            })
            ->select([
                'ft.code',
                'ft.id as fee_type_primary_id',
                'class_fees.fee_type_id',
                'class_fees.month',
                'class_fees.amount as assigned_fee',
            ])
            ->get();
        // Check for existing invoice
        $exist_invoice = Invoice::withoutGlobalScope(BranchFilterScope::class)
            ->where('student_info_id', $student->id)
            ->where('academic_year_id', $year_id)
            ->where('branch_id', $branch_id)
            ->where(function ($query) use ($months) {
                $query->whereHas('invoiceItems', function ($q) use ($months) {
                    $q->whereIn('month', $months)
                    ->orWhereNull('month');
                });
            })
            ->with(['invoiceItems' => function ($q) use ($months) {
                $q->whereIn('month', $months)
                ->orWhereNull('month');
            }])
            ->orderBy('id', 'desc')
            ->first();



        // âœ… Build fee summary
        $feeSummary = collect();

        foreach ($months as $month) {
            $fee_type_id = DB::table('class_fees')
                ->where('branch_id', $branch_id)
                ->where('academic_year_id', $year_id)
                ->where('student_class_id', $student->student_class_id)
                ->where('month', $month)->pluck('fee_type_id');
            $class_fees_amount = DB::table('class_fees')
                ->where('branch_id', $branch_id)
                ->where('academic_year_id', $year_id)
                ->where('student_class_id', $student->student_class_id)
                ->where('month', $month)->pluck('amount');


            $total = DB::table('class_fees')
                ->where('branch_id', $branch_id)
                ->where('academic_year_id', $year_id)
                ->where('student_class_id', $student->student_class_id)
                ->where('month', $month)
                ->sum('amount');

            $paid = DB::table('student_fees')
                ->where('branch_id', $branch_id)
                ->where('academic_year_id', $year_id)
                ->where('student_id', $student->id)
                ->where('month', $month)
                ->sum('total_amount');

            // Skip if no fees at all
            if ($total == 0 && $paid == 0) {
                continue;
            }

            $feeSummary->push((object)[
                'student_id' => $student->id,
                'name' => $student->name,
                'student_id_no' => $student->student_id_no,
                'class_name' => $student->studentClass?->class_name,
                'fee_type_id' => $fee_type_id,
                'class_fees_amount' => $class_fees_amount,
                'month' => $month,
                'total_amount' => $total,
                'paid_amount' => $paid,
            ]);
        }

        // âœ… Directly use collection as dueFees
        $dueFees = $feeSummary;

        // âœ… Calculate totals
        $grand_total_amount = 0;
        $grand_paid_amount = 0;

        foreach ($dueFees as $fee) {
            $total = (float) ($fee->total_amount ?? 0);
            $paid = (float) ($fee->paid_amount ?? 0);

            $grand_total_amount += $total;
            $grand_paid_amount += $paid;
        }

        // âœ… Calculate due before discount
        $grand_due2 = (float) $grand_total_amount - (float) $grand_paid_amount;

        // âœ… Try to fetch discount
        $studentDiscount = StudentDiscount::withoutGlobalScope(BranchFilterScope::class)
            ->where('student_id', 727)
            ->first();

        // ðŸš¨ If  discount found
        $grand_discount_amount = 0.00;
        if (!empty($studentDiscount)) {
            $grand_discount_amount = $this->calculateDiscount($grand_due2 , $studentDiscount->discount_type, $studentDiscount->amount, count($months));
        }

        $grand_due_amount = $grand_due2 - $grand_discount_amount;
        // âœ… Format numbers (always 2 decimal places)
        $grand_total_amount    = number_format($grand_due2, 2, '.', '');
        $grand_paid_amount     = number_format($grand_paid_amount, 2, '.', '');
        $grand_discount_amount = number_format($grand_discount_amount, 2, '.', '');
        $grand_due_amount      = number_format($grand_due_amount, 2, '.', '');

        $invoiceData = [
            'student_info_id'       => $student->id, // Use internal ID here
            'academic_year_id' => $year_id,
            'branch_id'        => $branch_id,
            'shift_id'         => $student->shift_id ?? null,
            'student_class_id' => $student->student_class_id,
            'section_id'       => $student->section_id ?? null,
            'total_amount'     => (float) $grand_total_amount,
            'total_discount'   => (float) ($grand_discount_amount ?? 0),
            'late_fine'        => (float) (lateFineCalculation() ?? 0),
            'paid_amount'      => (float) $grand_due_amount,
            'payment_date'     => now(), // timestamp
            'invoice_date'     => now()->format('Y-m-d'),
            'due_date'         => now()->format('Y-m-d'),
            'payment_method'   => '',
            'note'             => 'payment completed through CELLFIN',
            'status'           => Status::DUE, // match allowed value
        ];

        // Create invoice
        if($grand_total_amount >= $grand_paid_amount){
            $invoice_data = Invoice::create($invoiceData);
            $total_amount = 0;
            $total_discount = 0;
            $paid_amount = 0;
            $invoiceItemData = [];

            foreach ($dueFees as $fee) {
                $month = $fee->month;
                $fee_total = (float) $fee->total_amount;
                $fee_paid = (float) $fee->paid_amount;

                // âœ… Calculate discount (if applicable)
                $individual_discount_amount = 0.00;
                if (!empty($studentDiscount)) {
                    $individual_discount_amount = $this->calculateDiscount(
                        $fee_total - $fee_paid,
                        $studentDiscount->discount_type ?? null,
                        $studentDiscount->amount ?? 0
                    );
                }

                // âœ… Create one StudentFee per month
                $studentFee = StudentFee::create([
                    'branch_id'         => $branch_id,
                    'academic_year_id'  => $year_id,
                    'student_class_id'  => $student->student_class_id,
                    'student_id'        => $student->id,
                    'total_amount'      => $fee_total,
                    'total_discount'    => $individual_discount_amount,
                    'grand_total'       => $fee_total - $individual_discount_amount,
                    'month'             => $month,
                    'start_date'        => now()->startOfMonth(),
                    'end_date'          => now()->endOfMonth(),
                    'status'            => Status::ACTIVE,
                    'created_at'        => now(),
                    'updated_at'        => now(),
                ]);

                // âœ… Loop inside fee_type_id and class_fees_amount arrays
                $feeTypeIds = $fee->fee_type_id ?? [];
                $classFeeAmounts = $fee->class_fees_amount ?? [];

                foreach ($feeTypeIds as $index => $feeTypeId) {
                    $amount = isset($classFeeAmounts[$index]) ? (float) $classFeeAmounts[$index] : 0.00;

                    $invoiceItemData[] = [
                        'invoice_id'       => $invoice_data->id,
                        'student_id'       => $student->id,
                        'fee_type_id'      => $feeTypeId,
                        'amount'           => $amount,
                        'month'            => $month,
                        'academic_year_id' => $year_id,
                        'student_fee_id'   => $studentFee->id,
                        'payment_date'     => now()->format('Y-m-d'),
                        'created_at'       => now(),
                        'updated_at'       => now(),
                    ];
                }

                // âœ… Totals
                $total_amount   += $fee_total;
                $total_discount += $individual_discount_amount;
                $paid_amount    += $fee_paid;
            }

            // âœ… Insert all invoice items at once
            InvoiceItem::insert($invoiceItemData);

            // âœ… Update the main invoice
            $invoice_data->update([
                'total_amount'   => number_format($total_amount, 2, '.', ''),
                'total_discount' => number_format($total_discount, 2, '.', ''),
                'late_fine'      => lateFineCalculation() ?? 0.00,
                'paid_amount'    => number_format($paid_amount, 2, '.', ''),
            ]);
        }

        //generate invoice end

        return [
            'grand_total_amount' => $exist_invoice?->total_amount ?? $grand_total_amount,
            'grand_discount_amount' => $exist_invoice?->total_discount ?? $grand_discount_amount,
            'grand_due_amount' => $exist_invoice?->total_amount - $exist_invoice?->total_discount ?? $grand_due_amount,
            'invoice_no' => $exist_invoice?->invoice_no ?? $invoice_data?->invoice_no,
        ];
    }

    //payment response (post)
    public function handlePaymentNotification(Request $request)
    {
        try {
            // Validate IPN notification
            $validationResult = $this->validateIPNRequest($request);
            if ($validationResult !== '') {
                return response()->json([
                    'responseCode' => '90',
                    'responseMsg' => $validationResult
                ]);
            }
            // Process payment
            $invoice = Invoice::withoutGlobalScope(BranchFilterScope::class)->where('invoice_no', $request->invoiceNo)->first();
            if (!$invoice) {
                return response()->json([
                    'responseCode' => '90',
                    'responseMsg' => 'Invoice not found'
                ]);
            }
            // new funtion for store payment info and generate invoice



            // Update payment status
            $invoice->update([
                'status' => $request->status === 'APPROVED' ? Status::PAID : Status::FAILED,
                'payment_method' => $request->source_of_fund,
                'bank_transection_id' => $request->trId,
                'paid_amount' => $request->cr_amount,
                'payment_date' => date('Y-m-d', strtotime($request->date_time))
            ]);
            DB::table('student_fees')
                ->where('invoice_id', $invoice->id)
                ->update([
                    'status' => $request->status === 'APPROVED' ? Status::ACTIVE : Status::INACTIVE,
                ]);
            return response()->json([
                'responseCode' => '00',
                'responseMsg' => 'success'
            ]);
        } catch (\Exception $e) {
            Log::error('IBBL IPN Error: ' . $e->getMessage());
            return response()->json([
                'responseCode' => '90',
                'responseMsg' => 'Failed'
            ]);
        }
    }













    private function calculateDiscount($amount, $discount_type, $discount)
    {
        if ($discount_type == 'percentage') {
            return ($amount * $discount) / 100;
        } elseif ($discount_type == 'fixedamount') {
            return $discount;
        }
        return 0;
    }


    public function generateInvoiceNumber($student, $month)
    {
        $previousInvoice = Invoice::withoutGlobalScope(BranchFilterScope::class)
            ->where('student_id', $student->id)
            ->where('status', Status::DUE)
            ->where('academic_year_id', getDefaultAcademicYearID())
            ->orderBy('id', 'desc')
            ->first();
        if($previousInvoice){
            DB::transaction(function() use ($previousInvoice) {
                $previousInvoice->invoiceItems()->withoutGlobalScope(BranchFilterScope::class)->forceDelete();
                $previousInvoice->forceDelete();
            });
        }
        $feeDetails = $this->getStudentDueFees($student, $month);
        $invoiceData = [
            'student_id' => $student->id,
            'academic_year_id' => getDefaultAcademicYearID(),
            'branch_id' => $student?->branch_id ?? null,
            'shift_id' => $student->shift_id,
            'student_class_id' => $student->student_class_id,
            'section_id' => $student->section_id,
            'invoice_date' => date('Y-m-d'),
            'due_date' => date('Y-m-d'),
            'payment_method' => Accounting::IBBL,
            'note' => NULL,
            'created_by' => NULL,
            'status' => Status::DUE
        ];
        $invoice_data = Invoice::create($invoiceData);
        $studentDiscount = StudentDiscount::withoutGlobalScope(BranchFilterScope::class)->where('student_id', $student->id)->first();
        $invoiceItemData = [];
        $total_amount = 0;
        $total_discount = 0;
        foreach ($feeDetails as $fee) {
            $amount = $fee->due_fee;
            $month = $fee->month;
            if ($amount) {
                $discount_amount = $studentDiscount ? calculateStudentDiscount($amount, $studentDiscount->discount_type, $studentDiscount->discount) : 0;
                $invoiceItemData[] = [
                    'invoice_id' => $invoice_data->id,
                    'student_id' => $student->id,
                    'fee_type_id' => $fee->fee_type_id,
                    'amount' => $amount,
                    'month' => $month,
                    'academic_year_id' => getDefaultAcademicYearID(),
                    'discount' => $discount_amount,
                    'created_at' => now(),
                    'updated_at' => now(),
                ];
                $total_amount += $amount;
                $total_discount += $discount_amount;
            }
        }
        InvoiceItem::insert($invoiceItemData);
        $invoice_data->update([
            'total_amount' => $total_amount,
            'total_discount' => $total_discount,
            'late_fine' => lateFineCalculation(),
        ]);
        return $invoice_data;
    }

    public function getFeesSummaryByMonth($student, $month)
    {
        $year = getDefaultAcademicYearValue();
        // Ensure month is two digits
        $month = str_pad($month, 2, '0', STR_PAD_LEFT);
        // Calculate date range from start of year to end of given month
        $fromDate = "{$year}-01-01";  // Start of year
        $toDate = date('Y-m-t', strtotime("{$year}-{$month}-01")); // End of given month
        $classFeesQuery = ClassFee::withoutGlobalScope(BranchFilterScope::class)
            ->join('fee_types as ft', 'class_fees.fee_type_id', '=', 'ft.id')
            ->join('student_classes as sc', 'class_fees.student_class_id', '=', 'sc.id')
            ->join('student_infos as si', 'sc.id', '=', 'si.student_class_id')
            ->leftJoin('invoice_items as ii', function ($join) use ($fromDate, $toDate) {
                $join->on('si.id', '=', 'ii.student_id')
                    ->whereColumn('class_fees.fee_type_id', 'ii.fee_type_id')
                    ->where(function($query) use ($fromDate, $toDate) {
                        $query->whereBetween('ii.payment_date', [$fromDate, $toDate]);
                    });
            })
            ->whereNotIn('ft.code', ['Hostel', 'Transport'])
            ->where('si.student_id_no', $student->student_id_no)
            
            ->where(function($query) use ($month) {
                $query->where('ft.month', '<=', $month)
                    ->orWhereNull('ft.month');
            })
            ->select([
                DB::raw('SUM(class_fees.amount) as total_assigned_fee'),
                DB::raw('COALESCE(SUM(ii.amount), 0) as total_paid_fee'),
                DB::raw('SUM(class_fees.amount) - COALESCE(SUM(ii.amount), 0) as total_due_fee')
            ]);
        // Student-specific fees query (hostel and transport)
        $studentFeesQuery = StudentFee::join('fee_types as ft', 'student_fees.fee_type_id', '=', 'ft.id')
            ->leftJoin('invoice_items as ii', function ($join) use ($fromDate, $toDate) {
                $join->on('student_fees.student_id', '=', 'ii.student_id')
                    ->whereColumn('student_fees.fee_type_id', 'ii.fee_type_id')
                    ->where(function($query) use ($fromDate, $toDate) {
                        $query->whereBetween('ii.payment_date', [$fromDate, $toDate]);
                    });
            })
            ->whereIn('ft.code', ['Hostel', 'Transport'])
            ->where('student_fees.student_id', $student->id)
            ->where('student_fees.status', 'active')
            ->where(function($query) use ($month) {
                $query->where('ft.month', '<=', $month);
            })
            ->select([
                DB::raw('SUM(student_fees.amount) as total_assigned_fee'),
                DB::raw('COALESCE(SUM(ii.amount), 0) as total_paid_fee'),
                DB::raw('SUM(student_fees.amount) - COALESCE(SUM(ii.amount), 0) as total_due_fee')
            ]);
        // Combine both queries using UNION ALL
        $finalQuery = $classFeesQuery->unionAll($studentFeesQuery);
        // Get the total summary
        $feeSummary = DB::query()
            ->fromSub($finalQuery, 'combined_fees')
            ->select([
                DB::raw('SUM(total_assigned_fee) as total_assigned_fee'),
                DB::raw('SUM(total_paid_fee) as total_paid_fee'),
                DB::raw('SUM(total_due_fee) as total_due_fee')
            ])
            ->first();
        return $feeSummary;
    }
    public function getStudentDueFees($studentInfo, $month)
    {
        $academicYear = getDefaultAcademicYearValue();
        // Ensure month is two digits
        $month = str_pad($month, 2, '0', STR_PAD_LEFT);
        // Calculate date range from start of year to end of given month
        $fromDate = "{$academicYear}-01-01";  // Start of year
        $toDate = date('Y-m-t', strtotime("{$academicYear}-{$month}-01")); // End of given month
        // Class-wide fees query for due fees
        $classFeesQuery = ClassFee::withoutGlobalScope(BranchFilterScope::class)
            ->join('fee_types as ft', 'class_fees.fee_type_id', '=', 'ft.id')
            ->join('student_classes as sc', 'class_fees.student_class_id', '=', 'sc.id')
            ->join('student_infos as si', 'sc.id', '=', 'si.student_class_id')
            
            ->leftJoin('invoice_items as ii', function ($join) use ($fromDate, $toDate) {
                $join->on('si.id', '=', 'ii.student_id')
                    ->whereColumn('class_fees.fee_type_id', 'ii.fee_type_id')
                    ->where(function($query) use ($fromDate, $toDate) {
                        $query->whereBetween('ii.payment_date', [$fromDate, $toDate]);
                    });
            })
            ->whereNotIn('ft.code', ['Hostel', 'Transport'])
            ->where('si.student_id_no', $studentInfo->student_id_no)
            ->where(function($query) use ($month) {
                $query->where('ft.month', '<=', $month);
            })
            ->groupBy('ft.id', 'ft.title', 'ft.month', 'ft.code', 'class_fees.amount')
            ->havingRaw('class_fees.amount - COALESCE(SUM(ii.amount), 0) > 0')
            ->select([
                'ft.id as fee_type_id',
                'ft.title',
                'ft.month',
                'ft.code',
                'class_fees.amount as assigned_fee',
                DB::raw('COALESCE(SUM(ii.amount), 0) as paid_fee'),
                DB::raw('class_fees.amount - COALESCE(SUM(ii.amount), 0) as due_fee'),
            ]);
        // Student-specific fees query for due fees
        $studentFeesQuery = StudentFee::join('fee_types as ft', 'student_fees.fee_type_id', '=', 'ft.id')
            ->leftJoin('invoice_items as ii', function ($join) use ($fromDate, $toDate) {
                $join->on('student_fees.student_id', '=', 'ii.student_id')
                    ->whereColumn('student_fees.fee_type_id', 'ii.fee_type_id')
                    ->where(function($query) use ($fromDate, $toDate) {
                        $query->whereBetween('ii.payment_date', [$fromDate, $toDate]);
                    });
            })
            ->whereIn('ft.code', ['Hostel', 'Transport'])
            ->where('student_fees.student_id', $studentInfo->id)
            ->where('student_fees.status', 'active')
            ->where(function($query) use ($month) {
                $query->where('ft.month', '<=', $month);
            })
            ->groupBy('ft.id', 'ft.title', 'ft.month', 'ft.code', 'student_fees.amount')
            ->havingRaw('student_fees.amount - COALESCE(SUM(ii.amount), 0) > 0')
            ->select([
                'ft.id as fee_type_id',
                'ft.title',
                'ft.month',
                'ft.code',
                'student_fees.amount as assigned_fee',
                DB::raw('COALESCE(SUM(ii.amount), 0) as paid_fee'),
                DB::raw('student_fees.amount - COALESCE(SUM(ii.amount), 0) as due_fee'),
            ]);
        // Union the two queries
        $finalQuery = $classFeesQuery->unionAll($studentFeesQuery);
        // Execute the query and return results
        return DB::table(DB::raw("({$finalQuery->toSql()}) as fees"))
            ->mergeBindings($finalQuery->getQuery())
            ->select('*')
            ->get();
    }




    private function validateRequest($request)
    {
        //validate iid
        if ($request->iid !== $this->iid) {
            Log::warning('IID mismatch', [
                'expected' => $this->iid,
                'received' => $request->iid
            ]);
            return $this->errorResponse('Invalid IID');
        }
        // Verify userId
        if ($request->userId !== $this->userId) {
            Log::warning('User ID mismatch', [
                'expected' => $this->userId,
                'received' => $request->userId
            ]);
            return $this->errorResponse('Invalid user ID');
        }
        // Calculate hash using the stored password
        $calculatedHash = hash('sha512', $this->password . $request->random . $request->referenceId);
        // Get the password hash from the request
        $requestHash = $request->password;
        // Compare the hashes
        if ($calculatedHash !== $requestHash) {
            Log::warning('Password hash mismatch', [
                'calculated' => $calculatedHash,
                'received' => $requestHash
            ]);
            return $this->errorResponse('Invalid Credentials');
        }
        return true;
    }
    
    // response validation
    private function validateIPNRequest($request)
    {
        try {
            // 1. Validate required fields are present
            $requiredFields = [
                'userId',
                'iid',
                'invoiceNo',
                'password',
                'trId',
                'status',
                'date_time',
                'tr_amount',
                'cr_amount',
                'source_of_fund'
            ];
            foreach ($requiredFields as $field) {
                if (!$request->has($field)) {
                    Log::error("Missing required field - {$field}");
                    return "Missing required field - {$field}";
                }
            }
            // 2. Validate userId and iid match our credentials
            if ($request->userId !== $this->userId) {
                Log::error('Invalid userId');
                return 'Invalid userId';
            }
            if ($request->iid !== $this->iid) {
                Log::error('Invalid iid');
                return 'Invalid iid';
            }
            // 3. Validate password hash
            $calculatedHash = hash('sha512', $this->password . $request->trId . $request->invoiceNo);
            
            if ($calculatedHash !== $request->password) {
                Log::error('Invalid Credentials '. $calculatedHash);
                return 'Invalid Credentials. Incorrect password';
            }
            // 4. check invoice amount and paid amount
            $invoice = Invoice::withoutGlobalScope(BranchFilterScope::class)->where('invoice_no', $request->invoiceNo)->first();
            if($invoice->total_amount != $request->cr_amount){
                Log::error('Amount Not Matched');
                return 'Amount Not Matched';
            }
            // 5. Validate date_time format
            try {
                DateTime::createFromFormat('d/m/Y h:i A', $request->date_time);
            } catch (\Exception $e) {
                Log::error('Invalid date_time format');
                return 'Invalid date_time format';
            }
            // 6. Validate status value
            $validStatuses = ['APPROVED', 'FAILED', 'CANCELLED'];
            if (!in_array($request->status, $validStatuses)) {
                Log::error('Invalid status');
                return 'Invalid status';
            }
            return '';
        } catch (\Exception $e) {
            Log::error('' . $e->getMessage());
            return false;
        }
    }

    private function errorResponse($message)
    {
        return response()->json([
            'referenceId' => '',
            'dateTime' => now()->format('d/m/Y h:i A'),
            'responseCode' => '90',
            'responseMsg' => $message,
            'feeDetails' => [
                'studentId' => null,
                'instituteName' => null,
                'branchName' => null,
                'shift' => null,
                'className' => null,
                'sectionName' => null,
                'invoiceNo' => null,
                'studentName' => null,
                'fatherName' => null,
                'month' => null,
                'academicYear' => null,
                'fee' => null,
                'waiver' => null,
                'totalDue' => null
            ]
        ]);
    }
}