<?php

namespace App\Services;

use App\Models\Credit;
use App\Models\Installment;
use App\ValueObjects\Money;
use Carbon\Carbon;
use Illuminate\Support\Collection;

/**
 * Servicio principal para operaciones de consulta sobre créditos.
 *
 * Usa Money Value Object con BC Math para precisión financiera exacta.
 */
class CreditManager
{
    protected SmartCreditStateResolver $stateResolver;

    public function __construct()
    {
        $this->stateResolver = new SmartCreditStateResolver;
    }

    /**
     * Carga relaciones mínimas necesarias para operar de forma consistente.
     */
    private function load(Credit $credit): Credit
    {
        return $credit->loadMissing([
            'installments',
            'company',
            'client',
        ]);
    }

    /*=========================================================
    | ESTADO DEL CRÉDITO
    =========================================================*/
    /**
     * Estado de negocio del crédito usando SmartCreditStateResolver.
     */
    public function getState(Credit $credit): string
    {
        $credit = $this->load($credit);

        return $this->stateResolver->resolve($credit);
    }

    /**
     * Color de badge para UI según el estado.
     */
    public function getStateBadgeColor(Credit $credit): string
    {
        return match ($this->getState($credit)) {
            Credit::STATUS_PAID => 'success',
            Credit::STATUS_CANCELED => 'gray',
            Credit::STATUS_OVERDUE => 'danger',
            Credit::STATUS_DELAYED => 'warning',
            default => 'primary',
        };
    }

    /**
     * Indica si el crédito debe considerarse "cerrado" (pagado/cancelado/etc.).
     */
    public function isClosed(Credit $credit): bool
    {
        return $credit->isClosed();
    }

    /*=========================================================
    | CUOTAS
    =========================================================*/
    /**
     * Cuotas totalmente pagadas.
     *
     * @return Collection<int, Installment>
     */
    public function getPaidInstallments(Credit $credit): Collection
    {
        $credit = $this->load($credit);

        return $credit->installments
            ->where('status', Installment::STATUS_PAID);
    }

    /**
     * Cuotas pendientes "puras" (sin pago alguno).
     *
     * @return Collection<int, Installment>
     */
    public function getPendingInstallments(Credit $credit): Collection
    {
        $credit = $this->load($credit);

        return $credit->installments
            ->where('status', Installment::STATUS_PENDING);
    }

    /**
     * Cuotas con pago parcial.
     *
     * @return Collection<int, Installment>
     */
    public function getPartialPaidInstallments(Credit $credit): Collection
    {
        $credit = $this->load($credit);

        return $credit->installments
            ->where('status', Installment::STATUS_PARTIAL_PAID);
    }

    /**
     * Cuotas vencidas (fecha vencida y monto pendiente).
     *
     * @return Collection<int, Installment>
     */
    public function getOverdueInstallments(Credit $credit): Collection
    {
        $credit = $this->load($credit);
        $today = Carbon::today();

        return $credit->installments
            ->filter(function (Installment $i) use ($today) {
                if (! $i->due_date) {
                    return false;
                }

                $due = Carbon::parse($i->due_date)->startOfDay();
                $paid = Money::of($i->amount_paid);
                $total = Money::of($i->total_amount);

                return $due->lt($today) && $paid->lessThan($total);
            });
    }

    /**
     * Cuotas "abiertas": pendientes, parciales o vencidas.
     *
     * @return Collection<int, Installment>
     */
    public function getOpenInstallments(Credit $credit): Collection
    {
        $credit = $this->load($credit);

        return $credit->installments->filter(function (Installment $i) {
            return in_array($i->status, [
                Installment::STATUS_PENDING,
                Installment::STATUS_PARTIAL_PAID,
                Installment::STATUS_OVERDUE,
            ], true);
        });
    }

    /**
     * Próxima cuota relevante (pendiente / parcial / vencida) ordenada por fecha de vencimiento.
     */
    public function getNextInstallment(Credit $credit): ?Installment
    {
        return $this->getOpenInstallments($credit)
            ->sortBy('due_date')
            ->first();
    }

    /*=========================================================
    | FECHAS
    =========================================================*/
    /**
     * Próxima fecha de vencimiento (o null si no hay cuotas pendientes).
     */
    public function getNextDueDate(Credit $credit): ?string
    {
        $next = $this->getNextInstallment($credit);

        return $next?->due_date?->toDateString();
    }

