<?php

declare(strict_types=1);

namespace Tests\Feature\Metrics;

use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\Payment;
use App\Models\User;
use App\Services\Metrics\InterestAttributionService;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * La BD de dev es una copia de produccion, pero CI corre con BD limpia: cada
 * test crea su propia empresa y solo asevera sobre ella.
 */
class InterestAttributionServiceTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private Installment $installment;

    private User $user;

    protected function setUp(): void
    {
        parent::setUp();

        $this->company = Company::factory()->create(['status' => Company::STATUS_ACTIVE]);
        $this->user = User::factory()->create(['company_id' => $this->company->id]);

        $credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'status' => Credit::STATUS_ACTIVE,
        ]);

        // Cuota con $10.000 de interes y $50.000 de capital.
        // company_id explicito: InstallmentFactory no lo setea (la columna
        // multi-tenant se agrego despues de la factory).
        $this->installment = Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $credit->id,
            'interest_amount' => 10000,
            'principal_amount' => 50000,
            'total_amount' => 60000,
        ]);
    }

    /** Registra un pago aplicado a la cuota del setUp. */
    private function pay(float $amount, string $date, bool $voided = false): void
    {
        $payment = Payment::create([
            'company_id' => $this->company->id,
            'credit_id' => $this->installment->credit_id,
            'amount' => $amount,
            'payment_date' => $date,
            'registered_by_user_id' => $this->user->id,
            'voided' => $voided,
        ]);

        DB::table('payment_installment')->insert([
            'payment_id' => $payment->id,
            'installment_id' => $this->installment->id,
            'applied_amount' => $amount,
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function service(): InterestAttributionService
    {
        return app(InterestAttributionService::class);
    }

    #[Test]
    public function el_primer_pago_cubre_primero_el_interes(): void
    {
        $this->pay(25000, '2026-09-01');

        // De los $25.000 aplicados, los primeros $10.000 son interes.
        $this->assertEqualsWithDelta(10000.0, $this->service()->interestTotal($this->company->id), 0.01);
    }

    #[Test]
    public function el_segundo_pago_ya_es_solo_capital(): void
    {
        $this->pay(25000, '2026-09-01');
        $this->pay(20000, '2026-09-05');

        // El interes de la cuota ya se cubrio: el total no sube.
        $this->assertEqualsWithDelta(10000.0, $this->service()->interestTotal($this->company->id), 0.01);

        // Y el dia del segundo pago no aporta interes.
        $this->assertEqualsWithDelta(
            0.0,
            $this->service()->interestBetween(
                $this->company->id,
                Carbon::parse('2026-09-05'),
                Carbon::parse('2026-09-05')
            ),
            0.01
        );
    }

    #[Test]
    public function el_interes_queda_fechado_en_el_pago_que_lo_genero(): void
    {
        $this->pay(25000, '2026-09-01');
        $this->pay(20000, '2026-09-05');

        $this->assertEqualsWithDelta(
            10000.0,
            $this->service()->interestBetween(
                $this->company->id,
                Carbon::parse('2026-09-01'),
                Carbon::parse('2026-09-01')
            ),
            0.01
        );
    }

    #[Test]
    public function los_pagos_anulados_no_cuentan(): void
    {
        $this->pay(25000, '2026-09-01', voided: true);

        $this->assertEqualsWithDelta(0.0, $this->service()->interestTotal($this->company->id), 0.01);
    }

    #[Test]
    public function un_pago_parcial_menor_al_interes_solo_imputa_lo_pagado(): void
    {
        // Paga $4.000 de una cuota con $10.000 de interes: todo es interes,
        // pero solo los $4.000 efectivamente pagados.
        $this->pay(4000, '2026-09-01');

        $this->assertEqualsWithDelta(4000.0, $this->service()->interestTotal($this->company->id), 0.01);
    }

    #[Test]
    public function no_mezcla_empresas(): void
    {
        $this->pay(25000, '2026-09-01');

        $otra = Company::factory()->create(['status' => Company::STATUS_ACTIVE]);

        $this->assertEqualsWithDelta(0.0, $this->service()->interestTotal($otra->id), 0.01);
    }
}
