<?php

namespace Tests\Feature\Monitoring;

use App\Livewire\Monitoring\Expirations;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Products\LicenseToken;
use App\Models\Products\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * Próximas caducidades: lo que va a cortar servicio.
 *
 * Solo entra lo que, al caducar, **deja a un cliente sin contenido**: licencias
 * («Token expirado», se pierde todo) y productos dentro de una licencia («Licence does
 * not include this plugin», se pierde ese plugin en todos sus entornos).
 *
 * **No entran** los soportes —un acuerdo comercial que no corta nada—, los tokens de
 * servicios web —el Manager no guarda su caducidad— ni los clientes de baja, cuyas
 * licencias ya están desactivadas.
 */
class ExpirationsTest extends TestCase
{
    use RefreshDatabase;

    private function usuario(bool $conPermiso = true): User
    {
        foreach (['admin.api-logs.index', 'admin.dashboard.index'] as $nombre) {
            Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'monitoring', 'description' => $nombre]
            );
        }

        $user = User::factory()->create();
        $user->givePermissionTo($conPermiso ? 'admin.api-logs.index' : 'admin.dashboard.index');
        $this->actingAs($user);
        Cache::flush();

        return $user;
    }

    private function cliente(bool $deAlta = true): Client
    {
        return Client::create([
            'name' => 'Cliente ' . uniqid(),
            'shortname' => 'c' . uniqid(),
            'actived' => $deAlta,
        ]);
    }

    private function licencia(Client $cliente, ?string $fin, bool $activa = true, string $nombre = 'Licencia X'): LicenseToken
    {
        $licencia = LicenseToken::create([
            'client_id' => $cliente->id,
            'name' => $nombre,
            'token' => '3IP-CAD-' . strtoupper(uniqid()),
            'active' => $activa,
            'start_at' => now()->subMonth(),
            'end_at' => $fin,
        ]);

        Environment::create([
            'client_id' => $cliente->id,
            'license_token_id' => $licencia->id,
            'name' => 'Sitio ' . uniqid(),
            'domain' => 'https://' . uniqid() . '.test',
            'version' => '5.1.1',
            'env' => 'pro',
            'active' => true,
        ]);

        return $licencia;
    }

    /** @return array<int, array<string, mixed>> */
    private function filas(): array
    {
        return Livewire::test(Expirations::class)->viewData('filas');
    }

    /* ===================== Acceso ===================== */

    public function test_sin_permiso_no_se_entra(): void
    {
        $this->usuario(conPermiso: false);

        $this->get(route('monitoring.expirations'))->assertForbidden();
    }

    /* ===================== Qué entra ===================== */

    public function test_lista_las_licencias_por_caducar(): void
    {
        $this->usuario();
        $this->licencia($this->cliente(), now()->addDays(20)->toDateTimeString(), nombre: 'La que caduca');

        $filas = $this->filas();

        $this->assertCount(1, $filas);
        $this->assertSame('Licencia', $filas[0]['tipo']);
        $this->assertSame('La que caduca', $filas[0]['que']);
        // Una licencia caducada corta TODO lo del cliente, y eso hay que decirlo.
        $this->assertStringContainsString('Todo el contenido', $filas[0]['alcance']);
    }

    public function test_lista_los_productos_con_fecha_propia(): void
    {
        // El caso que no avisaba nadie: un producto que caduca dentro de una licencia
        // vigente, con un mensaje que apunta a los productos contratados y no a la fecha.
        $this->usuario();
        $licencia = $this->licencia($this->cliente(), now()->addYears(2)->toDateTimeString());

        $producto = Product::create(['name' => 'Bloque SEPE', 'slug' => 'block_sepe', 'shortname' => 'sepe']);
        $licencia->products()->attach($producto->id, [
            'status' => 'active',
            'end_at' => now()->addDays(9),
        ]);

        $filas = $this->filas();

        $this->assertSame('Producto', $filas[0]['tipo']);
        $this->assertSame('Bloque SEPE', $filas[0]['que']);
        $this->assertSame('Solo ese plugin', $filas[0]['alcance']);
    }

    public function test_ordena_por_lo_que_caduca_antes(): void
    {
        $this->usuario();
        $this->licencia($this->cliente(), now()->addDays(60)->toDateTimeString(), nombre: 'La lejana');
        $this->licencia($this->cliente(), now()->addDays(3)->toDateTimeString(), nombre: 'La urgente');

        $filas = $this->filas();

        $this->assertSame('La urgente', $filas[0]['que']);
        $this->assertSame('La lejana', $filas[1]['que']);
    }

    public function test_dice_a_cuantos_entornos_afecta(): void
    {
        $this->usuario();
        $cliente = $this->cliente();
        $licencia = $this->licencia($cliente, now()->addDays(5)->toDateTimeString());

        // Un segundo entorno bajo la misma licencia.
        Environment::create([
            'client_id' => $cliente->id,
            'license_token_id' => $licencia->id,
            'name' => 'Segundo sitio',
            'domain' => 'https://' . uniqid() . '.test',
            'version' => '5.1.1',
            'env' => 'pro',
            'active' => true,
        ]);

        $this->assertSame(2, $this->filas()[0]['entornos']);
    }

    /* ===================== Qué NO entra ===================== */

    public function test_no_lista_lo_ya_caducado(): void
    {
        // Esta pantalla es «lo próximo»: lo vencido es otro problema y lo trata la tarea
        // de caducidad, que desactiva la licencia.
        $this->usuario();
        $this->licencia($this->cliente(), now()->subDays(5)->toDateTimeString());

        $this->assertSame([], $this->filas());
    }

    public function test_no_lista_licencias_sin_fecha(): void
    {
        $this->usuario();
        $this->licencia($this->cliente(), null);

        $this->assertSame([], $this->filas());
    }

    public function test_no_lista_licencias_ya_desactivadas(): void
    {
        $this->usuario();
        $this->licencia($this->cliente(), now()->addDays(10)->toDateTimeString(), activa: false);

        $this->assertSame([], $this->filas());
    }

    public function test_no_lista_lo_de_un_cliente_de_baja(): void
    {
        // Sus Moodles ya reciben «Cliente inactivo»: no hay nada que se vaya a cortar.
        $this->usuario();
        $cliente = $this->cliente();
        $this->licencia($cliente, now()->addDays(10)->toDateTimeString());

        $this->assertCount(1, $this->filas());

        $cliente->darDeBaja('Se va', null);

        $this->assertSame([], $this->filas());
    }

    public function test_no_lista_un_producto_quitado(): void
    {
        // Borrado en blando: la fila sigue en la tabla y no debe aparecer como algo que
        // vaya a caducar.
        $this->usuario();
        // Sin fecha en la licencia, para que la única fila posible sea la del producto.
        $licencia = $this->licencia($this->cliente(), null);

        $producto = Product::create(['name' => 'Quitado', 'slug' => 'block_quitado', 'shortname' => 'quit']);
        $licencia->products()->attach($producto->id, ['status' => 'active', 'end_at' => now()->addDays(9)]);

        $this->assertCount(1, $this->filas());

        \App\Models\Products\LicenseTokenProduct::where('license_token_id', $licencia->id)
            ->where('product_id', $producto->id)
            ->first()
            ->delete();

        $this->assertSame([], $this->filas());
    }

    /* ===================== El recorte ===================== */

    public function test_dice_cuantas_hay_en_total_y_no_solo_las_mostradas(): void
    {
        // Leer «15» como «15 en total» es el mismo error que el truncado silencioso del
        // Dashboard (MGR-030).
        $this->usuario();

        for ($i = 1; $i <= 17; $i++) {
            $this->licencia($this->cliente(), now()->addDays($i)->toDateTimeString());
        }

        $componente = Livewire::test(Expirations::class);

        $this->assertCount(15, $componente->viewData('filas'));
        $this->assertSame(17, $componente->viewData('total'));
    }

    public function test_se_puede_ampliar_el_recorte(): void
    {
        $this->usuario();

        for ($i = 1; $i <= 17; $i++) {
            $this->licencia($this->cliente(), now()->addDays($i)->toDateTimeString());
        }

        $this->assertCount(
            17,
            Livewire::test(Expirations::class)->set('cuantas', 30)->viewData('filas')
        );
    }
}
