<?php

declare(strict_types=1);

namespace Tests\Unit;

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

/**
 * #91 — En FlatRate cada cuota debe cumplir total_amount == principal_amount + interest_amount.
 *
 * Antes se allocaban capital, interés y total por SEPARADO; cada uno absorbía su residuo
 * de redondeo en la última cuota, pero los residuos no coincidían → filas intermedias con
 * P+I ≠ total (hasta ±1¢), corrompiendo el prorrateo de principal_paid/interest_paid.
 * El fix deriva el total = capital + interés por fila.
 */
class FlatRateStrategyRowInvariantTest extends TestCase
{
    private function buildCredit(float $amount, float $rate, int $n): Credit
    {
        $credit = new Credit;
        $credit->company_id = 1;
        $credit->amount = $amount;
        $credit->interest_rate = $rate;
        $credit->installments_count = $n;
        $credit->periodicity = 'daily';
        $credit->start_date = '2026-01-01';

        return $credit;
    }

    /** @return array<string, array{0: float, 1: float, 2: int}> */
    public static function cases(): array
    {
        return [
            'issue 100 @2% x3' => [100.0, 2.0, 3],
            'diverging 100 @10% x3' => [100.0, 10.0, 3],   // antes: filas con P+I=36.66 vs total=36.67
            'odd 1000 @15% x7' => [1000.0, 15.0, 7],
            'ugly 333 @33% x9' => [333.0, 33.0, 9],
        ];
    }

    #[Test]
    #[DataProvider('cases')]
    public function each_row_total_equals_principal_plus_interest(float $amount, float $rate, int $n): void
    {
        foreach ([new FlatRateStrategy, new FlatRateProjectedStrategy] as $strategy) {
            $rows = $strategy->calculate($this->buildCredit($amount, $rate, $n));
            $this->assertCount($n, $rows);

            $sumP = 0.0;
            $sumI = 0.0;
            $sumT = 0.0;
            foreach ($rows as $r) {
                $this->assertEqualsWithDelta(
                    round($r['principal_amount'] + $r['interest_amount'], 2),
                    round($r['total_amount'], 2),
                    0.001,
                    sprintf('%s fila %d: P+I (%.2f+%.2f) != total (%.2f)',
                        $strategy->getName(), $r['installment_number'],
                        $r['principal_amount'], $r['interest_amount'], $r['total_amount']),
                );
                $sumP += $r['principal_amount'];
                $sumI += $r['interest_amount'];
                $sumT += $r['total_amount'];
            }

            // El total global sigue cuadrando exactamente.
            $expectedInterest = round($amount * $rate / 100, 2);
            $this->assertEqualsWithDelta($amount, $sumP, 0.001, 'suma de capital = monto');
            $this->assertEqualsWithDelta($expectedInterest, $sumI, 0.001, 'suma de interés = interés total');
            $this->assertEqualsWithDelta($amount + $expectedInterest, $sumT, 0.001, 'suma de totales = capital + interés');
        }
    }

    /**
     * #389 — cuando el total a cobrar divide EXACTO entre las cuotas, todas deben ser
     * iguales. 100.000 al 20% = 120.000 en 30 cuotas ⇒ 30 × 4.000,00.
     *
     * Antes salían 29 cuotas de 3.999,99 + 1 de 4.000,29: se allocaban capital
     * (100.000/30) e interés (20.000/30) por SEPARADO y el residuo de cada uno caía en
     * la última cuota. Aunque 120.000/30 = 4.000 exacto, la división por componentes
     * rompía la uniformidad. El fix alloca el TOTAL y deriva capital = total − interés.
     */
    #[Test]
    public function installment_totals_are_uniform_when_the_receivable_divides_evenly(): void
    {
        foreach ([new FlatRateStrategy, new FlatRateProjectedStrategy] as $strategy) {
            $rows = $strategy->calculate($this->buildCredit(100000.0, 20.0, 30));

            foreach ($rows as $r) {
                $this->assertEqualsWithDelta(
                    4000.00,
                    $r['total_amount'],
                    0.001,
                    sprintf('%s fila %d: total %.2f != 4000.00',
                        $strategy->getName(), $r['installment_number'], $r['total_amount']),
                );
            }
        }
    }
}
