<?php

namespace App\Http\Controllers\Tenant;

use App\Http\Controllers\Controller;
use App\Models\Loan;
use App\Models\LoanPayment;
use App\Models\LoanRepaymentSchedule;
use App\Models\User;
use App\Services\LoanPaymentService;
use App\Services\TransactionCancellationService;
use App\Services\TransactionApprovalSettings;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use InvalidArgumentException;

class LoanPaymentController extends Controller
{
    public function index(Request $request): View
    {
        $search = trim((string) $request->query('search', ''));
        $receiptSearch = trim((string) $request->query('receipt_search', ''));
        $receiptMethod = trim((string) $request->query('receipt_method', ''));
        $receiptFrom = $request->query('receipt_from');
        $receiptTo = $request->query('receipt_to');
        $receiptInitiatedBy = $request->integer('receipt_initiated_by') ?: null;
        $receiptApprovedBy = $request->integer('receipt_approved_by') ?: null;
        $user = $request->user();
        $staffRoles = [User::ROLE_TENANT_ADMIN, User::ROLE_MANAGER, User::ROLE_LOAN_OFFICER, User::ROLE_ACCOUNTANT];
        $approvalRequired = app(TransactionApprovalSettings::class)->requiresApproval($user->tenant, 'repayment');

        $baseLoansQuery = fn (?callable $scheduleScope = null) => Loan::with([
            'customer',
            'schedules' => function ($query) use ($scheduleScope): void {
                $query->whereIn('status', ['pending', 'partial', 'overdue']);

                if ($scheduleScope) {
                    $scheduleScope($query);
                }

                $query->orderBy('due_date');
            },
        ])
            ->whereHas('customer', fn ($query) => $query->visibleTo($user))
            ->whereIn('status', ['active', 'disbursed'])
            ->where('balance', '>', 0);

        $applySearch = function ($query) use ($search) {
            if ($search === '') {
                return $query;
            }

            return $query->where(function ($nested) use ($search): void {
                $nested->where('loan_number', 'like', "%{$search}%")
                    ->orWhereHas('customer', function ($customerQuery) use ($search): void {
                        $customerQuery->where('account_number', 'like', "%{$search}%")
                            ->orWhere('first_name', 'like', "%{$search}%")
                            ->orWhere('last_name', 'like', "%{$search}%")
                            ->orWhere('phone', 'like', "%{$search}%");
                    });
            });
        };

        $loans = $applySearch($baseLoansQuery())
            ->orderBy('due_date')
            ->paginate(12)
            ->withQueryString();

        $dueTodayLoans = $applySearch($baseLoansQuery(fn ($schedule) => $schedule->whereDate('due_date', today())))
            ->whereHas('schedules', fn ($schedule) => $schedule
                ->whereIn('status', ['pending', 'partial', 'overdue'])
                ->whereDate('due_date', today()))
            ->orderBy('due_date')
            ->limit(50)
            ->get();

        $overdueLoans = $applySearch($baseLoansQuery(fn ($schedule) => $schedule->whereDate('due_date', '<', today())))
            ->whereHas('schedules', fn ($schedule) => $schedule
                ->whereIn('status', ['pending', 'partial', 'overdue'])
                ->whereDate('due_date', '<', today()))
            ->orderBy('due_date')
            ->limit(50)
            ->get();

        $upcomingLoans = $applySearch($baseLoansQuery(fn ($schedule) => $schedule->whereDate('due_date', '>', today())))
            ->whereHas('schedules', fn ($schedule) => $schedule
                ->whereIn('status', ['pending', 'partial'])
                ->whereDate('due_date', '>', today()))
            ->orderBy('due_date')
            ->limit(50)
            ->get();

        $basePaymentQuery = fn () => LoanPayment::with(['loan.customer', 'receiver', 'approver', 'rejecter', 'canceler'])
            ->whereHas('customer', fn ($query) => $query->visibleTo($user));

        $baseScheduleQuery = fn () => LoanRepaymentSchedule::query()
            ->whereIn('status', ['pending', 'partial', 'overdue'])
            ->whereHas('loan', function ($loan) use ($user): void {
                $loan->whereIn('status', ['active', 'disbursed'])
                    ->whereHas('customer', fn ($customer) => $customer->visibleTo($user));
            });

        $scheduleAmount = fn ($schedules): float => round((float) $schedules->sum(fn ($schedule) => max(0, (float) $schedule->total_due - (float) $schedule->paid_amount)), 2);
        $dueTodaySchedules = $baseScheduleQuery()->whereDate('due_date', today())->get(['total_due', 'paid_amount']);
        $overdueSchedules = $baseScheduleQuery()->whereDate('due_date', '<', today())->get(['total_due', 'paid_amount']);
        $upcomingSchedules = $baseScheduleQuery()->whereDate('due_date', '>', today())->get(['total_due', 'paid_amount']);
        $collectedTodayQuery = $basePaymentQuery()->posted()->whereDate('paid_at', today());
        $pendingPayments = $basePaymentQuery()
            ->where('status', LoanPayment::STATUS_PENDING)
            ->where(function ($query): void {
                $query->whereNull('received_by')
                    ->orWhereDoesntHave('receiver', fn ($receiver) => $receiver->where('role', User::ROLE_CUSTOMER));
            })
            ->latest('paid_at')
            ->limit(50)
            ->get();

        $memberPaymentRequests = $basePaymentQuery()
            ->where('status', LoanPayment::STATUS_PENDING)
            ->whereHas('receiver', fn ($receiver) => $receiver->where('role', User::ROLE_CUSTOMER))
            ->latest('paid_at')
            ->limit(50)
            ->get();

        $receiptQuery = $basePaymentQuery()
            ->where(function ($query): void {
                $query->whereIn('status', [
                    LoanPayment::STATUS_POSTED,
                    LoanPayment::STATUS_REJECTED,
                    LoanPayment::STATUS_CANCELED,
                ])->orWhereNull('status');
            })
            ->when($receiptSearch !== '', function ($query) use ($receiptSearch): void {
                $query->where(function ($nested) use ($receiptSearch): void {
                    $nested->where('reference', 'like', "%{$receiptSearch}%")
                        ->orWhere('method', 'like', "%{$receiptSearch}%")
                        ->orWhereHas('loan', fn ($loan) => $loan->where('loan_number', 'like', "%{$receiptSearch}%"))
                        ->orWhereHas('customer', function ($customer) use ($receiptSearch): void {
                            $customer->where('account_number', 'like', "%{$receiptSearch}%")
                                ->orWhere('first_name', 'like', "%{$receiptSearch}%")
                                ->orWhere('last_name', 'like', "%{$receiptSearch}%")
                                ->orWhere('phone', 'like', "%{$receiptSearch}%");
                        });
                });
            })
            ->when($receiptMethod !== '', fn ($query) => $query->where('method', $receiptMethod))
            ->when($receiptInitiatedBy, fn ($query) => $query->where('received_by', $receiptInitiatedBy))
            ->when($receiptApprovedBy, fn ($query) => $query->where('approved_by', $receiptApprovedBy))
            ->when($receiptFrom, fn ($query) => $query->whereDate('paid_at', '>=', $receiptFrom))
            ->when($receiptTo, fn ($query) => $query->whereDate('paid_at', '<=', $receiptTo));

        return view('tenant.repayments.index', [
            'loans' => $loans,
            'dueTodayLoans' => $dueTodayLoans,
            'overdueLoans' => $overdueLoans,
            'upcomingLoans' => $upcomingLoans,
            'search' => $search,
            'receiptSearch' => $receiptSearch,
            'receiptMethod' => $receiptMethod,
            'receiptFrom' => $receiptFrom,
            'receiptTo' => $receiptTo,
            'receiptInitiatedBy' => $receiptInitiatedBy,
            'receiptApprovedBy' => $receiptApprovedBy,
            'dueTodayAmount' => $scheduleAmount($dueTodaySchedules),
            'dueTodayCount' => $dueTodaySchedules->count(),
            'collectedTodayAmount' => (float) (clone $collectedTodayQuery)->sum('amount'),
            'collectedTodayCount' => (clone $collectedTodayQuery)->count(),
            'overdueAmount' => $scheduleAmount($overdueSchedules),
            'overdueCount' => $overdueSchedules->count(),
            'upcomingAmount' => $scheduleAmount($upcomingSchedules),
            'upcomingCount' => $upcomingSchedules->count(),
            'receiptMethods' => $basePaymentQuery()
                ->whereNotNull('method')
                ->distinct()
                ->orderBy('method')
                ->pluck('method')
                ->filter()
                ->values(),
            'pendingPayments' => $pendingPayments,
            'memberPaymentRequests' => $memberPaymentRequests,
            'repaymentApprovalRequired' => $approvalRequired,
            'pendingPaymentAmount' => (float) $pendingPayments->sum('amount'),
            'memberPaymentRequestAmount' => (float) $memberPaymentRequests->sum('amount'),
            'usersForFilter' => User::query()
                ->where('tenant_id', $user->tenant_id)
                ->whereIn('role', $staffRoles)
                ->orderBy('name')
                ->get(['id', 'name']),
            'recentPayments' => $receiptQuery->latest('paid_at')->limit(50)->get(),
        ]);
    }

