<?php

namespace App\Domain\Billing;

use App\Enums\InstallmentStatus;
use App\Enums\InvoiceStatus;
use App\Models\Installment;
use App\Models\Invoice;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\DB;

/**
 * Installments — the flagship feature for Iraqi clinics (crowns, implants,
 * ortho are routinely paid monthly).
 *
 * Schedule = optional down payment due today (seq 0) + N monthly parts.
 * Amounts are rounded to 250 IQD (smallest practical note); the LAST
 * installment absorbs the rounding remainder so the schedule always sums
 * exactly to the invoice remainder.
 */
class InstallmentService
{
    public const ROUND_TO = 250;

    public function createSchedule(
        Invoice $invoice,
        int $months,
        int $downPayment = 0,
        ?string $firstDueDate = null,
    ): Invoice {
        abort_unless($invoice->status->isOpen(), 422, __('Issue the invoice before scheduling installments.'));
        abort_if($invoice->hasInstallments(), 422, __('This invoice already has an installment schedule.'));
        abort_unless($months >= 1 && $months <= 36, 422, __('Months must be between 1 and 36.'));

        $remainder = $invoice->remaining();
        abort_if($remainder <= 0, 422, __('Nothing left to schedule — the invoice is paid.'));
        abort_unless($downPayment >= 0 && $downPayment < $remainder, 422,
            __('Down payment must be less than the remaining amount.'));

        return DB::transaction(function () use ($invoice, $months, $downPayment, $remainder, $firstDueDate) {
            $seq = 0;

            if ($downPayment > 0) {
                $invoice->installments()->create([
                    'seq' => $seq++,
                    'due_date' => today(),
                    'amount' => $downPayment,
                ]);
            }

            $financed = $remainder - $downPayment;
            $base = intdiv($financed, $months);
            $base = max(self::ROUND_TO, intdiv($base, self::ROUND_TO) * self::ROUND_TO);

            $start = $firstDueDate
                ? CarbonImmutable::parse($firstDueDate)
                : CarbonImmutable::today()->addMonth();

            $allocated = 0;
            for ($i = 1; $i <= $months; $i++) {
                $amount = $i === $months ? $financed - $allocated : min($base, $financed - $allocated);
                $allocated += $amount;

                $invoice->installments()->create([
                    'seq' => $seq++,
                    'due_date' => $start->addMonths($i - 1),
                    'amount' => $amount,
                ]);
            }

            return $invoice->fresh('installments');
        });
    }

    /** Apply an amount to one installment (capped at its remainder); updates status. */
    public function applyToInstallment(Installment $installment, int $amount): int
    {
        $applied = min($amount, $installment->remaining());
        if ($applied <= 0) {
            return 0;
        }

        $newPaid = $installment->paid_amount + $applied;
        $installment->update([
            'paid_amount' => $newPaid,
            'status' => $newPaid >= $installment->amount
                ? InstallmentStatus::Paid
                : InstallmentStatus::Partial,
            'paid_at' => $newPaid >= $installment->amount ? now() : $installment->paid_at,
        ]);

        return $applied;
    }

    /** Outstanding-balance dashboard widgets. */
    public function dashboardWidgets(): array
    {
        $weekEnd = today()->addDays(6);

        return [
            'due_today' => (int) Installment::dueBetween(today(), today())
                ->sum(DB::raw('amount - paid_amount')),
            'due_week' => (int) Installment::dueBetween(today(), $weekEnd)
                ->sum(DB::raw('amount - paid_amount')),
            'overdue' => (int) Installment::overdue()
                ->sum(DB::raw('amount - paid_amount')),
            'overdue_count' => Installment::overdue()->count(),
            'total_outstanding' => (int) Invoice::open()
                ->sum(DB::raw('total - paid_amount')),
        ];
    }
}
