<?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\Plan;
use App\Models\Subscription;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Laravel\Sanctum\Sanctum;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Tests de integración para validación de fecha de pago desde PWA.
 *
 * Caso de prueba específico:
 * - Crédito #84, Cliente Dairo Castro
 * - Cuota #4 con due_date: 26 de enero
 * - Pago parcial de 100,000 hoy
 * - Origen: PWA
 *
 * El bug original: La PWA enviaba fechas en UTC usando toISOString(),
 * lo que causaba que pagos hechos después de las 7pm en Colombia
 * fueran rechazados como "fecha futura".
 */
class PwaPaymentDateValidationTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $collector;

    private Client $client;

    private Credit $credit;

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

        // Crear empresa
        $this->company = Company::factory()->create([
            'name' => 'Test Company',
        ]);

        // Crear plan y suscripción activa (requerida por EnsurePwaAccess middleware)
        $plan = Plan::factory()->create(['has_pwa_access' => true]);
        Subscription::factory()->active()->create([
            'company_id' => $this->company->id,
            'plan_id' => $plan->id,
            'ends_at' => now()->addYear(),
        ]);

        // Crear collector con rol Spatie (requerido por EnsurePwaAccess middleware)
        $this->collector = User::factory()->create([
            'company_id' => $this->company->id,
            'name' => 'Test Collector',
        ]);
        Role::firstOrCreate(['name' => 'collector', 'guard_name' => 'web']);
        $this->collector->assignRole('collector');

        // Crear cliente
        $this->client = Client::factory()->create([
            'company_id' => $this->company->id,
            'name' => 'Dairo Castro',
        ]);

        // Crear crédito activo
        $this->credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'client_id' => $this->client->id,
            'collector_user_id' => $this->collector->id,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => 1000000,
            'installments_count' => 6,
        ]);

        // Crear cuotas - la #4 con due_date = 26 de enero (vencida)
        for ($i = 1; $i <= 6; $i++) {
            Installment::factory()->create([
                'company_id' => $this->company->id,
                'credit_id' => $this->credit->id,
                'installment_number' => $i,
                'due_date' => Carbon::create(2024, 1, 20)->addDays(($i - 1) * 7),
                'total_amount' => 200000,
                'principal_amount' => 166666.67,
                'interest_amount' => 33333.33,
                'amount_paid' => $i < 4 ? 200000 : 0, // Primeras 3 pagadas
                'status' => $i < 4 ? Installment::STATUS_PAID : Installment::STATUS_PENDING,
            ]);
        }
    }

    /**
     * Test del escenario exacto del crédito #84.
     * Pago de cuota vencida con fecha de hoy debe ser aceptado.
     */
    public function test_payment_for_overdue_installment_with_today_date_is_accepted(): void
    {
        Sanctum::actingAs($this->collector);

        // Simular que "hoy" es 28 de enero (la cuota #4 vencía el 26)
        Carbon::setTestNow(Carbon::create(2024, 1, 28, 20, 0, 0)); // 8pm

        $response = $this->postJson('/api/pwa/payments', [
            'credit_id' => $this->credit->id,
            'amount' => 100000,
            'payment_method' => 'cash',
            'payment_date' => '2024-01-28', // Formato correcto Y-m-d
            'idempotency_key' => fake()->uuid(),
        ]);

        $response->assertStatus(201)
            ->assertJsonStructure([
                'message',
                'payment' => ['id', 'credit_id', 'amount'],
            ]);

        Carbon::setTestNow();
    }

    /**
     * Test: Pago con fecha de ayer debe ser aceptado (pago retroactivo).
     */
    public function test_payment_with_yesterday_date_is_accepted(): void
    {
        Sanctum::actingAs($this->collector);

        Carbon::setTestNow(Carbon::create(2024, 1, 28, 20, 0, 0));

        $response = $this->postJson('/api/pwa/payments', [
            'credit_id' => $this->credit->id,
            'amount' => 50000,
            'payment_method' => 'cash',
            'payment_date' => '2024-01-27', // Ayer
            'idempotency_key' => fake()->uuid(),
        ]);

        $response->assertStatus(201);

        Carbon::setTestNow();
    }

    /**
     * Test: Pago con fecha futura debe ser rechazado.
     */
    public function test_payment_with_future_date_is_rejected(): void
    {
        Sanctum::actingAs($this->collector);

        Carbon::setTestNow(Carbon::create(2024, 1, 28, 20, 0, 0));

        $response = $this->postJson('/api/pwa/payments', [
            'credit_id' => $this->credit->id,
            'amount' => 50000,
            'payment_method' => 'cash',
            'payment_date' => '2024-01-29', // Mañana
            'idempotency_key' => fake()->uuid(),
        ]);

        $response->assertStatus(422)
            ->assertJsonValidationErrors(['payment_date']);

        Carbon::setTestNow();
    }

    /**
     * Test del bug original: fecha en formato ISO con timezone UTC.
     *
     * Simula lo que enviaba la PWA antes de la corrección:
     * A las 8pm en Colombia, toISOString() generaba fecha del día siguiente en UTC.
     */
    public function test_payment_date_normalization_handles_iso_format(): void
    {
        Sanctum::actingAs($this->collector);

        // Son las 8pm del 28 de enero en Colombia
        Carbon::setTestNow(Carbon::create(2024, 1, 28, 20, 0, 0, 'America/Bogota'));

        // Incluso si el cliente envía el formato ISO completo,
        // el backend debe normalizarlo a solo la fecha
        $response = $this->postJson('/api/pwa/payments', [
            'credit_id' => $this->credit->id,
            'amount' => 100000,
            'payment_method' => 'cash',
            'payment_date' => '2024-01-28T20:00:00-05:00', // ISO con timezone
            'idempotency_key' => fake()->uuid(),
        ]);

        // Debe ser aceptado porque la fecha es hoy (28)
        $response->assertStatus(201);

        Carbon::setTestNow();
    }

    /**
     * Test edge case: Pago a las 11:59pm debe ser aceptado.
     */
    public function test_payment_at_end_of_day_is_accepted(): void
    {
        Sanctum::actingAs($this->collector);

        // 11:59pm del 28 de enero
        Carbon::setTestNow(Carbon::create(2024, 1, 28, 23, 59, 59, 'America/Bogota'));

        $response = $this->postJson('/api/pwa/payments', [
            'credit_id' => $this->credit->id,
            'amount' => 100000,
            'payment_method' => 'cash',
            'payment_date' => '2024-01-28',
            'idempotency_key' => fake()->uuid(),
        ]);

        $response->assertStatus(201);

        Carbon::setTestNow();
    }

    /**
     * Test: El monto debe actualizarse correctamente.
     * Verifica vía DB que el pago queda registrado (remaining_balance no es columna física).
     */
    public function test_payment_updates_installment_and_credit_balance(): void
    {
        Sanctum::actingAs($this->collector);

        Carbon::setTestNow(Carbon::create(2024, 1, 28, 10, 0, 0));

        $response = $this->postJson('/api/pwa/payments', [
            'credit_id' => $this->credit->id,
            'amount' => 100000,
            'payment_method' => 'cash',
            'payment_date' => '2024-01-28',
            'idempotency_key' => fake()->uuid(),
        ]);

        $response->assertStatus(201);

        // Verificar que el pago quedó registrado en la base de datos
        $this->assertDatabaseHas('payments', [
            'credit_id' => $this->credit->id,
            'amount' => 100000,
        ]);

        // Verificar que se aplicó al menos una cuota (pivot payment_installment)
        $payment = Payment::where('credit_id', $this->credit->id)->latest()->first();
        $this->assertNotNull($payment);
        $this->assertGreaterThan(0, $payment->installments()->count());

        Carbon::setTestNow();
    }

    /**
     * Test: Idempotencia - el mismo pago no se puede registrar dos veces.
     */
    public function test_duplicate_payment_is_prevented(): void
    {
        Sanctum::actingAs($this->collector);

        $idempotencyKey = fake()->uuid();

        // Primer pago
        $this->postJson('/api/pwa/payments', [
            'credit_id' => $this->credit->id,
            'amount' => 100000,
            'payment_method' => 'cash',
            'payment_date' => Carbon::today()->toDateString(),
            'idempotency_key' => $idempotencyKey,
        ])->assertStatus(201);

        // Segundo pago con misma key debe ser rechazado como duplicado
        $this->postJson('/api/pwa/payments', [
            'credit_id' => $this->credit->id,
            'amount' => 100000,
            'payment_method' => 'cash',
            'payment_date' => Carbon::today()->toDateString(),
            'idempotency_key' => $idempotencyKey,
        ])->assertStatus(200)
            ->assertJson(['duplicate' => true]);
    }
}
