<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\Payment;
use App\Models\User;
use App\Policies\CreditPolicy;
use App\Services\CreditCalculator;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * #387 — al ANULAR el único pago de un crédito (para poder modificarlo), el crédito
 * quedaba en SOLO LECTURA: `CreditRules::hasPayments()` contaba el pago anulado (`voided=1`)
 * y su reversa (`payment_method='reversal'`), así que `CreditPolicy::update` lo negaba y
 * el `CreditForm` deshabilitaba los campos. Un pago anulado + su reversa NO representan
 * dinero recibido → no deben marcar el crédito como "con pagos".
 */
class CreditVoidedPaymentEditableTest extends TestCase
{
    use DatabaseTransactions;

    /** @return array{0: Company, 1: User, 2: Credit} */
    private function creditWithPendingInstallments(): array
    {
        $company = Company::factory()->create(['interest_method' => 'flat_rate']);
        $user = User::factory()->create(['company_id' => $company->id]);
        $this->actingAs($user); // el flujo real de edición corre autenticado (auditoría, etc.)
        $client = Client::factory()->create(['company_id' => $company->id]);

        $credit = Credit::create([
            'company_id' => $company->id,
            'client_id' => $client->id,
            'created_by_user_id' => $user->id,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => 100000,
            'interest_rate' => 20,
            'installments_count' => 4,
            'periodicity' => 'weekly',
            'start_date' => Carbon::now(),
        ]);

        for ($i = 1; $i <= 4; $i++) {
            Installment::create([
                'company_id' => $company->id,
                'credit_id' => $credit->id,
                'installment_number' => $i,
                'status' => Installment::STATUS_PENDING,
                'due_date' => Carbon::now()->addWeeks($i),
                'principal_amount' => 25000,
                'interest_amount' => 5000,
                'total_amount' => 30000,
                'principal_balance_after' => 30000,
                'amount_paid' => 0,
            ]);
        }

        return [$company, $user, $credit->fresh()];
    }

    #[Test]
    public function a_credit_whose_only_payment_was_voided_is_editable_again(): void
    {
        [$company, $user, $credit] = $this->creditWithPendingInstallments();

        // Estado exacto tras anular el pago en el panel: original con voided=1 + reversa.
        $original = Payment::create([
            'company_id' => $company->id, 'credit_id' => $credit->id, 'amount' => 4000,
            'payment_date' => now(), 'payment_method' => 'cash', 'registered_by_user_id' => $user->id,
            'voided' => true, 'voided_by_user_id' => $user->id, 'voided_at' => now(),
            'void_reason' => 'Modificación del crédito',
        ]);
        Payment::create([
            'company_id' => $company->id, 'credit_id' => $credit->id, 'amount' => -4000,
            'payment_date' => now(), 'payment_method' => 'reversal', 'registered_by_user_id' => $user->id,
            'voided' => false, 'original_payment_id' => $original->id,
        ]);

        $credit->refresh();

        $this->assertFalse($credit->hasPayments(), 'un pago anulado + su reversa no deben marcar el crédito como con pagos');
        $this->assertFalse($credit->isReadOnly(), 'el crédito debe volver a ser editable');
        $this->assertFalse($credit->hasCapitalPayments());
        $this->assertTrue(app(CreditPolicy::class)->update($user, $credit), 'la política de edición debe permitirlo');
    }

    #[Test]
    public function a_credit_with_a_real_active_payment_stays_read_only(): void
    {
        [$company, $user, $credit] = $this->creditWithPendingInstallments();

        Payment::create([
            'company_id' => $company->id, 'credit_id' => $credit->id, 'amount' => 4000,
            'payment_date' => now(), 'payment_method' => 'cash', 'registered_by_user_id' => $user->id,
            'voided' => false,
        ]);

        $this->assertTrue($credit->fresh()->hasPayments(), 'un pago real (no anulado) sí debe bloquear la edición');
    }

    #[Test]
    public function the_schedule_can_be_regenerated_after_voiding_the_only_payment(): void
    {
        [$company, $user, $credit] = $this->creditWithPendingInstallments();

        $original = Payment::create([
            'company_id' => $company->id, 'credit_id' => $credit->id, 'amount' => 4000,
            'payment_date' => now(), 'payment_method' => 'cash', 'registered_by_user_id' => $user->id,
            'voided' => true, 'voided_by_user_id' => $user->id, 'voided_at' => now(),
        ]);
        Payment::create([
            'company_id' => $company->id, 'credit_id' => $credit->id, 'amount' => -4000,
            'payment_date' => now(), 'payment_method' => 'reversal', 'registered_by_user_id' => $user->id,
            'voided' => false, 'original_payment_id' => $original->id,
        ]);

        // El objetivo del usuario: semanal → diario, 4 → 30 cuotas.
        $credit->update(['periodicity' => 'daily', 'installments_count' => 30]);

        // Antes lanzaba «No se puede recalcular el plan porque el crédito ya tiene pagos».
        app(CreditCalculator::class)->regenerateInstallments($credit->fresh());

        $this->assertSame(30, $credit->fresh()->installments()->count(), 'el plan se regeneró a 30 cuotas');
    }

    #[Test]
    public function operations_use_the_capital_criterion_but_edit_needs_no_effective_payment(): void
    {
        [$company, $user, $credit] = $this->creditWithPendingInstallments();

        // Pago de SOLO interés (amount_paid == interest_amount → NO amortiza capital).
        $credit->installments()->orderBy('installment_number')->first()
            ->update(['amount_paid' => 5000, 'status' => Installment::STATUS_PARTIAL_PAID]);
        Payment::create([
            'company_id' => $company->id, 'credit_id' => $credit->id, 'amount' => 5000,
            'payment_date' => now(), 'payment_method' => 'cash', 'registered_by_user_id' => $user->id,
            'voided' => false,
        ]);

        $credit->refresh();
        $policy = app(CreditPolicy::class);

        $this->assertTrue($credit->hasPayments());
        $this->assertFalse($credit->hasCapitalPayments(), 'un pago de solo interés no amortiza capital');

        // Operaciones (crean hijo / cascada, no regeneran el plan del padre) → permitidas.
        $this->assertTrue($policy->clone($user, $credit), 'clonar permitido sin capital pagado');
        $this->assertTrue($policy->extend($user, $credit), 'extender permitido sin capital pagado');
        $this->assertTrue($policy->cancel($user, $credit), 'cancelar permitido sin capital pagado');
        $this->assertTrue($policy->delete($user, $credit), 'eliminar permitido sin capital pagado');

        // Editar REGENERA el plan → NO permitido con un pago efectivo (aunque sea interés).
        $this->assertFalse($policy->update($user, $credit), 'editar bloqueado con pago efectivo');
    }
}
