<?php

declare(strict_types=1);

namespace Tests\Feature\Pwa;

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 App\Services\Payments\PaymentReverser;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Testing\TestResponse;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * El recibo lleva el historial completo del crédito, estilo factura de
 * supermercado: el cliente ve todo lo que ha abonado, no solo el pago de hoy.
 *
 * Es el soporte que el cobrador entrega en mano, así que tiene dos propiedades
 * que no se pueden romper: no muestra movimientos que no fueron dinero recibido,
 * y reimprimirlo meses después dice exactamente lo mismo que el día que se
 * entregó.
 */
class ReciboConHistorialTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $collector;

    private User $supervisor;

    private Credit $credit;

    private Installment $installment;

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

        foreach (['collector', 'supervisor', 'admin'] as $role) {
            Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
        }

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

        Subscription::factory()->create([
            'company_id' => $this->company->id,
            'plan_id' => Plan::factory()->create()->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->supervisor = User::factory()->create(['company_id' => $this->company->id]);
        $this->supervisor->assignRole('supervisor');

        $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,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => 500000,
            'installments_count' => 10,
        ]);

        $this->installment = Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $this->credit->id,
            'installment_number' => 1,
            'status' => Installment::STATUS_PENDING,
            'due_date' => now()->toDateString(),
            'principal_amount' => 50000,
            'interest_amount' => 20000,
            'total_amount' => 70000,
            'amount_paid' => 0,
            'principal_balance_after' => 450000,
        ]);
    }

    private function pago(float $monto, string $fecha, ?User $quien = null): Payment
    {
        return Payment::create([
            'company_id' => $this->company->id,
            'credit_id' => $this->credit->id,
            'installment_id' => $this->installment->id,
            'amount' => $monto,
            'applied_amount' => $monto,
            'extra_amount' => 0,
            'payment_date' => $fecha,
            'payment_method' => 'cash',
            'registered_by_user_id' => ($quien ?? $this->collector)->id,
        ]);
    }

    private function recibo(Payment $pago, ?User $comoQuien = null): TestResponse
    {
        return $this->actingAs($comoQuien ?? $this->collector, 'sanctum')
            ->getJson("/api/pwa/payments/{$pago->id}/receipt");
    }

    #[Test]
    public function el_recibo_lista_todos_los_abonos_con_su_acumulado(): void
    {
        $this->pago(20000, now()->subDays(14)->toDateString());
        $this->pago(30000, now()->subDays(7)->toDateString());
        $ultimo = $this->pago(25000, now()->toDateString());

        $historial = $this->recibo($ultimo)->assertOk()->json('history');

        $this->assertCount(3, $historial);

        // `floatval`: el JSON entrega enteros cuando el monto no tiene decimales.
        $montos = fn (string $campo) => array_map('floatval', array_column($historial, $campo));

        // Del más antiguo al más nuevo, como una factura.
        $this->assertSame([20000.0, 30000.0, 25000.0], $montos('amount'));

        // El acumulado es lo que el cliente quiere ver: cuánto lleva pagado.
        $this->assertSame([20000.0, 50000.0, 75000.0], $montos('accumulated'));

        // Y cuál es el abono de hoy, para resaltarlo.
        $this->assertSame([false, false, true], array_column($historial, 'is_current'));
    }

    #[Test]
    public function reimprimir_un_recibo_viejo_no_muestra_abonos_posteriores(): void
    {
        $primero = $this->pago(20000, now()->subDays(14)->toDateString());
        $this->pago(30000, now()->subDays(7)->toDateString());

        // Un comprobante entregado hace dos semanas tiene que seguir diciendo lo
        // mismo hoy: si mostrara pagos que aún no habían ocurrido, dejaría de
        // servir como soporte de lo que se entregó ese día.
        $historial = $this->recibo($primero)->assertOk()->json('history');

        $this->assertCount(1, $historial);
        $this->assertSame(20000.0, (float) $historial[0]['amount']);
    }

    #[Test]
    public function un_pago_anulado_no_aparece_en_el_papel_del_cliente(): void
    {
        $anulado = $this->pago(50000, now()->subDays(3)->toDateString());
        $vigente = $this->pago(25000, now()->toDateString());

        app(PaymentReverser::class)
            ->reverse($anulado, $this->supervisor, 'cuota mal ingresada');

        $historial = $this->recibo($vigente)->assertOk()->json('history');

        // Ni el pago anulado ni su asiento de reversión: en el papel del cliente
        // no caben movimientos que no fueron dinero recibido.
        $this->assertCount(1, $historial);
        $this->assertSame(25000.0, (float) $historial[0]['amount']);
        $this->assertSame(25000.0, (float) $historial[0]['accumulated']);
    }

    #[Test]
    public function el_cobrador_puede_emitir_el_recibo_de_un_pago_que_registro_el_supervisor(): void
    {
        // Caso real: el supervisor registró un abono en la oficina y el cliente le
        // pide el comprobante al cobrador. Este ya ve ese pago en el historial del
        // crédito, así que negarle el recibo sería incoherente.
        $delSupervisor = $this->pago(40000, now()->toDateString(), $this->supervisor);

        $this->recibo($delSupervisor)->assertOk();
    }

    #[Test]
    public function el_cobrador_no_puede_emitir_recibos_de_creditos_ajenos(): void
    {
        $otroCobrador = User::factory()->create(['company_id' => $this->company->id]);
        $otroCobrador->assignRole('collector');

        $pago = $this->pago(40000, now()->toDateString());

        // La frontera sigue siendo la de siempre: los créditos asignados a él.
        $this->recibo($pago, $otroCobrador)->assertNotFound();
    }
}
