<?php

namespace App\Services;

use App\Models\Loan;
use App\Services\CreditScore\CreditScoreRefreshCoordinator;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Illuminate\Support\Facades\DB;

class OverdueLoanService
{
    public function __construct(
        private MoneyMath $money,
        private LoanPenaltyService $penalties,
        private PaymentDeadlinePenaltyService $deadlinePenalties,
        private CreditScoreRefreshCoordinator $creditScores,
    ) {}

    /** @return array{processed:int,overdue:int,penalties_added:string} */
    public function process(CarbonInterface|string $date): array
    {
        $target = $date instanceof CarbonInterface
            ? CarbonImmutable::instance($date)->startOfDay()
            : CarbonImmutable::parse($date)->startOfDay();

        $stats = ['processed' => 0, 'overdue' => 0, 'penalties_added_minor' => 0];
        $loanIds = Loan::query()->where('status', 'active')->orderBy('id')->pluck('id');

        foreach ($loanIds as $loanId) {
            DB::transaction(function () use ($loanId, $target, &$stats): void {
                $loan = Loan::query()->lockForUpdate()->findOrFail($loanId);
                $before = [
                    'days_overdue' => (int) ($loan->days_overdue ?? 0),
                    'total_overdue' => $this->money->toMinor((string) ($loan->total_overdue ?? '0')),
                    'arrears_bucket' => $loan->arrears_bucket,
                ];

                $stats['processed']++;
                $stats['penalties_added_minor'] += $this->deadlinePenalties->assessMissedSchedules($loan, $target);
                $stats['penalties_added_minor'] += $this->penalties->assess($loan->fresh(), $target);
                $refreshed = $this->refreshLoan($loan->fresh(), $target);
                if ($refreshed->days_overdue > 0) $stats['overdue']++;

                $changed = $before['days_overdue'] !== (int) ($refreshed->days_overdue ?? 0)
                    || $before['total_overdue'] !== $this->money->toMinor((string) ($refreshed->total_overdue ?? '0'))
                    || $before['arrears_bucket'] !== $refreshed->arrears_bucket;

                if ($changed) {
                    $this->creditScores->afterCommitForLoan($refreshed, 'arrears_recalculated');
                }
            });
        }

        return [
            'processed' => $stats['processed'],
            'overdue' => $stats['overdue'],
            'penalties_added' => $this->money->fromMinor($stats['penalties_added_minor']),
        ];
    }

    public function refreshLoan(Loan $loan, CarbonInterface|string $date): Loan
    {
        $target = $date instanceof CarbonInterface
            ? CarbonImmutable::instance($date)->startOfDay()
            : CarbonImmutable::parse($date)->startOfDay();

        $components = ['principal' => 0, 'interest' => 0, 'fees' => 0, 'penalties' => 0];
        $daysOverdue = 0;
        $installments = $loan->installments()->orderBy('installment_number')->get();

        foreach ($installments as $installment) {
            $principal = max(0, $this->money->toMinor((string) $installment->principal) - $this->money->toMinor((string) $installment->principal_paid));
            $interest = max(0, $this->money->toMinor((string) $installment->interest) - $this->money->toMinor((string) $installment->interest_paid));
            $fees = max(0, $this->money->toMinor((string) $installment->fees) - $this->money->toMinor((string) $installment->fees_paid));
            $penalties = max(0, $this->money->toMinor((string) $installment->penalty_due) - $this->money->toMinor((string) $installment->penalty_paid));
            $remaining = $principal + $interest + $fees + $penalties;
            $due = CarbonImmutable::parse($installment->due_date)->startOfDay();

            if ($remaining === 0) {
                if ($installment->status !== 'paid' || $this->money->toMinor((string) $installment->remaining) !== 0) $installment->update(['remaining' => '0.00', 'status' => 'paid']);
                continue;
            }

            if ($due->lessThan($target)) {
                $components['principal'] += $principal;
                $components['interest'] += $interest;
                $components['fees'] += $fees;
                $components['penalties'] += $penalties;
                $daysOverdue = max($daysOverdue, (int) $due->diffInDays($target));
                if ($installment->status !== 'overdue' || $this->money->toMinor((string) $installment->remaining) !== $remaining) $installment->update(['remaining' => $this->money->fromMinor($remaining), 'status' => 'overdue']);
                continue;
            }

            $status = $this->money->toMinor((string) $installment->amount_paid) > 0 ? 'partial' : 'upcoming';
            if ($installment->status !== $status || $this->money->toMinor((string) $installment->remaining) !== $remaining) $installment->update(['remaining' => $this->money->fromMinor($remaining), 'status' => $status]);
        }

        $total = array_sum($components);
        $loan->update([
            'overdue_principal' => $this->money->fromMinor($components['principal']),
            'overdue_interest' => $this->money->fromMinor($components['interest']),
            'overdue_fees' => $this->money->fromMinor($components['fees']),
            'overdue_penalties' => $this->money->fromMinor($components['penalties']),
            'total_overdue' => $this->money->fromMinor($total),
            'days_overdue' => $daysOverdue,
            'arrears_bucket' => $this->bucket($daysOverdue),
            'arrears_updated_at' => now(),
        ]);

        return $loan->fresh();
    }

    private function bucket(int $days): ?string
    {
        if ($days < 1) return null;
        if ($days <= 7) return '1-7';
        if ($days <= 30) return '8-30';
        if ($days <= 60) return '31-60';
        if ($days <= 90) return '61-90';
        return '90+';
    }
}
