<?php

namespace Tests\Feature\LicenseTokens;

use App\Livewire\LicenseTokens\EnvironmentAssociationModal;
use App\Livewire\LicenseTokens\Index;
use App\Livewire\LicenseTokens\LicenseStatusModal;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Monitoring\Log as MonitoringLog;
use App\Models\Products\LicenseToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * Listado de licencias y las dos modales nuevas, desde `Licencias.dc.html`.
 *
 * Lo que se fija: que «caducada» y «apagada» se puedan separar, que la columna de
 * entornos muestre algo (no lo hacía), que apagar una licencia deje rastro, y que
 * asociar un entorno diga a quién se lo quita.
 */
class LicenseListTest extends TestCase
{
    use RefreshDatabase;

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

        $permisos = [
            'admin.license-tokens.index',
            'admin.license-tokens.create',
            'admin.license-tokens.edit',
            'admin.license-tokens.destroy',
            'admin.license-tokens.manage-environments',
            'admin.clients.index',
            'admin.environments.index',
            'admin.environments.create',
        ];

        foreach ($permisos as $nombre) {
            Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'license_tokens', 'description' => $nombre]
            );
        }

        $user->givePermissionTo($permisos);
        $this->actingAs($user);
        Cache::flush();

        return $user;
    }

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

    private function licencia(array $extra = [], ?Client $cliente = null): LicenseToken
    {
        return LicenseToken::create(array_merge([
            'client_id' => ($cliente ?? $this->cliente())->id,
            'name' => 'Licencia de pruebas',
            'token' => '3IP-' . strtoupper(\Illuminate\Support\Str::random(4)) . '-' . strtoupper(\Illuminate\Support\Str::random(12)),
            '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' => '5.1.1',
            'client_id' => $cliente->id,
            'active' => true,
        ], $extra));
    }

    /* ===================== El listado ===================== */

    public function test_caducada_y_apagada_se_pueden_separar(): void
    {
        // Las dos cortan el servicio, pero una se renueva y la otra se vuelve a
        // encender: estaban en la misma columna sin forma de filtrarlas.
        $this->admin();
        $cliente = $this->cliente();

        $this->licencia(['name' => 'La caducada', 'end_at' => now()->subDays(10)], $cliente);
        $this->licencia(['name' => 'La apagada', 'active' => false, 'end_at' => now()->addYear()], $cliente);
        $this->licencia(['name' => 'La buena', 'end_at' => now()->addYear()], $cliente);

        $caducadas = Livewire::test(Index::class)->set('estado', 'caducadas')
            ->viewData('tokens')->pluck('name')->all();
        $apagadas = Livewire::test(Index::class)->set('estado', 'apagadas')
            ->viewData('tokens')->pluck('name')->all();
        $activas = Livewire::test(Index::class)->set('estado', 'activas')
            ->viewData('tokens')->pluck('name')->all();

        $this->assertSame(['La caducada'], $caducadas);
        $this->assertSame(['La apagada'], $apagadas);
        $this->assertSame(['La buena'], $activas);
    }

    public function test_una_licencia_apagada_y_caducada_cuenta_solo_como_apagada(): void
    {
        // Si contara en las dos, los números de la cabecera no sumarían.
        $this->admin();

        $this->licencia(['active' => false, 'end_at' => now()->subDays(5)]);

        $resumen = Livewire::test(Index::class)->viewData('resumen');

        $this->assertSame(1, $resumen['apagadas']);
        $this->assertSame(0, $resumen['caducadas']);
    }

    public function test_la_columna_de_entornos_muestra_el_numero(): void
    {
        // **Salía vacía en todas las filas** (MGR-055): la consulta calculaba
        // `environments_count` y la plantilla imprimía `sites_count`, que no existe.
        $this->admin();
        $cliente = $this->cliente();
        $licencia = $this->licencia([], $cliente);

        $this->entorno($cliente, 'uno.test', ['license_token_id' => $licencia->id]);
        $this->entorno($cliente, 'dos.test', ['license_token_id' => $licencia->id]);

        $fila = Livewire::test(Index::class)->viewData('tokens')->first();

        $this->assertSame(2, $fila->environments_count);
    }

    public function test_una_licencia_sin_entornos_se_marca_y_se_puede_filtrar(): void
    {
        // Un contrato firmado que nadie ha instalado: conversación comercial pendiente.
        $this->admin();
        $cliente = $this->cliente();

        $conUso = $this->licencia(['name' => 'En uso'], $cliente);
        $this->entorno($cliente, 'usa.test', ['license_token_id' => $conUso->id]);
        $this->licencia(['name' => 'Nadie la usa'], $cliente);

        $sinUso = Livewire::test(Index::class)->set('soloSinEntornos', true)
            ->viewData('tokens')->pluck('name')->all();

        $this->assertSame(['Nadie la usa'], $sinUso);
        $this->get(route('license_tokens.index'))->assertOk()->assertSee('Sin uso');
    }

    public function test_el_filtro_de_caduca_pronto_no_incluye_las_ya_caducadas(): void
    {
        // Rango cerrado: es la trampa de MGR-025, que hacía contar dos veces la misma.
        $this->admin();
        $cliente = $this->cliente();

        $this->licencia(['name' => 'Vence pronto', 'end_at' => now()->addDays(10)], $cliente);
        $this->licencia(['name' => 'Ya vencida', 'end_at' => now()->subDays(2)], $cliente);

        $pronto = Livewire::test(Index::class)->set('soloPronto', true)
            ->viewData('tokens')->pluck('name')->all();

        $this->assertSame(['Vence pronto'], $pronto);
    }

    public function test_el_token_recortado_conserva_el_prefijo_y_el_final(): void
    {
        // Un `Str::limit` por la izquierda deja varios tokens indistinguibles: el
        // prefijo es justo la parte que comparten.
        $cliente = $this->cliente('Cliente del token');
        $licencia = $this->licencia(['token' => 'DEMO-TPRXWHSCXOZFOZAW9WGZA2X5W3ZITQ7O'], $cliente);

        $this->assertSame('DEMO-TPRX…TQ7O', $licencia->token_corto);
        $this->assertSame('3IP-CORTO', $this->licencia(['token' => '3IP-CORTO'], $cliente)->token_corto);
    }

    public function test_la_busqueda_respeta_el_filtro_de_estado(): void
    {
        // El error que apareció en Clientes (MGR-054): los `orWhere` sueltos al lado del
        // `where` del estado hacen que el filtro deje de aplicarse.
        $this->admin();
        $cliente = $this->cliente();

        $this->licencia(['name' => 'Contrato norte', 'active' => true, 'end_at' => now()->addYear()], $cliente);
        $this->licencia(['name' => 'Contrato sur', 'active' => false], $cliente);

        $nombres = Livewire::test(Index::class)
            ->set('estado', 'activas')
            ->set('search', 'Contrato')
            ->viewData('tokens')->pluck('name')->all();

        $this->assertSame(['Contrato norte'], $nombres);
    }

    /* ===================== Apagar y encender ===================== */

    public function test_apagar_una_licencia_exige_motivo(): void
    {
        // Era un `toggleActive()`: un clic y todos los entornos dejaban de recibir
        // contenido, sin decir a cuántos afectaba y sin rastro de quién ni por qué.
        $this->admin();
        $licencia = $this->licencia();

        Livewire::test(LicenseStatusModal::class, ['licenseTokenId' => $licencia->id])
            ->set('motivo', 'no')
            ->call('desactivar')
            ->assertHasErrors(['motivo']);

        $this->assertTrue($licencia->refresh()->active);
    }

    public function test_apagar_deja_quien_cuando_y_por_que(): void
    {
        $user = $this->admin();
        $cliente = $this->cliente();
        $licencia = $this->licencia([], $cliente);
        $this->entorno($cliente, 'afectado.test', ['license_token_id' => $licencia->id]);

        Livewire::test(LicenseStatusModal::class, ['licenseTokenId' => $licencia->id])
            ->set('motivo', 'Impago de la cuota anual')
            ->call('desactivar')
            ->assertHasNoErrors();

        $licencia->refresh();

        $this->assertFalse($licencia->active);
        $this->assertNotNull($licencia->deactivated_at);
        $this->assertSame($user->id, $licencia->deactivated_by);
        $this->assertSame('Impago de la cuota anual', $licencia->deactivation_reason);

        // Y queda en el log del panel, con el número de entornos afectados.
        $log = MonitoringLog::where('code', '16022')->first();
        $this->assertNotNull($log);
        $this->assertStringContainsString('1 entorno(s)', $log->msg);
        $this->assertStringContainsString('Impago de la cuota anual', $log->msg);
    }

    public function test_encender_una_licencia_caducada_avisa_de_que_sigue_sin_servir(): void
    {
        // La trampa del botón: el servicio mira las dos cosas —activa y en fecha—, así
        // que encenderla no la revive. Sin decirlo, alguien pulsa y se va.
        $this->admin();
        $licencia = $this->licencia(['active' => false, 'end_at' => now()->subDays(20)]);

        Livewire::test(LicenseStatusModal::class, ['licenseTokenId' => $licencia->id])
            ->call('reactivar');

        $this->assertTrue($licencia->refresh()->active);
        // El log lo dice: es el rastro que queda cuando el aviso de pantalla ya se fue.
        $this->assertStringContainsString(
            'CADUCADA',
            MonitoringLog::where('code', '16022')->latest('id')->value('msg')
        );
    }

    public function test_encender_limpia_el_registro_del_apagado(): void
    {
        // Si no, la ficha seguiría diciendo «apagada por…» de una licencia encendida.
        $user = $this->admin();
        $licencia = $this->licencia([
            'active' => false,
            'end_at' => now()->addYear(),
            'deactivated_at' => now()->subDay(),
            'deactivated_by' => $user->id,
            'deactivation_reason' => 'Prueba terminada',
        ]);

        Livewire::test(LicenseStatusModal::class, ['licenseTokenId' => $licencia->id])
            ->call('reactivar');

        $licencia->refresh();

        $this->assertTrue($licencia->active);
        $this->assertNull($licencia->deactivated_at);
        $this->assertNull($licencia->deactivated_by);
        $this->assertNull($licencia->deactivation_reason);
    }

    /* ===================== Asociar un entorno ===================== */

    public function test_solo_se_ofrecen_entornos_del_mismo_cliente(): void
    {
        // Asociar el de otro cliente se lo quita a su token y lo deja sin licencia
        // (MGR-004). Ofrecer lo que después se rechaza no ayuda a nadie.
        $this->admin();

        $propio = $this->cliente('Cliente propio');
        $ajeno = $this->cliente('Cliente ajeno');
        $licencia = $this->licencia([], $propio);

        $this->entorno($propio, 'mio.test');
        $this->entorno($ajeno, 'ajeno.test');

        $dominios = Livewire::test(EnvironmentAssociationModal::class, ['licenseTokenId' => $licencia->id])
            ->viewData('candidatos')->pluck('domain')->all();

        $this->assertSame(['https://mio.test'], $dominios);
    }

    public function test_asociar_un_entorno_que_ya_tenia_licencia_avisa_del_cambio(): void
    {
        // Un entorno apunta a UNA licencia: asociarlo aquí lo saca de la que tenía, y
        // eso es lo que nadie ve venir.
        $this->admin();
        $cliente = $this->cliente();

        $vieja = $this->licencia(['name' => 'La de antes'], $cliente);
        $nueva = $this->licencia(['name' => 'La nueva'], $cliente);
        $entorno = $this->entorno($cliente, 'cambia.test', ['license_token_id' => $vieja->id]);

        Livewire::test(EnvironmentAssociationModal::class, ['licenseTokenId' => $nueva->id])
            ->call('elegir', $entorno->id)
            ->call('asociar');

        $this->assertSame($nueva->id, $entorno->refresh()->license_token_id);
        // Y la vieja se queda sin ese entorno: es la consecuencia real del cambio.
        $this->assertSame(0, $vieja->environments()->count());
    }

    public function test_sin_ninguno_elegido_el_boton_no_hace_nada(): void
    {
        $this->admin();
        $cliente = $this->cliente();
        $licencia = $this->licencia([], $cliente);
        $entorno = $this->entorno($cliente, 'libre.test');

        Livewire::test(EnvironmentAssociationModal::class, ['licenseTokenId' => $licencia->id])
            ->call('asociar');

        $this->assertNull($entorno->refresh()->license_token_id);
    }

    public function test_desasociar_pasa_por_la_modal_del_panel(): void
    {
        // Era un `wire:confirm`: la alerta del navegador, un cuadro gris del sistema
        // que no puede decir que ese sitio se queda **sin licencia**.
        $this->admin();
        $cliente = $this->cliente();
        $licencia = $this->licencia([], $cliente);
        $entorno = $this->entorno($cliente, 'sale.test', ['license_token_id' => $licencia->id]);

        $componente = Livewire::test(\App\Livewire\LicenseTokens\Show::class, ['license_token' => $licencia]);

        // Pedirlo abre la modal y **no** quita nada todavía.
        $componente->call('pedirDesasociar', $entorno->id)->assertDispatched('openModal');
        $this->assertSame($licencia->id, $entorno->refresh()->license_token_id);

        // Y el receptor va sin parámetros tipados: la modal despacha un array (MGR-042).
        $componente->dispatch('environmentUnlinkConfirmed', ['itemId' => $entorno->id]);

        $this->assertNull($entorno->refresh()->license_token_id);
    }
}
