<?php

declare(strict_types=1);

namespace Tests\Unit;

use App\Models\Credit;
use App\Services\Interest\DiminishingBalanceStrategy;
use App\Services\Interest\FlatRateStrategy;
use Carbon\Carbon;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * La primera cuota cae en el día que eligió el cobrador, y nunca el mismo día
 * del desembolso.
 *
 * El cobrador reportó que el día elegido no se respetaba. Detrás había cuatro
 * cálculos distintos de la misma fecha —el JavaScript del formulario,
 * `DueDateService` y una copia dentro de CADA estrategia de interés— y no
 * coincidían entre sí. Las estrategias, que son las que de verdad crean el
 * calendario, devolvían el MISMO día del desembolso cuando el día elegido caía
 * en esa fecha: el cliente recibía la plata y ya debía una cuota.
 *
 * `DueDateService` sí tenía la regla correcta escrita y probada ("la primera
 * cuota debe ser posterior a la fecha de inicio"), solo que no la llamaba nadie.
 */
class PrimeraCuotaSegunDiaElegidoTest extends TestCase
{
    /** @return array<string, array{0: string}> */
    public static function estrategias(): array
    {
        return [
            'tasa fija' => [FlatRateStrategy::class],
            'saldo decreciente' => [DiminishingBalanceStrategy::class],
        ];
    }

    private function credito(string $inicio, string $periodicidad, ?int $dia1, ?int $dia2 = null): Credit
    {
        $credit = new Credit;
        $credit->company_id = 1;
        $credit->amount = 500000;
        $credit->interest_rate = 20;
        $credit->installments_count = 6;
        $credit->periodicity = $periodicidad;
        $credit->start_date = $inicio;
        $credit->due_day_1 = $dia1;
        $credit->due_day_2 = $dia2;

        return $credit;
    }

    /** @param  class-string  $estrategia */
    private function primeraCuota(string $estrategia, Credit $credit): Carbon
    {
        $filas = (new $estrategia)->calculate($credit);

        return Carbon::parse($filas[0]['due_date'])->startOfDay();
    }

    #[Test]
    #[DataProvider('estrategias')]
    public function la_primera_cuota_semanal_cae_en_el_dia_elegido(string $estrategia): void
    {
        // Un lunes cualquiera; se recorren los siete días para no depender de
        // uno en concreto.
        $lunes = Carbon::parse('2026-03-02');
        $this->assertSame(1, $lunes->dayOfWeekIso, 'la fecha base debe ser lunes');

        foreach (range(1, 7) as $diaElegido) {
            $primera = $this->primeraCuota(
                $estrategia,
                $this->credito($lunes->toDateString(), 'weekly', $diaElegido)
            );

            $this->assertSame(
                $diaElegido,
                $primera->dayOfWeekIso,
                "eligiendo el día ISO {$diaElegido}, la primera cuota cayó en {$primera->toDateString()}"
            );
        }
    }

    #[Test]
    #[DataProvider('estrategias')]
    public function la_primera_cuota_nunca_cae_el_dia_del_desembolso(string $estrategia): void
    {
        // El caso que rompía: se desembolsa un lunes y se elige "lunes". Antes
        // la cuota quedaba ESE mismo lunes — el cliente debía antes de irse.
        $lunes = Carbon::parse('2026-03-02');

        $primera = $this->primeraCuota(
            $estrategia,
            $this->credito($lunes->toDateString(), 'weekly', 1)
        );

        $this->assertTrue(
            $primera->gt($lunes),
            "la primera cuota quedó en {$primera->toDateString()}, el mismo día del desembolso"
        );
        $this->assertSame('2026-03-09', $primera->toDateString());
    }

    #[Test]
    #[DataProvider('estrategias')]
    public function la_primera_cuota_mensual_cae_en_el_dia_del_mes_elegido(string $estrategia): void
    {
        $primera = $this->primeraCuota(
            $estrategia,
            $this->credito('2026-03-10', 'monthly', 25)
        );

        $this->assertSame(25, $primera->day);
        $this->assertSame('2026-03-25', $primera->toDateString());
    }

    #[Test]
    #[DataProvider('estrategias')]
    public function un_dia_del_mes_ya_pasado_se_va_al_mes_siguiente(string $estrategia): void
    {
        $primera = $this->primeraCuota(
            $estrategia,
            $this->credito('2026-03-28', 'monthly', 5)
        );

        $this->assertSame('2026-04-05', $primera->toDateString());
    }

    #[Test]
    #[DataProvider('estrategias')]
    public function la_fecha_forzada_manda_sobre_el_dia_elegido(string $estrategia): void
    {
        // Sigue habiendo una salida manual: si alguien fija una fecha concreta,
        // es la suya. Lo que ya no pasa es que el formulario la rellene solo y
        // deje el día elegido sin efecto.
        $credit = $this->credito('2026-03-02', 'weekly', 1);
        $credit->first_due_date_override = '2026-03-05';

        $this->assertSame(
            '2026-03-05',
            $this->primeraCuota($estrategia, $credit)->toDateString()
        );
    }
}