    /**
     * Días de mora (desde la primera cuota vencida que aún debe dinero).
     */
    public function getDaysLate(Credit $credit): int
    {
        $firstOverdue = $this->getOverdueInstallments($credit)
            ->sortBy('due_date')
            ->first();

        if (! $firstOverdue || ! $firstOverdue->due_date) {
            return 0;
        }

        return Carbon::parse($firstOverdue->due_date)
            ->startOfDay()
            ->diffInDays(Carbon::today());
    }

    /*=========================================================
    | SALDOS Y TOTALES (usando Money para precisión)
    =========================================================*/
    /**
     * Total programado a cobrar como Money.
     */
    public function getTotalAmountAsMoney(Credit $credit): Money
    {
        $credit = $this->load($credit);

        $total = Money::zero();
        foreach ($credit->installments as $installment) {
            $total = $total->add(Money::of($installment->total_amount));
        }

        return $total;
    }

    /**
     * Total programado a cobrar (suma de total_amount de todas las cuotas).
     */
    public function getTotalAmount(Credit $credit): float
    {
        return $this->getTotalAmountAsMoney($credit)->value();
    }

    /**
     * Total efectivamente pagado como Money.
     */
    public function getTotalPaidAsMoney(Credit $credit): Money
    {
        $credit = $this->load($credit);

        $total = Money::zero();
        foreach ($credit->installments as $installment) {
            $total = $total->add(Money::of($installment->amount_paid));
        }

        return $total;
    }

    /**
     * Total efectivamente pagado (suma de amount_paid en todas las cuotas).
     */
    public function getTotalPaid(Credit $credit): float
    {
        return $this->getTotalPaidAsMoney($credit)->value();
    }

    /**
     * Saldo pendiente como Money.
     * Es la fuente de verdad usada para operaciones como extensión/clon.
     */
    public function getRemainingBalanceAsMoney(Credit $credit): Money
    {
        $total = $this->getTotalAmountAsMoney($credit);
        $paid = $this->getTotalPaidAsMoney($credit);

        return $total->subtract($paid)->max(0);
    }

    /**
     * Saldo pendiente basado en cuotas (no en amount del crédito).
     * Es la fuente de verdad usada para operaciones como extensión/clon.
     */
    public function getRemainingBalance(Credit $credit): float
    {
        return $this->getRemainingBalanceAsMoney($credit)->value();
    }

    /**
     * Monto pendiente de la próxima cuota.
     */
    public function getNextPayableAmount(Credit $credit): ?float
    {
        $next = $this->getNextInstallment($credit);

        if (! $next) {
            return null;
        }

        $total = Money::of($next->total_amount);
        $paid = Money::of($next->amount_paid);

        return $total->subtract($paid)->max(0)->value();
    }

    /**
     * ¿El crédito tiene algún pago registrado?
     * Fuente única: modelo Credit::hasPayments().
     */
    public function hasPayments(Credit $credit): bool
    {
        $credit = $this->load($credit);

        return $credit->hasPayments();
    }

    /*=========================================================
    | KPIs (Para dashboards / resúmenes)
    =========================================================*/
    public function getSummary(Credit $credit): array
    {
        $credit = $this->load($credit);

        $paidInstallments = $this->getPaidInstallments($credit);
        $pendingInstallments = $this->getPendingInstallments($credit);
        $partialInstallments = $this->getPartialPaidInstallments($credit);
        $overdueInstallments = $this->getOverdueInstallments($credit);
        $openInstallments = $this->getOpenInstallments($credit);

        $totalAmount = $this->getTotalAmount($credit);
        $totalPaid = $this->getTotalPaid($credit);
        $remaining = $this->getRemainingBalance($credit);
        $nextDueDate = $this->getNextDueDate($credit);
        $daysLate = $this->getDaysLate($credit);
        $hasPayments = $this->hasPayments($credit);
        $state = $this->getState($credit);
        $stateBadge = $this->getStateBadgeColor($credit);
        $isClosed = $this->isClosed($credit);

        return [
            // Estado
            'state' => $state,
            'state_badge' => $stateBadge,
            'is_closed' => $isClosed,
            'has_payments' => $hasPayments,

            // Cuotas
            'installments_total' => $credit->installments_count,
            'installments_paid' => $paidInstallments->count(),
            'installments_pending' => $pendingInstallments->count(),
            'installments_partial' => $partialInstallments->count(),
            'installments_overdue' => $overdueInstallments->count(),
            'installments_open' => $openInstallments->count(),

            // Montos
            'total_amount' => $totalAmount,
            'total_paid' => $totalPaid,
            'remaining_balance' => $remaining,

            // Fechas
            'next_due_date' => $nextDueDate,
            'days_late' => $daysLate,
        ];
    }
}
