<?php

declare(strict_types=1);

namespace Tests\Feature\Filament;

use App\Filament\Resources\Payments\Pages\CreatePayment;
use App\Models\Company;
use App\Models\CompanyFinancialSettings;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\Payment;
use App\Models\User;
use Filament\Support\Exceptions\Halt;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use PHPUnit\Framework\Attributes\Test;
use ReflectionMethod;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * #67: el panel Filament CreatePayment ahora aplica las mismas guardas que la PWA
 * — solo créditos hoja activos y las políticas de PaymentPolicyValidator —, en vez
 * de registrar el pago contra Credit::findOrFail sin validación.
 */
class CreatePaymentGuardsTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $admin;

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

        Role::firstOrCreate(['name' => 'admin', 'guard_name' => 'web']);
        $this->company = Company::factory()->create();
        $this->admin = User::factory()->create(['company_id' => $this->company->id]);
        $this->admin->assignRole('admin');
        $this->actingAs($this->admin->fresh());
    }

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

    private function leafCreditWith(int $installmentTotal): Credit
    {
        $credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'status' => Credit::STATUS_ACTIVE,
        ]);
        Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $credit->id,
            'installment_number' => 1,
            'status' => Installment::STATUS_PENDING,
            'due_date' => now()->addDays(7),
            'total_amount' => $installmentTotal,
            'amount_paid' => 0,
        ]);

        return $credit->fresh();
    }

    /** @return mixed */
    private function handleCreation(array $data)
    {
        $page = new CreatePayment;
        $method = new ReflectionMethod(CreatePayment::class, 'handleRecordCreation');
        $method->setAccessible(true);

        return $method->invoke($page, $data);
    }

    #[Test]
    public function it_rejects_a_payment_on_a_parent_credit(): void
    {
        $this->settings(allowPartial: true, allowOver: true);

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

        $this->expectException(Halt::class);

        $this->handleCreation([
            'credit_id' => $parent->id,
            'amount' => 10000,
            'payment_date' => today()->toDateString(),
            'payment_method' => 'cash',
        ]);
    }

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

        $this->expectException(Halt::class);

        $this->handleCreation([
            'credit_id' => $credit->id,
            'amount' => 60001, // supera la cuota (sin permitir sobrepago)
            'payment_date' => today()->toDateString(),
            'payment_method' => 'cash',
        ]);
    }

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

        $payment = $this->handleCreation([
            'credit_id' => $credit->id,
            'amount' => 60000,
            'payment_date' => today()->toDateString(),
            'payment_method' => 'cash',
        ]);

        $this->assertInstanceOf(Payment::class, $payment);
        $this->assertSame($credit->id, $payment->credit_id);
    }
}
