<?php

declare(strict_types=1);

namespace Tests\Feature\Filament;

use App\Filament\Widgets\Collector\CollectorDueInstallmentsTable;
use App\Filament\Widgets\Supervisor\InstallmentsDueTodayTable;
use App\Filament\Widgets\Supervisor\OverdueInstallmentsTable;
use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\SupervisorCollector;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Livewire\Livewire;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Los widgets de cobro deben mostrar lo que el cliente paga, no el saldo del credito.
 *
 * `installments.principal_balance_after` es la columna de la TABLA DE AMORTIZACION: el saldo
 * de capital que le queda al credito DESPUES de esa cuota. No tiene relacion con
 * lo que se recauda ese dia.
 *
 * Tres widgets la mostraban cruda, etiquetada "Monto a Cobrar" / "Monto" /
 * "Monto Pendiente", y uno de ellos ademas ordenaba la lista del dia por ella.
 * En produccion el total del dia aparecia como $2.772.942 cuando lo real era
 * $1.002.200, y la lista de prioridades salia ordenada por el tamano del credito
 * en vez de por lo que hay que recaudar.
 *
 * El escenario de abajo reproduce la forma exacta de los datos reales.
 */
class CobroMontoWidgetsTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $collector;

    private Credit $credit;

    /** Cuota chica de un credito grande: el caso que destapaba el bug. */
    private Installment $cuotaDeHoy;

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

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

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

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

        $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,
        ]);

        // Cuota de hoy: el cliente paga $70.000, pero al credito le quedan
        // $450.000 de capital despues de ella. Los dos numeros conviven en la
        // misma fila, y el widget mostraba el equivocado.
        $this->cuotaDeHoy = 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 supervisor(): User
    {
        $supervisor = User::factory()->create(['company_id' => $this->company->id]);
        $supervisor->assignRole('supervisor');

        SupervisorCollector::create([
            'company_id' => $this->company->id,
            'supervisor_id' => $supervisor->id,
            'collector_id' => $this->collector->id,
        ]);

        return $supervisor;
    }

    #[Test]
    public function el_widget_del_dia_muestra_lo_que_se_cobra_no_el_saldo_del_credito(): void
    {
        $this->actingAs($this->supervisor());

        Livewire::test(InstallmentsDueTodayTable::class)
            ->assertCanSeeTableRecords([$this->cuotaDeHoy])
            ->assertTableColumnStateSet('remaining_amount', 70000.0, $this->cuotaDeHoy);
    }

    #[Test]
    public function el_widget_del_cobrador_muestra_lo_que_se_cobra(): void
    {
        $this->actingAs($this->collector);

        Livewire::test(CollectorDueInstallmentsTable::class)
            ->assertCanSeeTableRecords([$this->cuotaDeHoy])
            ->assertTableColumnStateSet('remaining_amount', 70000.0, $this->cuotaDeHoy);
    }

    #[Test]
    public function el_widget_de_mora_muestra_lo_que_falta_por_cobrar(): void
    {
        $vencida = Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $this->credit->id,
            'installment_number' => 2,
            'status' => Installment::STATUS_OVERDUE,
            'due_date' => now()->subDays(10)->toDateString(),
            'principal_amount' => 50000,
            'interest_amount' => 20000,
            'total_amount' => 70000,
            'amount_paid' => 30000,   // abono parcial: faltan 40.000
            'principal_balance_after' => 400000,
        ]);

        $this->actingAs($this->supervisor());

        Livewire::test(OverdueInstallmentsTable::class)
            ->assertCanSeeTableRecords([$vencida])
            ->assertTableColumnStateSet('remaining_amount', 40000.0, $vencida);
    }

    #[Test]
    public function el_abono_parcial_se_refleja_en_el_monto_a_cobrar(): void
    {
        $this->cuotaDeHoy->update([
            'amount_paid' => 25000,
            'status' => Installment::STATUS_PARTIAL_PAID,
        ]);

        $this->actingAs($this->collector);

        Livewire::test(CollectorDueInstallmentsTable::class)
            ->assertTableColumnStateSet('remaining_amount', 45000.0, $this->cuotaDeHoy->fresh());
    }

    #[Test]
    public function ningun_widget_vuelve_a_exponer_principal_balance_after_como_monto(): void
    {
        // Red de seguridad: la columna sigue existiendo y sigue siendo útil para la
        // amortización, así que nada impide que alguien la vuelva a poner en una
        // columna de dinero. Se vigila también el nombre viejo: puede reaparecer
        // copiando código de una rama anterior o de un ejemplo antiguo.
        $prohibidas = ['principal_balance_after', 'balance_due'];
        $widgets = glob(app_path('Filament/Widgets/*/*.php')) ?: [];

        $this->assertNotEmpty($widgets, 'no se encontraron widgets que revisar');

        $ofensores = [];
        foreach ($widgets as $archivo) {
            $codigo = (string) file_get_contents($archivo);
            foreach ($prohibidas as $columna) {
                if (str_contains($codigo, "make('{$columna}')")) {
                    $ofensores[] = basename($archivo).' → '.$columna;
                }
            }
        }

        $this->assertSame(
            [],
            $ofensores,
            'Estos widgets exponen el saldo de amortización como si fuera dinero a cobrar. '.
            'Es el capital que le queda al CRÉDITO después de la cuota, no lo que paga el '.
            'cliente: usar `remaining_amount`. Ofensores: '.implode(', ', $ofensores)
        );
    }
}
