<?php

namespace Tests\Feature\Clients;

use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Products\LicenseToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * Listado de clientes, implementado desde `Clientes.dc.html` (Claude Design).
 *
 * Lo que se fija aquí es lo que el rediseño arregla, no la maquetación: que la búsqueda
 * respete la pestaña, que «sin licencias» y «licencias desactivadas» no sean lo mismo, y
 * que los contadores de entornos distingan la avería del alta sin terminar.
 */
class ClientListTest extends TestCase
{
    use RefreshDatabase;

    private function admin(): User
    {
        $user = User::factory()->create();

        foreach (['admin.clients.index', 'admin.clients.create', 'admin.clients.edit'] as $nombre) {
            Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'clients', 'description' => $nombre]
            );
        }

        $user->givePermissionTo(['admin.clients.index', 'admin.clients.create', 'admin.clients.edit']);
        $this->actingAs($user);

        return $user;
    }

    private function cliente(string $nombre, array $extra = []): Client
    {
        return Client::create(array_merge([
            'name' => $nombre,
            'shortname' => \Illuminate\Support\Str::slug($nombre),
            'actived' => true,
        ], $extra));
    }

    private function licencia(Client $cliente, array $extra = []): LicenseToken
    {
        return LicenseToken::create(array_merge([
            'client_id' => $cliente->id,
            'name' => 'Licencia de ' . $cliente->shortname,
            'token' => '3IP-' . strtoupper(\Illuminate\Support\Str::random(6)),
            'active' => true,
        ], $extra));
    }

    private function entorno(Client $cliente, string $dominio, array $extra = []): Environment
    {
        return Environment::create(array_merge([
            'name' => $dominio,
            'domain' => 'https://' . $dominio,
            'env' => 'pro',
            // `version` es NOT NULL en el esquema.
            'version' => '5.1.1',
            'client_id' => $cliente->id,
            'active' => true,
        ], $extra));
    }

    /* ===================== La búsqueda y las pestañas ===================== */

    public function test_buscar_desde_la_pestana_de_alta_no_saca_a_los_de_baja(): void
    {
        // **Este era un error de verdad.** Los cinco `orWhere` de la búsqueda iban al
        // mismo nivel que el `where('actived', …)` de la pestaña, y `AND` liga más fuerte
        // que `OR`: la condición acababa siendo «(de alta Y nombre) O shortname O …», así
        // que buscando desde «De alta» aparecían también los clientes dados de baja.
        $this->admin();

        $this->cliente('Constructora Norte');
        $deBaja = $this->cliente('Constructora Sur');
        $deBaja->darDeBaja('Fin de contrato');

        $respuesta = \Livewire\Livewire::test(\App\Livewire\Clients\Index::class)
            ->set('search', 'Constructora');

        $nombres = $respuesta->viewData('clients')->pluck('name')->all();

        $this->assertContains('Constructora Norte', $nombres);
        $this->assertNotContains('Constructora Sur', $nombres);
    }

    public function test_la_pestana_de_baja_solo_trae_los_de_baja(): void
    {
        $this->admin();

        $this->cliente('Sigue de alta');
        $this->cliente('Se fue')->darDeBaja('Migración a plataforma propia');

        $nombres = \Livewire\Livewire::test(\App\Livewire\Clients\Index::class)
            ->set('estado', 'baja')
            ->viewData('clients')
            ->pluck('name')
            ->all();

        $this->assertSame(['Se fue'], $nombres);
    }

    public function test_se_busca_tambien_por_cif_y_razon_social(): void
    {
        // El marcador del buscador los promete, así que tienen que funcionar.
        $this->admin();

        $this->cliente('Cliente con CIF', ['cif' => 'B-12345678', 'razon_social' => 'Cosas del Norte S.L.']);
        $this->cliente('Otro cualquiera');

        $porCif = \Livewire\Livewire::test(\App\Livewire\Clients\Index::class)
            ->set('search', 'B-12345678')
            ->viewData('clients')->pluck('name')->all();

        $porRazon = \Livewire\Livewire::test(\App\Livewire\Clients\Index::class)
            ->set('search', 'Cosas del Norte')
            ->viewData('clients')->pluck('name')->all();

        $this->assertSame(['Cliente con CIF'], $porCif);
        $this->assertSame(['Cliente con CIF'], $porRazon);
    }

    /* ===================== El estado de las licencias ===================== */

    public function test_sin_licencias_y_licencias_desactivadas_no_son_lo_mismo(): void
    {
        // Era el problema 2 del brief: `getLicenseStatus()` devolvía «desactivada» para
        // las dos cosas, y en la base real son 18 de 20 clientes en la misma cesta.
        $this->admin();

        $nunca = $this->cliente('Nunca tuvo licencia');
        $apagadas = $this->cliente('Se le apagaron');
        $this->licencia($apagadas, ['active' => false]);

        $this->assertSame('nunca', $nunca->refresh()->load('tokens')->estadoDeLasLicencias());
        $this->assertSame('desactivada', $apagadas->refresh()->load('tokens')->estadoDeLasLicencias());

        $this->get(route('clients.index'))
            ->assertOk()
            ->assertSee('Sin licencias')
            ->assertSee('Desactivadas');
    }

    public function test_el_estado_agregado_va_por_lo_peor(): void
    {
        // Un cliente con nueve licencias bien y una vencida tiene un problema, y
        // promediarlo lo esconde.
        $this->admin();

        $cliente = $this->cliente('Mezcla');
        $this->licencia($cliente, ['end_at' => now()->addYear()]);
        $this->licencia($cliente, ['end_at' => now()->subDay()]);

        $cliente = $cliente->refresh()->load('tokens');

        $this->assertSame('vencida', $cliente->estadoDeLasLicencias());
        // Y con estados mezclados, la etiqueta es el desglose y no una palabra sola.
        $this->assertStringContainsString('1 activa', $cliente->etiquetaDeLasLicencias());
        $this->assertStringContainsString('1 vencida', $cliente->etiquetaDeLasLicencias());
    }

    public function test_con_todas_igual_la_etiqueta_lleva_el_numero(): void
    {
        $this->admin();

        $cliente = $this->cliente('Todas bien');
        $this->licencia($cliente, ['end_at' => now()->addYear()]);
        $this->licencia($cliente, ['end_at' => null]);

        $this->assertSame('2 · Activas', $cliente->refresh()->load('tokens')->etiquetaDeLasLicencias());
    }

    public function test_una_licencia_que_vence_en_un_mes_no_cuenta_como_vencida(): void
    {
        // Rango cerrado, que es la trampa de MGR-025: sin cota inferior una vencida
        // saldría también en «vence pronto» y se contaría dos veces.
        $this->admin();

        $cliente = $this->cliente('Vence pronto');
        $token = $this->licencia($cliente, ['end_at' => now()->addDays(10)]);

        $this->assertSame('pronto', $token->estado());
        $this->assertSame('vencida', $this->licencia($cliente, ['end_at' => now()->subDay()])->estado());
        // Desactivada gana a cualquier fecha: ya no da servicio.
        $this->assertSame('desactivada', $this->licencia($cliente, ['active' => false, 'end_at' => now()->addYear()])->estado());
        // Y sin fecha de fin es activa, no caducada.
        $this->assertSame('activa', $this->licencia($cliente, ['end_at' => null])->estado());
    }

    /* ===================== Los entornos ===================== */

    public function test_distingue_sin_senal_de_nunca_sincronizado(): void
    {
        // Misma distinción que en el listado de Entornos: «nunca sincronizado» es un alta
        // sin terminar y «sin señal» es una avería. Mezclarlos manda a Sistemas a mirar
        // un servidor que nunca se conectó.
        $this->admin();

        $averiado = $this->cliente('Con averia');
        $this->entorno($averiado, 'callado.test', ['refresh_at' => now()->subDays(9)]);

        $aMedias = $this->cliente('Alta sin terminar');
        $this->entorno($aMedias, 'nuevo.test', ['refresh_at' => null]);

        $sano = $this->cliente('Todo bien');
        $this->entorno($sano, 'ok.test', ['refresh_at' => now()->subHour()]);

        $clientes = \Livewire\Livewire::test(\App\Livewire\Clients\Index::class)
            ->viewData('clients')
            ->keyBy('name');

        $this->assertSame(1, $clientes['Con averia']->sin_senal_count);
        $this->assertSame(0, $clientes['Con averia']->sin_sincronizar_count);

        $this->assertSame(0, $clientes['Alta sin terminar']->sin_senal_count);
        $this->assertSame(1, $clientes['Alta sin terminar']->sin_sincronizar_count);

        $this->assertSame(0, $clientes['Todo bien']->sin_senal_count);
        $this->assertSame(0, $clientes['Todo bien']->sin_sincronizar_count);

        $this->get(route('clients.index'))
            ->assertOk()
            ->assertSee('1 sin señal')
            ->assertSee('1 sin sincronizar');
    }

    /* ===================== El orden y la cabecera ===================== */

    public function test_el_cliente_con_actividad_va_antes_que_el_vacio(): void
    {
        // Por actividad y no alfabético: con 18 de 20 vacíos, el orden alfabético
        // entierra a los que tienen algo. Los entornos pesan doble.
        $this->admin();

        $vacio = $this->cliente('Aaa Vacio');
        $conCosas = $this->cliente('Zzz Con cosas');
        $this->licencia($conCosas);
        $this->entorno($conCosas, 'algo.test');

        $nombres = \Livewire\Livewire::test(\App\Livewire\Clients\Index::class)
            ->viewData('clients')
            ->pluck('name')
            ->all();

        $this->assertSame(['Zzz Con cosas', 'Aaa Vacio'], $nombres);
        $this->assertNotNull($vacio->id);
    }

    public function test_la_cabecera_describe_la_pantalla_y_no_cuenta_filas(): void
    {
        // **Regla del proyecto**: la descripción dice qué es la pantalla y qué se puede
        // hacer, no un recuento. Los números van en la barra de filtros, donde se usan
        // para filtrar. Se pidió primero para Entornos y aplica a todas.
        $this->admin();

        $conCosas = $this->cliente('Trabajando');
        $this->licencia($conCosas);
        $this->cliente('Vacio uno');
        $this->cliente('Vacio dos');

        $respuesta = $this->get(route('clients.index'))->assertOk();

        // Describe: qué es y qué se puede hacer.
        $respuesta->assertSee('Quién nos compra');
        $respuesta->assertSee('para darlo de baja');

        // Y el recuento sigue estando, pero donde toca: en los filtros. Va en dos
        // trozos porque el número lleva su propio `<strong>`, así que se comprueban
        // en orden en vez de como una cadena suelta.
        $respuesta->assertSeeInOrder(['>3<', 'clientes'], false);
    }

    public function test_la_baja_se_lee_entera_en_el_listado(): void
    {
        // Antes la etiqueta «DE BAJA» salía, pero el motivo y el responsable solo se
        // veían entrando a la ficha.
        $user = $this->admin();

        $cliente = $this->cliente('Ajuntament de Prova');
        $cliente->darDeBaja('Fin del contrato marco', $user->id);

        $this->get(route('clients.index', ['estado' => 'baja']))
            ->assertOk()
            ->assertSee('DE BAJA')
            ->assertSee('Fin del contrato marco')
            ->assertSee($user->name);
    }

    /* ===================== El paginado ===================== */

    public function test_el_paginado_esta_en_espanol_y_no_repite_el_resumen(): void
    {
        // La vista por defecto de Livewire decía «Showing 1 to 15 of 18 results» con
        // colores de Tailwind a pelo, y el listado pintaba además su propio
        // «Mostrando…»: el resumen salía dos veces y en dos idiomas. Ahora lo pinta
        // solo la vista publicada, que es la que usan los dieciséis listados.
        $this->admin();

        for ($i = 1; $i <= 18; $i++) {
            $this->cliente('Cliente numero ' . $i);
        }

        $respuesta = $this->get(route('clients.index'))->assertOk();

        $respuesta->assertSee('Mostrando');
        $respuesta->assertDontSee('Showing');
        $respuesta->assertDontSee('results');
        $respuesta->assertSee('Paginación');
        // Y una sola vez: dos «Mostrando» era el fallo.
        $this->assertSame(1, substr_count($respuesta->getContent(), 'Mostrando'));
    }
}
