<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Loan;
use App\Models\EmiCard;
use App\Models\User;
use App\Models\Payment;
use App\Models\LoanInstallment;
use App\Services\LoanCalculationService;
use App\Services\NotificationService;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Throwable;

class LoanController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Loan List
    |--------------------------------------------------------------------------
    */

    public function index(Request $request)
    {
        $query = Loan::with([
            'user',
            'emiCard',
            'approver',
        ])->latest();


        /*
        |--------------------------------------------------------------------------
        | Status Filter
        |--------------------------------------------------------------------------
        */

        if ($request->filled('status')) {

            $query->where(
                'status',
                $request->status
            );
        }


        /*
        |--------------------------------------------------------------------------
        | Search
        |--------------------------------------------------------------------------
        */

        if ($request->filled('search')) {

            $search = trim(
                $request->search
            );

            $query->where(function ($q) use ($search) {

                $q->where(
                    'loan_number',
                    'like',
                    "%{$search}%"
                )

                ->orWhereHas(
                    'user',
                    function ($userQuery) use ($search) {

                        $userQuery
                            ->where(
                                'name',
                                'like',
                                "%{$search}%"
                            )
                            ->orWhere(
                                'mobile',
                                'like',
                                "%{$search}%"
                            )
                            ->orWhere(
                                'email',
                                'like',
                                "%{$search}%"
                            );
                    }
                );
            });
        }


        $loans = $query
            ->paginate(20)
            ->withQueryString();


        /*
        |--------------------------------------------------------------------------
        | Statistics
        |--------------------------------------------------------------------------
        */

        $totalLoans = Loan::count();

        $pendingLoans = Loan::where(
            'status',
            'pending'
        )->count();

        $approvedLoans = Loan::where(
            'status',
            'approved'
        )->count();

        $activeLoans = Loan::where(
            'status',
            'active'
        )->count();

        $overdueLoans = Loan::where(
            'status',
            'overdue'
        )->count();

        $completedLoans = Loan::where(
            'status',
            'completed'
        )->count();


        $totalDisbursed = Loan::whereIn(
            'status',
            [
                'disbursed',
                'active',
                'completed',
                'overdue',
            ]
        )->sum(
            'disbursed_amount'
        );


        $totalOutstanding = Loan::whereIn(
            'status',
            [
                'disbursed',
                'active',
                'overdue',
            ]
        )->sum(
            'outstanding_amount'
        );


        return view(
            'admin.loans.index',
            compact(
                'loans',
                'totalLoans',
                'pendingLoans',
                'approvedLoans',
                'activeLoans',
                'overdueLoans',
                'completedLoans',
                'totalDisbursed',
                'totalOutstanding'
            )
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Show Loan
    |--------------------------------------------------------------------------
    */

    public function show(Loan $loan)
    {
        $loan->load([
            'user.clientProfile',
            'user.clientKyc',
            'user.clientEmployment',
            'user.bankDetail',
            'emiCard',
            'approver',
            'closer',
            'installments',

        ]);


        return view(
            'admin.loans.show',
            compact('loan')
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Approve Loan
    |--------------------------------------------------------------------------
    */

    public function approve(Request $request, Loan $loan, NotificationService $notificationService)
    {
        /*
        |--------------------------------------------------------------------------
        | Already Approved
        |--------------------------------------------------------------------------
        */

        if ($loan->status !== 'pending') {

            return back()->with(
                'error',
                'Only pending loans can be approved.'
            );
        }


        /*
        |--------------------------------------------------------------------------
        | EMI Card Validation
        |--------------------------------------------------------------------------
        */

        $emiCard = $loan->emiCard;


        if (!$emiCard) {

            return back()->with(
                'error',
                'EMI card not found.'
            );
        }


        if ($emiCard->status !== 'active') {

            return back()->with(
                'error',
                'Loan cannot be approved because the EMI card is not active.'
            );
        }


        /*
        |--------------------------------------------------------------------------
        | Expiry Validation
        |--------------------------------------------------------------------------
        */

        if (
            $emiCard->expires_at &&
            Carbon::parse(
                $emiCard->expires_at
            )->isPast()
        ) {

            return back()->with(
                'error',
                'EMI card has expired.'
            );
        }


        /*
        |--------------------------------------------------------------------------
        | Available Limit
        |--------------------------------------------------------------------------
        */
$limitRequired = round(
    (float) $loan->loan_amount
    + (float) $loan->interest_amount,
    2
);

if (
    $limitRequired
    > (float) $emiCard->available_limit
) {

    return back()->with(
        'error',
        'Loan amount including interest exceeds the available EMI card limit.'
    );
}


        /*
        |--------------------------------------------------------------------------
        | Approve
        |--------------------------------------------------------------------------
        */

        DB::transaction(function () use ($loan) {

            /*
            |--------------------------------------------------------------------------
            | Lock Loan Row
            |--------------------------------------------------------------------------
            | Prevent two admins from approving the same pending loan at
            | the same time.
            */

            $lockedLoan = Loan::where(
                'id',
                $loan->id
            )
                ->lockForUpdate()
                ->firstOrFail();

            if ($lockedLoan->status !== 'pending') {
                throw new \RuntimeException(
                    'This loan is no longer pending.'
                );
            }

            $lockedLoan->update([

                'status' =>
                    'approved',

                'approved_by' =>
                    auth()->id(),

                'approved_at' =>
                    now(),

            ]);
        });


        $loan->refresh();
        $notificationService->loanApproved($loan);
        $notificationService->processingFeePending($loan);

        return back()->with(
            'success',
            'Loan approved successfully.'
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Reject Loan
    |--------------------------------------------------------------------------
    */

    public function reject(
        Request $request,
        Loan $loan,
        NotificationService $notificationService
    ) {

        $validated = $request->validate([

            'rejection_reason' => [
                'required',
                'string',
                'max:2000',
            ],

        ]);


        if (
            !in_array(
                $loan->status,
                [
                    'pending',
                    'approved',
                ],
                true
            )
        ) {

            return back()->with(
                'error',
                'This loan cannot be rejected at its current stage.'
            );
        }


        DB::transaction(function () use (
            $loan,
            $validated
        ) {

            $loan->update([

                'status' =>
                    'rejected',

                'rejection_reason' =>
                    $validated[
                        'rejection_reason'
                    ],

                'approved_by' =>
                    auth()->id(),

                'approved_at' =>
                    now(),

            ]);
        });


        $loan->refresh();
        $notificationService->loanRejected($loan);

        return back()->with(
            'success',
            'Loan rejected successfully.'
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Pay Processing Fee (Admin Manual)
    |--------------------------------------------------------------------------
    */

    public function payProcessingFee(
        Request $request,
        Loan $loan,
        NotificationService $notificationService
    ) {
        if ($loan->status !== 'approved') {
            return back()->with(
                'error',
                'Processing fee can be recorded only after the loan is approved.'
            );
        }

        if ($loan->processing_fee_paid_at) {
            return back()->with(
                'error',
                'Processing fee is already paid.'
            );
        }

        $amount = round((float) $loan->processing_fee, 2);

        if ($amount <= 0) {
            $loan->update([
                'processing_fee_paid_at' => now(),
                'processing_fee_paid_by' => auth()->id(),
            ]);

            return back()->with(
                'success',
                'No processing fee is due for this loan.'
            );
        }

        $validated = $request->validate([
            'payment_method' => ['required', 'in:cash,upi,bank_transfer'],
            'reference_number' => ['nullable', 'string', 'max:255'],
            'remarks' => ['nullable', 'string', 'max:2000'],
        ]);

        try {
            DB::transaction(function () use ($loan, $validated, $amount) {
                $lockedLoan = Loan::where('id', $loan->id)
                    ->lockForUpdate()
                    ->firstOrFail();

                if ($lockedLoan->status !== 'approved') {
                    throw new \RuntimeException(
                        'Processing fee can be recorded only after the loan is approved.'
                    );
                }

                if ($lockedLoan->processing_fee_paid_at) {
                    throw new \RuntimeException('Processing fee is already paid.');
                }

                $orderId = 'PF-' . now()->format('YmdHis') . '-' . random_int(100, 999);
                while (Payment::where('order_id', $orderId)->exists()) {
                    $orderId = 'PF-' . now()->format('YmdHis') . '-' . random_int(100, 999);
                }

                Payment::create([
                    'user_id' => $lockedLoan->user_id,
                    'order_id' => $orderId,
                    'payment_session_id' => null,
                    'amount' => $amount,
                    'currency' => 'INR',
                    'status' => 'paid',
                    'payment_id' => $validated['reference_number'] ?? $orderId,
                    'paid_at' => now(),
                    'payment_method' => $validated['payment_method'],
                    'reference_number' => $validated['reference_number'] ?? null,
                    'remarks' => $validated['remarks'] ?? 'Processing fee recorded manually by admin.',
                    'is_manual' => true,
                    'recorded_by' => auth()->id(),
                    'loan_id' => $lockedLoan->id,
                    'installment_id' => null,
                    'payment_type' => 'processing_fee',
                ]);

                $lockedLoan->update([
                    'processing_fee_paid_at' => now(),
                    'processing_fee_paid_by' => auth()->id(),
                ]);
            });
        } catch (Throwable $e) {
            report($e);

            return back()->with(
                'error',
                $e instanceof \RuntimeException
                    ? $e->getMessage()
                    : 'Processing fee payment failed. Please try again.'
            );
        }

        $loan->refresh();
        $notificationService->processingFeePaid($loan);

        return back()->with(
            'success',
            'Processing fee recorded successfully.'
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Disburse Loan
    |--------------------------------------------------------------------------
    */

    public function disburse(
        Request $request,
        Loan $loan,
        LoanCalculationService $calculator,
        NotificationService $notificationService
    ) {

        if ($loan->status !== 'approved') {

            return back()->with(
                'error',
                'Only approved loans can be disbursed.'
            );
        }


        $emiCard = $loan->emiCard;


        if (!$emiCard) {

            return back()->with(
                'error',
                'EMI card not found.'
            );
        }


        if ($emiCard->status !== 'active') {

            return back()->with(
                'error',
                'EMI card is not active.'
            );
        }


        /*
        |--------------------------------------------------------------------------
        | Transaction
        |--------------------------------------------------------------------------
        */

        try {

            DB::transaction(function () use (
                $loan,
                $calculator
            ) {

                /*
                |--------------------------------------------------------------------------
                | Lock Loan Row
                |--------------------------------------------------------------------------
                | This prevents double-disbursement if two admin requests
                | arrive at nearly the same time.
                */

                $loan = Loan::where(
                    'id',
                    $loan->id
                )
                    ->lockForUpdate()
                    ->firstOrFail();

                if ($loan->status !== 'approved') {
                    throw new \RuntimeException(
                        'Only approved loans can be disbursed.'
                    );
                }

                if ((float) $loan->processing_fee > 0 && !$loan->processing_fee_paid_at) {
                    throw new \RuntimeException(
                        'Processing fee must be paid before the loan can be disbursed.'
                    );
                }

                /*
                |--------------------------------------------------------------------------
                | Lock EMI Card
                |--------------------------------------------------------------------------
                */

                $emiCard = EmiCard::where(
                    'id',
                    $loan->emi_card_id
                )
                    ->lockForUpdate()
                    ->firstOrFail();


                /*
                |--------------------------------------------------------------------------
                | Re-check Available Limit
                |--------------------------------------------------------------------------
                */

                /*
                |--------------------------------------------------------------------------
                | Re-check Card Status / Expiry Inside Transaction
                |--------------------------------------------------------------------------
                */

                if ($emiCard->status !== 'active') {
                    throw new \RuntimeException(
                        'EMI card is not active.'
                    );
                }

                if (
                    $emiCard->expires_at &&
                    Carbon::parse(
                        $emiCard->expires_at
                    )->isPast()
                ) {
                    throw new \RuntimeException(
                        'EMI card has expired.'
                    );
                }

                /*
                |--------------------------------------------------------------------------
                | Re-check Available Limit
                |--------------------------------------------------------------------------
                */
$limitRequired = round(
    (float) $loan->loan_amount
    + (float) $loan->interest_amount,
    2
);

if (
    $limitRequired
    > (float) $emiCard->available_limit
) {
    throw new \RuntimeException(
        'Insufficient EMI card limit for loan amount including interest.'
    );
}


                /*
                |--------------------------------------------------------------------------
                | Reduce Available Limit
                |--------------------------------------------------------------------------
                */
$emiCard->available_limit =
    round(
        (float) $emiCard->available_limit
        - $limitRequired,
        2
    );

$emiCard->save();

                /*
                |--------------------------------------------------------------------------
                | Generate EMI Schedule
                |--------------------------------------------------------------------------
                */

                $firstEmiDate =
                    Carbon::today()->addMonth();


                $schedule =
                    $calculator
                        ->generateScheduleFromLoan(
                            $loan,
                            $firstEmiDate
                        );


                /*
                |--------------------------------------------------------------------------
                | Delete Existing Schedule
                |--------------------------------------------------------------------------
                */

                $loan->installments()->delete();


                /*
                |--------------------------------------------------------------------------
                | Create Installments
                |--------------------------------------------------------------------------
                */

                foreach (
                    $schedule
                    as $installment
                ) {

                    $loan->installments()->create(
                        $installment
                    );
                }


                /*
                |--------------------------------------------------------------------------
                | Update Loan
                |--------------------------------------------------------------------------
                */

                $loan->update([

                    'status' =>
                        'active',

                    'disbursed_amount' =>
                        $loan->loan_amount,

                    'outstanding_amount' =>
                        $loan->total_payable,

                    'disbursed_at' =>
                        now(),

                    'first_emi_date' =>
                        $firstEmiDate,

                ]);
            });


        } catch (Throwable $e) {

            report($e);

            return back()->with(
                'error',
                'Loan disbursement failed. Please review the loan and EMI card details and try again.'
            );
        }


        $loan->refresh();
        $notificationService->loanDisbursed($loan);

        return back()->with(
            'success',
            'Loan disbursed and EMI schedule generated successfully.'
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Pay EMI Installment
    |--------------------------------------------------------------------------
    */

    public function payInstallment(
        Request $request,
        Loan $loan,
        LoanInstallment $installment,
        NotificationService $notificationService
    ) {
        $validated = $request->validate([
            'amount' => ['required', 'numeric', 'min:0.01'],
            'payment_method' => ['required', 'in:cash,upi,bank_transfer'],
            'reference_number' => ['nullable', 'string', 'max:255'],
            'remarks' => ['nullable', 'string', 'max:2000'],
        ]);

        try {
            DB::transaction(function () use ($loan, $installment, $validated) {

                $lockedLoan = Loan::where('id', $loan->id)
                    ->lockForUpdate()
                    ->firstOrFail();

                $lockedInstallment = LoanInstallment::where('id', $installment->id)
                    ->where('loan_id', $lockedLoan->id)
                    ->lockForUpdate()
                    ->firstOrFail();

                if (!in_array($lockedLoan->status, ['active', 'disbursed', 'overdue'], true)) {
                    throw new \RuntimeException(
                        'Only active or overdue loans can receive EMI payments.'
                    );
                }

                if ($lockedInstallment->status === 'cancelled') {
                    throw new \RuntimeException('This EMI installment is cancelled.');
                }

                $remaining = max(
                    0,
                    round(
                        (float) $lockedInstallment->emi_amount
                        - (float) $lockedInstallment->paid_amount,
                        2
                    )
                );

                $amount = round((float) $validated['amount'], 2);

                if ($remaining <= 0) {
                    throw new \RuntimeException('This EMI installment is already fully paid.');
                }

                if ($amount > $remaining) {
                    throw new \RuntimeException(
                        'Payment cannot exceed the remaining EMI amount of ₹'
                        . number_format($remaining, 2)
                        . '.'
                    );
                }

                $newPaid = round(
                    (float) $lockedInstallment->paid_amount + $amount,
                    2
                );

                $newOutstanding = max(
                    0,
                    round(
                        (float) $lockedInstallment->emi_amount - $newPaid,
                        2
                    )
                );

                $newStatus = $newOutstanding <= 0 ? 'paid' : 'partial';

                $orderId = 'EMI-' . now()->format('YmdHis') . '-' . random_int(100, 999);

                while (Payment::where('order_id', $orderId)->exists()) {
                    $orderId = 'EMI-' . now()->format('YmdHis') . '-' . random_int(100, 999);
                }

                Payment::create([
                    'user_id' => $lockedLoan->user_id,
                    'order_id' => $orderId,
                    'payment_session_id' => null,
                    'amount' => $amount,
                    'currency' => 'INR',
                    'status' => 'paid',
                    'payment_id' => $validated['reference_number'] ?? $orderId,
                    'paid_at' => now(),
                    'payment_method' => $validated['payment_method'],
                    'reference_number' => $validated['reference_number'] ?? null,
                    'remarks' => $validated['remarks'] ?? null,
                    'is_manual' => true,
                    'recorded_by' => auth()->id(),
                    'loan_id' => $lockedLoan->id,
                    'installment_id' => $lockedInstallment->id,
                    'payment_type' => 'emi',
                ]);

                $lockedInstallment->update([
                    'paid_amount' => $newPaid,
                    'outstanding_amount' => $newOutstanding,
                    'paid_date' => $newStatus === 'paid' ? today() : null,
                    'status' => $newStatus,
                    'payment_reference' => $validated['reference_number'] ?? $orderId,
                ]);

                /*
                |--------------------------------------------------------------------------
                | Restore EMI Card Available Limit
                |--------------------------------------------------------------------------
                |
                | Disbursement consumed loan_amount + interest_amount.
                | Every EMI payment restores the actual EMI payment amount.
                |
                */

                $emiCard = EmiCard::where(
                    'id',
                    $lockedLoan->emi_card_id
                )
                    ->lockForUpdate()
                    ->first();

                if (!$emiCard) {
                    throw new \RuntimeException(
                        'EMI card linked to this loan was not found.'
                    );
                }

                $currentAvailable = round(
                    (float) $emiCard->available_limit,
                    2
                );

                $cardLimit = round(
                    (float) $emiCard->card_limit,
                    2
                );

                $newAvailable = min(
                    $cardLimit,
                    round($currentAvailable + $amount, 2)
                );

                $emiCard->update([
                    'available_limit' => max(0, $newAvailable),
                ]);

                $remainingLoan = (float) $lockedLoan->installments()
                    ->where('outstanding_amount', '>', 0)
                    ->sum('outstanding_amount');

                $remainingLoan = round(max(0, $remainingLoan), 2);

                $allPaid = $remainingLoan <= 0;

                $lockedLoan->update([
                    'outstanding_amount' => $remainingLoan,
                    'status' => $allPaid ? 'completed' : (
                        $lockedLoan->status === 'overdue' ? 'overdue' : 'active'
                    ),
                    'completed_at' => $allPaid ? today() : null,
                ]);
            });
        } catch (Throwable $e) {
            report($e);

            return back()->with(
                'error',
                $e instanceof \RuntimeException
                    ? $e->getMessage()
                    : 'EMI payment failed. Please try again.'
            );
        }

        $loan->refresh();

        if ($loan->status === 'completed') {
            $notificationService->loanCompleted($loan);
        } else {
            $notificationService->emiPaid($loan);
        }

        return back()->with('success', 'EMI payment recorded successfully.');
    }


    /*
|--------------------------------------------------------------------------
| Pay Overdue EMI + Fine
|--------------------------------------------------------------------------
*/

public function payOverdueInstallment(
    Request $request,
    Loan $loan,
    LoanInstallment $installment,
    NotificationService $notificationService
) {
    $validated = $request->validate([
        'amount' => ['required', 'numeric', 'min:0.01'],
        'payment_method' => ['required', 'in:cash,upi,bank_transfer'],
        'reference_number' => ['nullable', 'string', 'max:255'],
        'remarks' => ['nullable', 'string', 'max:2000'],
    ]);

    try {

        DB::transaction(function () use (
            $loan,
            $installment,
            $validated
        ) {

            $lockedLoan = Loan::where('id', $loan->id)
                ->lockForUpdate()
                ->firstOrFail();

            $lockedInstallment = LoanInstallment::where('id', $installment->id)
                ->where('loan_id', $lockedLoan->id)
                ->lockForUpdate()
                ->firstOrFail();

            if (!in_array(
                $lockedLoan->status,
                ['active', 'disbursed', 'overdue'],
                true
            )) {
                throw new \RuntimeException(
                    'Only active or overdue loans can receive EMI payments.'
                );
            }

            if ($lockedInstallment->status === 'cancelled') {
                throw new \RuntimeException(
                    'This EMI installment is cancelled.'
                );
            }

            /*
            |--------------------------------------------------------------------------
            | Remaining EMI
            |--------------------------------------------------------------------------
            */

            $remaining = max(
                0,
                round(
                    (float) $lockedInstallment->emi_amount
                    - (float) $lockedInstallment->paid_amount,
                    2
                )
            );

            if ($remaining <= 0) {
                throw new \RuntimeException(
                    'This EMI installment is already fully paid.'
                );
            }

            /*
            |--------------------------------------------------------------------------
            | Overdue Settings
            |--------------------------------------------------------------------------
            */

            $settings = \App\Models\LoanSetting::first();

            $fineType = $settings->overdue_fine_type ?? 'fixed';

            $fineAmount = (float) (
                $settings->overdue_fine_amount ?? 0
            );

            $graceDays = (int) (
                $settings->overdue_grace_days ?? 0
            );

            $maxFine = (float) (
                $settings->overdue_max_fine ?? 0
            );

            /*
            |--------------------------------------------------------------------------
            | Calculate Overdue Days
            |--------------------------------------------------------------------------
            */

            $today = today();

            $dueDate = Carbon::parse(
                $lockedInstallment->due_date
            );

            $calendarDaysOverdue = max(
                0,
                $dueDate->diffInDays($today)
            );

            $chargeableDays = max(
                0,
                $calendarDaysOverdue - $graceDays
            );

            /*
            |--------------------------------------------------------------------------
            | Calculate Fine
            |--------------------------------------------------------------------------
            */

            $fine = 0;

            if ($chargeableDays > 0) {

                if ($fineType === 'percentage') {

                    $fine = round(
                        $remaining * ($fineAmount / 100),
                        2
                    );

                } else {

                    $fine = round(
                        $fineAmount,
                        2
                    );
                }
            }

            /*
            |--------------------------------------------------------------------------
            | Maximum Fine
            |--------------------------------------------------------------------------
            */

            if ($maxFine > 0) {

                $fine = min(
                    $fine,
                    $maxFine
                );
            }

            $fine = round(
                max(0, $fine),
                2
            );

            /*
            |--------------------------------------------------------------------------
            | Total Payable
            |--------------------------------------------------------------------------
            */

            $totalPayable = round(
                $remaining + $fine,
                2
            );

            /*
            |--------------------------------------------------------------------------
            | Payment Amount
            |--------------------------------------------------------------------------
            */

            $amount = round(
                (float) $validated['amount'],
                2
            );

            if ($amount > $totalPayable) {

                throw new \RuntimeException(
                    'Payment cannot exceed the total payable amount of '
                    . money($totalPayable)
                    . '.'
                );
            }

            /*
            |--------------------------------------------------------------------------
            | EMI Amount
            |--------------------------------------------------------------------------
            |
            | First use payment amount for EMI.
            | Remaining amount is treated as fine.
            |
            */

            $emiPayment = min(
                $amount,
                $remaining
            );

            $finePayment = max(
                0,
                round(
                    $amount - $emiPayment,
                    2
                )
            );

            if ($finePayment > $fine) {

                throw new \RuntimeException(
                    'Fine payment cannot exceed the calculated overdue fine.'
                );
            }

            /*
            |--------------------------------------------------------------------------
            | EMI Payment Record
            |--------------------------------------------------------------------------
            */

            if ($emiPayment > 0) {

                $orderId = 'EMI-'
                    . now()->format('YmdHis')
                    . '-'
                    . random_int(100, 999);

                while (
                    Payment::where(
                        'order_id',
                        $orderId
                    )->exists()
                ) {

                    $orderId = 'EMI-'
                        . now()->format('YmdHis')
                        . '-'
                        . random_int(100, 999);
                }

                Payment::create([
                    'user_id' => $lockedLoan->user_id,

                    'order_id' => $orderId,

                    'payment_session_id' => null,

                    'amount' => $emiPayment,

                    'currency' => 'INR',

                    'status' => 'paid',

                    'payment_id' =>
                        $validated['reference_number']
                        ?? $orderId,

                    'paid_at' => now(),

                    'payment_method' =>
                        $validated['payment_method'],

                    'reference_number' =>
                        $validated['reference_number']
                        ?? null,

                    'remarks' =>
                        $validated['remarks']
                        ?? null,

                    'is_manual' => true,

                    'recorded_by' => auth()->id(),

                    'loan_id' => $lockedLoan->id,

                    'installment_id' =>
                        $lockedInstallment->id,

                    'payment_type' => 'emi',
                ]);
            }

            /*
            |--------------------------------------------------------------------------
            | Fine Payment Record
            |--------------------------------------------------------------------------
            */

            if ($finePayment > 0) {

                $orderId = 'FINE-'
                    . now()->format('YmdHis')
                    . '-'
                    . random_int(100, 999);

                while (
                    Payment::where(
                        'order_id',
                        $orderId
                    )->exists()
                ) {

                    $orderId = 'FINE-'
                        . now()->format('YmdHis')
                        . '-'
                        . random_int(100, 999);
                }

                Payment::create([
                    'user_id' => $lockedLoan->user_id,

                    'order_id' => $orderId,

                    'payment_session_id' => null,

                    'amount' => $finePayment,

                    'currency' => 'INR',

                    'status' => 'paid',

                    'payment_id' =>
                        $validated['reference_number']
                        ?? $orderId,

                    'paid_at' => now(),

                    'payment_method' =>
                        $validated['payment_method'],

                    'reference_number' =>
                        $validated['reference_number']
                        ?? null,

                    'remarks' =>
                        'Overdue fine'
                        . (
                            !empty($validated['remarks'])
                                ? ' - ' . $validated['remarks']
                                : ''
                        ),

                    'is_manual' => true,

                    'recorded_by' => auth()->id(),

                    'loan_id' => $lockedLoan->id,

                    'installment_id' =>
                        $lockedInstallment->id,

                    'payment_type' => 'overdue_fine',
                ]);
            }

            /*
            |--------------------------------------------------------------------------
            | Update Installment
            |--------------------------------------------------------------------------
            */

            $newPaid = round(
                (float) $lockedInstallment->paid_amount
                + $emiPayment,
                2
            );

            $newOutstanding = max(
                0,
                round(
                    (float) $lockedInstallment->emi_amount
                    - $newPaid,
                    2
                )
            );

            $newStatus =
                $newOutstanding <= 0
                    ? 'paid'
                    : 'partial';

            $lockedInstallment->update([
                'paid_amount' => $newPaid,

                'outstanding_amount' =>
                    $newOutstanding,

                'paid_date' =>
                    $newStatus === 'paid'
                        ? today()
                        : null,

                'status' => $newStatus,

                'payment_reference' =>
                    $validated['reference_number']
                    ?? null,
            ]);

            /*
            |--------------------------------------------------------------------------
            | Restore EMI Card Available Limit
            |--------------------------------------------------------------------------
            |
            | Only EMI payment restores the card limit.
            | Overdue fine is separate and does not consume card limit.
            |
            */

            if ($emiPayment > 0) {

                $emiCard = EmiCard::where(
                    'id',
                    $lockedLoan->emi_card_id
                )
                    ->lockForUpdate()
                    ->first();

                if (!$emiCard) {
                    throw new \RuntimeException(
                        'EMI card linked to this loan was not found.'
                    );
                }

                $currentAvailable = round(
                    (float) $emiCard->available_limit,
                    2
                );

                $cardLimit = round(
                    (float) $emiCard->card_limit,
                    2
                );

                $newAvailable = min(
                    $cardLimit,
                    round($currentAvailable + $emiPayment, 2)
                );

                $emiCard->update([
                    'available_limit' => max(0, $newAvailable),
                ]);
            }

            /*
            |--------------------------------------------------------------------------
            | Update Loan Outstanding
            |--------------------------------------------------------------------------
            */

            $remainingLoan = (float) $lockedLoan
                ->installments()
                ->where(
                    'outstanding_amount',
                    '>',
                    0
                )
                ->sum('outstanding_amount');

            $remainingLoan = round(
                max(0, $remainingLoan),
                2
            );

            $allPaid =
                $remainingLoan <= 0;

            $lockedLoan->update([
                'outstanding_amount' =>
                    $remainingLoan,

                'status' =>
                    $allPaid
                        ? 'completed'
                        : (
                            $lockedLoan->status === 'overdue'
                                ? 'overdue'
                                : 'active'
                        ),

                'completed_at' =>
                    $allPaid
                        ? today()
                        : null,
            ]);
        });

    } catch (Throwable $e) {

        report($e);

        return back()->with(
            'error',
            $e instanceof \RuntimeException
                ? $e->getMessage()
                : 'Overdue EMI payment failed. Please try again.'
        );
    }

    $loan->refresh();

    if ($loan->status === 'completed') {

        $notificationService->loanCompleted($loan);

    } else {

        $notificationService->emiPaid($loan);
    }

    return back()->with(
        'success',
        'Overdue EMI payment recorded successfully.'
    );
}


    /*
    |--------------------------------------------------------------------------
    | Pay Full Outstanding Balance
    |--------------------------------------------------------------------------
    */

    public function payOutstanding(
        Request $request,
        Loan $loan,
        NotificationService $notificationService
    ) {
        $validated = $request->validate([
            'payment_method' => ['required', 'in:cash,upi,bank_transfer'],
            'reference_number' => ['nullable', 'string', 'max:255'],
            'remarks' => ['nullable', 'string', 'max:2000'],
        ]);

        try {
            DB::transaction(function () use ($loan, $validated) {

                $lockedLoan = Loan::where('id', $loan->id)
                    ->lockForUpdate()
                    ->firstOrFail();

                if (!in_array($lockedLoan->status, ['active', 'disbursed', 'overdue'], true)) {
                    throw new \RuntimeException(
                        'Only active or overdue loans can receive outstanding payments.'
                    );
                }

                $installments = LoanInstallment::where('loan_id', $lockedLoan->id)
                    ->whereIn('status', ['pending', 'partial', 'overdue'])
                    ->orderBy('installment_number')
                    ->lockForUpdate()
                    ->get();

                $outstanding = round(
                    (float) $installments->sum(
                        fn ($emi) => max(
                            0,
                            (float) $emi->emi_amount - (float) $emi->paid_amount
                        )
                    ),
                    2
                );

                if ($outstanding <= 0) {
                    throw new \RuntimeException('This loan has no outstanding amount.');
                }

                $orderId = 'LOAN-' . now()->format('YmdHis') . '-' . random_int(100, 999);

                while (Payment::where('order_id', $orderId)->exists()) {
                    $orderId = 'LOAN-' . now()->format('YmdHis') . '-' . random_int(100, 999);
                }

                Payment::create([
                    'user_id' => $lockedLoan->user_id,
                    'order_id' => $orderId,
                    'payment_session_id' => null,
                    'amount' => $outstanding,
                    'currency' => 'INR',
                    'status' => 'paid',
                    'payment_id' => $validated['reference_number'] ?? $orderId,
                    'paid_at' => now(),
                    'payment_method' => $validated['payment_method'],
                    'reference_number' => $validated['reference_number'] ?? null,
                    'remarks' => $validated['remarks'] ?? 'Outstanding balance paid by admin.',
                    'is_manual' => true,
                    'recorded_by' => auth()->id(),
                    'loan_id' => $lockedLoan->id,
                    'installment_id' => null,
                    'payment_type' => 'emi',
                ]);

                foreach ($installments as $emi) {
                    $emi->update([
                        'paid_amount' => $emi->emi_amount,
                        'outstanding_amount' => 0,
                        'paid_date' => today(),
                        'status' => 'paid',
                        'payment_reference' => $validated['reference_number'] ?? $orderId,
                    ]);
                }

                /*
                |--------------------------------------------------------------------------
                | Restore EMI Card Available Limit
                |--------------------------------------------------------------------------
                |
                | Disbursement consumed loan_amount + interest_amount.
                | This payment clears the remaining EMI balance, so restore
                | exactly that remaining EMI amount.
                |
                */

                $emiCard = EmiCard::where(
                    'id',
                    $lockedLoan->emi_card_id
                )
                    ->lockForUpdate()
                    ->first();

                if (!$emiCard) {
                    throw new \RuntimeException(
                        'EMI card linked to this loan was not found.'
                    );
                }

                $currentAvailable = round((float) $emiCard->available_limit, 2);
                $cardLimit = round((float) $emiCard->card_limit, 2);

                $newAvailableLimit = min(
                    $cardLimit,
                    round($currentAvailable + $outstanding, 2)
                );

                $emiCard->update([
                    'available_limit' => max(0, $newAvailableLimit),
                ]);

                $lockedLoan->update([
                    'outstanding_amount' => 0,
                    'status' => 'completed',
                    'completed_at' => today(),
                ]);
            });
        } catch (Throwable $e) {
            report($e);

            return back()->with(
                'error',
                $e instanceof \RuntimeException
                    ? $e->getMessage()
                    : 'Outstanding payment failed. Please try again.'
            );
        }

        $loan->refresh();
        $notificationService->loanCompleted($loan);

        return back()->with(
            'success',
            'Outstanding balance paid successfully. You can now close the loan.'
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Close Loan
    |--------------------------------------------------------------------------
    |
    | Admin can close a loan from any status except an already closed loan.
    | Financial history and EMI/payment records are preserved.
    | If an outstanding amount exists, explicit confirmation and a reason
    | are required.
    |
    */

    public function close(
        Request $request,
        Loan $loan
    ) {
        if ($loan->status === 'closed') {
            return back()->with(
                'error',
                'This loan is already closed.'
            );
        }

        $validated = $request->validate([
            'close_reason' => ['required', 'string', 'max:2000'],
            'confirm_close' => ['accepted'],
        ]);

        try {
            DB::transaction(function () use ($loan, $validated) {

                $lockedLoan = Loan::where('id', $loan->id)
                    ->lockForUpdate()
                    ->firstOrFail();

                if ($lockedLoan->status === 'closed') {
                    throw new \RuntimeException('This loan is already closed.');
                }

                $outstanding = round((float) $lockedLoan->outstanding_amount, 2);

                if ($outstanding > 0) {
                    throw new \RuntimeException(
                        'Loan cannot be closed while an outstanding amount of '
                        . money($outstanding)
                        . ' remains. Please clear the outstanding balance first.'
                    );
                }

                /*
                |--------------------------------------------------------------------------
                | IMPORTANT: Do NOT restore EMI card limit here.
                |--------------------------------------------------------------------------
                |
                | Disbursement consumes loan_amount + interest_amount.
                | EMI payments restore the actual paid amount, and full outstanding
                | payment restores the final remaining amount. Restoring again here
                | would double-credit the card limit.
                |
                */

                $lockedLoan->update([
                    'status' => 'closed',
                    'closed_at' => now(),
                    'closed_by' => auth()->id(),
                    'close_reason' => $validated['close_reason'],
                ]);
            });
        } catch (Throwable $e) {
            report($e);

            return back()->with(
                'error',
                $e instanceof \RuntimeException
                    ? $e->getMessage()
                    : 'Unable to close this loan. Please try again.'
            );
        }

        return back()->with(
            'success',
            'Loan closed successfully.'
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Cancel Loan
    |--------------------------------------------------------------------------
    */

    public function cancel(
        Request $request,
        Loan $loan
    ) {

        if (
            !in_array(
                $loan->status,
                [
                    'pending',
                    'approved',
                ],
                true
            )
        ) {

            return back()->with(
                'error',
                'This loan cannot be cancelled at this stage.'
            );
        }


        DB::transaction(function () use ($loan) {

            $loan->update([

                'status' =>
                    'cancelled',

            ]);
        });


        return back()->with(
            'success',
            'Loan cancelled successfully.'
        );
    }


    /*
    |--------------------------------------------------------------------------
    | Mark Overdue
    |--------------------------------------------------------------------------
    |
    | This can later be moved to a scheduled command.
    |
    */

    public function updateOverdueStatus(
        NotificationService $notificationService
    )
    {
        $updated = 0;


        Loan::whereIn(
            'status',
            [
                'active',
            ]
        )
        ->with([
            'installments',
        ])
        ->chunkById(
            100,
            function ($loans) use (
                &$updated
            ) {

                foreach ($loans as $loan) {

                    $hasOverdue =
                        $loan
                            ->installments()
                            ->where(
                                'status',
                                'pending'
                            )
                            ->whereDate(
                                'due_date',
                                '<',
                                today()
                            )
                            ->exists();


                    if ($hasOverdue) {

                        $loan->update([

                            'status' =>
                                'overdue',

                        ]);

                        $loan
                            ->installments()
                            ->where(
                                'status',
                                'pending'
                            )
                            ->whereDate(
                                'due_date',
                                '<',
                                today()
                            )
                            ->update([

                                'status' =>
                                    'overdue',

                            ]);

                        $loan->refresh();
                        $notificationService->loanOverdue($loan);

                        $updated++;
                    }
                }
            }
        );


        return back()->with(
            'success',
            "{$updated} loan(s) marked as overdue."
        );
    }
}