<?php

declare(strict_types=1);

namespace Tests\Feature\Payments;

use App\Models\Company;
use App\Models\CompanyFinancialSettings;
use App\Models\Credit;
use App\Models\Installment;
use App\Services\Payments\PaymentPolicyException;
use App\Services\Payments\PaymentPolicyValidator;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

/**
 * #67: fuente única de validación de pago, compartida por PWA y Filament.
 */
class PaymentPolicyValidatorTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private PaymentPolicyValidator $validator;

    protected function setUp(): void
    {
        parent::setUp();
        $this->company = Company::factory()->create();
        $this->validator = app(PaymentPolicyValidator::class);
    }

    private function settings(bool $allowPartial, bool $allowOver): CompanyFinancialSettings
    {
        return CompanyFinancialSettings::updateOrCreate(
            ['company_id' => $this->company->id],
            ['allow_partial_payments' => $allowPartial, 'allow_overpayments' => $allowOver],
        );
    }

    /**
     * @param  list<int>  $installmentTotals
     */
    private function creditWithInstallments(array $installmentTotals): Credit
    {
        $credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'status' => Credit::STATUS_ACTIVE,
        ]);

        foreach ($installmentTotals as $i => $total) {
            Installment::factory()->create([
                'company_id' => $this->company->id,
                'credit_id' => $credit->id,
                'installment_number' => $i + 1,
                'status' => Installment::STATUS_PENDING,
                'due_date' => now()->addDays(($i + 1) * 7),
                'total_amount' => $total,
                'amount_paid' => 0,
            ]);
        }

        return $credit->fresh();
    }

    #[Test]
    public function it_rejects_amount_over_remaining_balance(): void
    {
        $credit = $this->creditWithInstallments([60000]);
        $settings = $this->settings(allowPartial: true, allowOver: true);

        try {
            $this->validator->validate($credit, 70000, $settings);
            $this->fail('Se esperaba PaymentPolicyException.');
        } catch (PaymentPolicyException $e) {
            $this->assertSame('El monto excede el saldo pendiente.', $e->getMessage());
            $this->assertArrayHasKey('amount', $e->errors());
        }
    }

    #[Test]
    public function it_rejects_partial_payment_when_not_allowed(): void
    {
        $credit = $this->creditWithInstallments([60000]);
        $settings = $this->settings(allowPartial: false, allowOver: true);

        $this->expectException(PaymentPolicyException::class);
        $this->expectExceptionMessage('Esta empresa no permite pagos parciales.');

        $this->validator->validate($credit, 30000, $settings);
    }

    #[Test]
    public function it_rejects_overpayment_over_installment_when_not_allowed(): void
    {
        // Dos cuotas (saldo 120k); la primera vale 60k. Pagar 90k no excede el
        // saldo pero sí la cuota → sobrepago.
        $credit = $this->creditWithInstallments([60000, 60000]);
        $settings = $this->settings(allowPartial: true, allowOver: false);

        $this->expectException(PaymentPolicyException::class);
        $this->expectExceptionMessage('Esta empresa no permite pagos que excedan el valor de la cuota.');

        $this->validator->validate($credit, 90000, $settings);
    }

    #[Test]
    public function it_passes_when_within_policy(): void
    {
        $credit = $this->creditWithInstallments([60000]);
        $settings = $this->settings(allowPartial: true, allowOver: true);

        $this->validator->validate($credit, 60000, $settings);

        $this->assertTrue(true); // no lanzó excepción
    }
}
