<?php

declare(strict_types=1);

namespace Tests\Feature\Security;

use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\Payment;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Str;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * FIX H-03: idempotency_key auto-generado cuando el cliente no lo envía.
 *
 * StorePaymentRequest::prepareForValidation() ahora genera un UUID si el campo
 * está ausente o vacío, eliminando el riesgo de duplicados en reintentos sin clave.
 */
class PaymentIdempotencyAutoKeyTest extends TestCase
{
    use DatabaseTransactions;

    private const PAYMENT_URL = '/api/pwa/payments';

    private Company $company;

    private User $collector;

    private Credit $credit;

    private float $installmentAmount = 500.0;

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

        Role::firstOrCreate(['name' => 'collector', 'guard_name' => 'web']);

        $plan = Plan::factory()->create();
        $this->company = Company::factory()->create();

        Subscription::factory()->create([
            'company_id' => $this->company->id,
            'plan_id' => $plan->id,
            'status' => Subscription::STATUS_ACTIVE,
            'is_active' => true,
            'starts_at' => now()->subMonth(),
            'ends_at' => now()->addYear(),
        ]);

        $this->collector = User::factory()->create(['company_id' => $this->company->id]);
        $this->collector->assignRole('collector');
        $this->collector = $this->collector->fresh();

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

        $this->credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'client_id' => $client->id,
            'collector_user_id' => $this->collector->id,
            'created_by_user_id' => $this->collector->id,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => $this->installmentAmount,
            'installments_count' => 1,
            'periodicity' => 'monthly',
            'start_date' => now()->subDays(5),
            'due_date' => now()->addDays(25),
        ]);

        Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $this->credit->id,
            'installment_number' => 1,
            'status' => 'pending',
            'amount_paid' => 0,
            'total_amount' => $this->installmentAmount,
            'principal_amount' => $this->installmentAmount * 0.8,
            'interest_amount' => $this->installmentAmount * 0.2,
            'due_date' => now()->addDays(25),
        ]);
    }

    private function collectorToken(): string
    {
        return $this->collector->createToken('device', ['pwa:collector'])->plainTextToken;
    }

    private function payload(array $overrides = []): array
    {
        return array_merge([
            'credit_id' => $this->credit->id,
            'amount' => 100,
            'payment_date' => now()->toDateString(),
        ], $overrides);
    }

    #[Test]
    public function payment_without_idempotency_key_succeeds_with_auto_generated_uuid(): void
    {
        $token = $this->collectorToken();

        $response = $this->withToken($token)
            ->postJson(self::PAYMENT_URL, $this->payload());

        // Auto-generated key → payment succeeds
        $response->assertStatus(201);

        // The stored payment must have a valid UUID idempotency_key
        $payment = Payment::where('credit_id', $this->credit->id)->first();
        $this->assertNotNull($payment?->idempotency_key);
        $this->assertTrue(Str::isUuid($payment->idempotency_key));
    }

    #[Test]
    public function explicit_idempotency_key_is_preserved_and_not_overwritten(): void
    {
        $token = $this->collectorToken();
        $myKey = (string) Str::uuid();

        $this->withToken($token)
            ->postJson(self::PAYMENT_URL, $this->payload(['idempotency_key' => $myKey]))
            ->assertStatus(201);

        $this->assertDatabaseHas('payments', [
            'credit_id' => $this->credit->id,
            'idempotency_key' => $myKey,
        ]);
    }

    #[Test]
    public function duplicate_idempotency_key_does_not_create_second_payment(): void
    {
        $token = $this->collectorToken();
        $myKey = (string) Str::uuid();

        // First submission — success
        $this->withToken($token)
            ->postJson(self::PAYMENT_URL, $this->payload(['idempotency_key' => $myKey]))
            ->assertStatus(201);

        // Second submission — same key, must return duplicate response
        $this->withToken($token)
            ->postJson(self::PAYMENT_URL, $this->payload(['idempotency_key' => $myKey]))
            ->assertStatus(200)
            ->assertJson(['duplicate' => true]);

        // Only one payment in DB
        $this->assertSame(
            1,
            Payment::where('idempotency_key', $myKey)->count()
        );
    }
}
