<?php

declare(strict_types=1);

namespace Tests\Feature\Financial;

use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\User;
use App\Services\CreditSettlementService;
use App\Services\Payments\PaymentInvariantGuard;
use App\Services\Payments\PaymentMaterializer;
use App\Services\Payments\PaymentRegistrar;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * La condonación de saldo (CreditSettlementService) eleva amount_paid al total de
 * la cuota y registra el perdón en forgiven_amount. Esta suite verifica que esa
 * condonación sea CONSISTENTE con la capa de pagos basada en pivotes:
 *   - una re-materialización (p. ej. al reversar/editar un pago del crédito) NO
 *     borra la condonación, y
 *   - el guard de integridad la acepta
 *     (invariante: amount_paid == SUM(pivotes) + forgiven_amount).
 */
class CondonationIntegrityTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $user;

    private Credit $credit;

    private PaymentRegistrar $registrar;

    private PaymentMaterializer $materializer;

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

        Event::fake();

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

        $this->credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'client_id' => Client::factory()->create(['company_id' => $this->company->id])->id,
            'collector_user_id' => $this->user->id,
            'created_by_user_id' => $this->user->id,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => 100_000,
            'installments_count' => 1,
            'due_date' => Carbon::today()->addDays(30),
        ]);

        Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $this->credit->id,
            'installment_number' => 1,
            'status' => Installment::STATUS_PENDING,
            'due_date' => Carbon::today()->addDays(30)->toDateString(),
            'principal_amount' => 80_000,
            'interest_amount' => 20_000,
            'total_amount' => 100_000,
            'principal_balance_after' => 100_000,
            'amount_paid' => 0,
        ]);

        $this->registrar = app(PaymentRegistrar::class);
        $this->materializer = app(PaymentMaterializer::class);
    }

    /**
     * Cliente paga 60.000 (real, con pivote) y se condonan los 40.000 restantes.
     */
    private function condoneAfterPartialPayment(): Installment
    {
        $this->registrar->forCredit($this->credit, 60_000, Carbon::today(), $this->user->id);
        app(CreditSettlementService::class)->settle($this->credit->fresh(), 'Condonación por pronto pago');

        return $this->credit->installments()->first()->refresh();
    }

    #[Test]
    public function condonation_is_recorded_with_forgiven_amount(): void
    {
        $inst = $this->condoneAfterPartialPayment();

        $this->assertSame(Installment::STATUS_PAID, $inst->status);
        $this->assertEquals(100_000, (float) $inst->amount_paid);
        $this->assertEquals(40_000, (float) $inst->forgiven_amount);
        $this->assertEquals(Credit::STATUS_PAID, $this->credit->fresh()->status);
    }

    #[Test]
    public function rematerializing_a_condoned_credit_does_not_wipe_the_condonation(): void
    {
        $inst = $this->condoneAfterPartialPayment();

        // Re-materializar es lo que ocurriría al reversar o editar un pago del crédito.
        $this->materializer->materializeCredit($this->credit->fresh());
        $inst->refresh();

        // La condonación sobrevive: la cuota sigue pagada al total, no reabierta.
        $this->assertSame(Installment::STATUS_PAID, $inst->status, 're-materializar no debe reabrir una cuota condonada');
        $this->assertEquals(100_000, (float) $inst->amount_paid);
        $this->assertEquals(40_000, (float) $inst->forgiven_amount);
    }

    #[Test]
    public function integrity_guard_accepts_a_condoned_credit(): void
    {
        // No debe lanzar: amount_paid (100k) == pivotes (60k) + forgiven (40k).
        $this->expectNotToPerformAssertions();

        $this->condoneAfterPartialPayment();

        app(PaymentInvariantGuard::class)->assertCreditIntegrity($this->credit->fresh());
    }
}
