<?php

declare(strict_types=1);

namespace Tests\Unit;

use App\DTOs\RefinanceCreditData;
use App\DTOs\RestructureCreditData;
use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\User;
use App\Services\Credits\CreditOperationOrchestrator;
use App\Services\Credits\CreditOperationValidator;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * #117 — Refinance y restructure ya aceptan una tasa (son operaciones "con términos"),
 * así que se les quita el bloqueo `assertNoCapitalPaid`: permiten créditos con capital
 * pagado (igual que renovación #95 y extendWithInterest). Además, restructure sin tasa
 * explícita crea el hijo a tasa 0 (redistribuye el saldo sin recargar interés — anti
 * anatocismo, mismo principio de #90; antes heredaba la tasa original).
 */
class RefinanceRestructureCapitalTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $user;

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

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

    /** Crédito flat-rate con las primeras N cuotas 100% pagadas (capital amortizado). */
    private function createCreditWithPaidInstallments(
        int $totalInstallments,
        int $paidInstallments,
        float $amount,
        float $interestRate,
    ): Credit {
        $total = $amount * (1 + $interestRate / 100);
        $perInstallment = $total / $totalInstallments;

        $client = Client::factory()->create(['company_id' => $this->company->id]);

        $credit = Credit::create([
            'company_id' => $this->company->id,
            'client_id' => $client->id,
            'collector_user_id' => $this->user->id,
            'created_by_user_id' => $this->user->id,
            'status' => Credit::STATUS_DELAYED,
            'amount' => $amount,
            'interest_rate' => $interestRate,
            'installments_count' => $totalInstallments,
            'periodicity' => 'weekly',
            'start_date' => Carbon::now()->subWeeks($totalInstallments),
        ]);

        for ($i = 1; $i <= $totalInstallments; $i++) {
            $isPaid = $i <= $paidInstallments;
            Installment::create([
                'company_id' => $this->company->id,
                'credit_id' => $credit->id,
                'installment_number' => $i,
                'status' => $isPaid ? Installment::STATUS_PAID : Installment::STATUS_PENDING,
                'due_date' => Carbon::now()->subWeeks($totalInstallments - $i),
                'paid_date' => $isPaid ? Carbon::now()->subWeeks($totalInstallments - $i) : null,
                'principal_amount' => $amount / $totalInstallments,
                'interest_amount' => ($amount * $interestRate / 100) / $totalInstallments,
                'total_amount' => $perInstallment,
                'principal_balance_after' => $isPaid ? 0 : $perInstallment,
                'amount_paid' => $isPaid ? $perInstallment : 0,
            ]);
        }

        return $credit->fresh(['installments']);
    }

    #[Test]
    public function refinance_is_allowed_for_a_credit_with_capital_paid(): void
    {
        // 500k @ 40% → total 700k, 10 cuotas de 70k. 6 pagadas → saldo 280k, capital pagado.
        $credit = $this->createCreditWithPaidInstallments(10, 6, 500000, 40);
        $this->assertTrue($credit->hasCapitalPayments());

        $this->assertTrue(
            app(CreditOperationOrchestrator::class)->canRefinance($credit),
            'canRefinance debe ser true para un crédito con capital pagado (#117).',
        );

        $data = new RefinanceCreditData(
            creditId: $credit->id,
            newInstallmentsCount: 12,
            additionalCapital: 100000,
            newInterestRate: '40',
        );
        $balance = app(CreditOperationValidator::class)->validateForRefinance($credit, $data);
        $this->assertEqualsWithDelta(280000, (float) $balance->value(), 0.01);
    }

    #[Test]
    public function restructure_is_allowed_for_a_credit_with_capital_paid(): void
    {
        $credit = $this->createCreditWithPaidInstallments(10, 6, 500000, 40);
        $this->assertTrue($credit->hasCapitalPayments());

        $this->assertTrue(
            app(CreditOperationOrchestrator::class)->canRestructure($credit),
            'canRestructure debe ser true para un crédito con capital pagado (#117).',
        );

        $data = new RestructureCreditData(creditId: $credit->id, newInstallmentsCount: 15);
        $balance = app(CreditOperationValidator::class)->validateForRestructure($credit, $data);
        $this->assertEqualsWithDelta(280000, (float) $balance->value(), 0.01);
    }

    #[Test]
    public function restructure_without_rate_redistributes_balance_without_new_interest(): void
    {
        // Saldo 280k, tasa original 40%. Reestructurar SIN tasa: debe redistribuir 280k tal cual.
        // Antes del fix heredaba el 40% → 280k × 1,40 = 392k (anatocismo).
        $credit = $this->createCreditWithPaidInstallments(10, 6, 500000, 40);

        $dto = new RestructureCreditData(
            creditId: $credit->id,
            newInstallmentsCount: 15,
            capitalizedInterest: 0,
            newInterestRate: null, // sin tasa → debe usar 0
            periodicity: 'weekly',
            startDate: Carbon::today(),
            reason: 'Re-repartir el saldo sin recargar interés',
        );

        $result = app(CreditOperationOrchestrator::class)->restructure($dto);
        $newCredit = $result->newCredit->fresh(['installments']);

        $this->assertEquals(0.0, (float) $newCredit->interest_rate, 'Restructure sin tasa → hijo a tasa 0.');
        $this->assertEqualsWithDelta(
            280000,
            (float) $newCredit->installments->sum('total_amount'),
            0.01,
            'Debe redistribuir el saldo SIN recargar interés (antes del fix daba 392.000).',
        );
        $this->assertSame(15, $newCredit->installments->count());
        $this->assertSame(Credit::STATUS_RESTRUCTURED, $credit->fresh()->status);
    }
}