    public function store(Request $request, Loan $loan, LoanPaymentService $payments): RedirectResponse
    {
        $loan->loadMissing('customer');
        abort_unless($loan->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');

        $validated = $request->validate([
            'amount' => ['required', 'numeric', 'min:0.01'],
            'method' => ['required', 'string', 'max:50'],
            'notes' => ['nullable', 'string', 'max:1000'],
        ]);

        $amount = (float) $validated['amount'];

        try {
            $payment = $payments->record($loan, $amount, $validated['method'], $request->user()->id, $validated['notes'] ?? null);
        } catch (InvalidArgumentException $exception) {
            return back()->with('warning', $exception->getMessage())->withInput();
        }

        if ($payment->status === LoanPayment::STATUS_POSTED) {
            return redirect()
                ->to(route('tenant.loans.show', $loan).'#repayment-schedule')
                ->with('status', 'Repayment posted successfully.');
        }

        return redirect()
            ->route('tenant.repayments.index', ['tab' => 'pending'])
            ->with('status', 'Repayment submitted for approval.');
    }

    public function receipt(LoanPayment $payment): View
    {
        $payment->load(['customer', 'loan.customer', 'receiver', 'canceler']);
        abort_unless($payment->customer?->isVisibleTo(auth()->user()), 403, 'This member is assigned to another account officer.');
        abort_if(in_array($payment->status, [LoanPayment::STATUS_PENDING, LoanPayment::STATUS_REJECTED], true), 404);

        return view('tenant.receipts.loan-payment', compact('payment'));
    }

    public function approve(Request $request, LoanPayment $payment, LoanPaymentService $payments): RedirectResponse
    {
        $payment->loadMissing('customer', 'loan');
        abort_unless($payment->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');

        try {
            $approvedPayment = $payments->approve($payment, $request->user()->id);
        } catch (InvalidArgumentException $exception) {
            return back()->with('warning', $exception->getMessage());
        }

        $tab = in_array($request->input('tab'), ['pending', 'member_requests'], true)
            ? $request->input('tab')
            : 'pending';

        return redirect()
            ->route('tenant.repayments.index', ['tab' => $tab])
            ->with('status', 'Repayment '.$approvedPayment->reference.' approved and posted.');
    }

    public function reject(Request $request, LoanPayment $payment, LoanPaymentService $payments): RedirectResponse
    {
        $payment->loadMissing('customer', 'loan');
        abort_unless($payment->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');

        $validated = $request->validate([
            'rejected_reason' => ['required', 'string', 'max:1000'],
        ]);

        try {
            $payments->reject($payment, $validated['rejected_reason'], $request->user()->id);
        } catch (InvalidArgumentException $exception) {
            return back()->with('warning', $exception->getMessage());
        }

        $tab = in_array($request->input('tab'), ['pending', 'member_requests'], true)
            ? $request->input('tab')
            : 'pending';

        return redirect()
            ->route('tenant.repayments.index', ['tab' => $tab])
            ->with('status', 'Repayment rejected.');
    }

    public function cancel(Request $request, LoanPayment $payment, TransactionCancellationService $cancellations): RedirectResponse
    {
        $payment->loadMissing('customer', 'loan');
        abort_unless($payment->customer?->isVisibleTo($request->user()), 403, 'This member is assigned to another account officer.');

        $validated = $request->validate([
            'canceled_reason' => ['required', 'string', 'max:1000'],
        ]);

        try {
            $cancellations->cancelLoanPayment($payment, $validated['canceled_reason'], $request->user()->id);
        } catch (InvalidArgumentException $exception) {
            return back()->with('warning', $exception->getMessage());
        }

        return redirect()
            ->to(route('tenant.loans.show', $payment->loan).'#repayment-schedule')
            ->with('status', 'Loan repayment canceled and the schedule was reversed.');
    }
}
