<?php

namespace Tests\Feature\Environments;

use App\Livewire\Environments\Edit as EntornoEdit;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Support\SugerenciasDeClientes;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * El selector de cliente del formulario de un entorno (MGR-098).
 *
 * **El fallo que arregla**: era un `<select>` con `Client::orderBy('name')->get()`
 * **escrito dentro de la plantilla**, así que la tabla entera se leía y se serializaba en
 * cada `render()`. Y era la última pantalla viva que elegía cliente así: las de licencias
 * ya buscaban contra el servidor con paginación.
 *
 * La consulta vive en `SugerenciasDeClientes` porque el mismo selector está en el visor de
 * peticiones. Lo que no puede divergir es **por qué campos busca y cuántas devuelve**: dos
 * copias acaban con una pantalla que encuentra un cliente y otra que no.
 */
class SelectorDeClienteTest extends TestCase
{
    use RefreshDatabase;

    private function editor(): User
    {
        // La tabla `permissions` de este proyecto tiene `model` NOT NULL.
        Permission::firstOrCreate(
            ['name' => 'admin.environments.edit', 'guard_name' => 'web'],
            ['model' => 'environments', 'description' => 'Entornos: Editar']
        );

        $usuario = User::factory()->create();
        $usuario->givePermissionTo('admin.environments.edit');
        $this->actingAs($usuario);

        Cache::flush();

        return $usuario;
    }

    private function cliente(string $nombre, ?string $shortname = null): Client
    {
        return Client::create([
            'name' => $nombre,
            'shortname' => $shortname ?? Str::slug($nombre),
            'actived' => true,
        ]);
    }

    private function entorno(?Client $cliente = null): Environment
    {
        return Environment::create([
            'client_id' => $cliente?->id,
            'name' => 'Campus de prueba',
            'domain' => 'https://campus-' . Str::random(6) . '.example.test',
            'version' => '5.1.1',
            'env' => 'pro',
            'active' => true,
        ]);
    }

    public function test_la_plantilla_ya_no_recibe_la_tabla_de_clientes(): void
    {
        // Es el fallo, y se comprueba donde estaba: en lo que el componente le pasa a la
        // vista.
        $this->editor();

        Livewire::test(EntornoEdit::class, ['environment' => $this->entorno()])
            ->assertViewMissing('clients');
    }

    public function test_ninguna_plantilla_del_panel_consulta_un_modelo(): void
    {
        // **El barrido.** Lo que hay que impedir es que vuelva a aparecer una consulta
        // dentro de un Blade: es lo que hacía que cada tecleo arrastrara una tabla.
        //
        // **Solo consultas.** Una constante de modelo en una plantilla —`Log::NIVELES`,
        // `Environment::ENVIRONMENTS`— es la forma documentada de compartir un vocabulario
        // con la vista y no cuesta nada; prohibirlas de paso habría hecho el test
        // insoportable y se habría acabado borrando.
        $consulta = '(?:query|where|orderBy|all|get|find|first|count|pluck|latest|when)';

        // **Una excepción, y con su motivo.** `monitoring/components/menu.blade.php` es un
        // componente Blade sin clase —`@props`, no hay `app/Livewire/.../Menu.php`—, así
        // que no hay componente al que mover la consulta. Y son los contadores de las
        // pestañas, cacheados 60 segundos y documentados en el propio fichero. Si algún
        // día se le hace clase, esta línea se quita.
        $permitidos = ['monitoring' . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'menu.blade.php'];

        $culpables = [];

        $vistas = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator(resource_path('views'))
        );

        foreach ($vistas as $fichero) {
            if (! str_ends_with($fichero->getFilename(), '.blade.php')) {
                continue;
            }

            foreach ($permitidos as $permitido) {
                if (str_ends_with($fichero->getPathname(), $permitido)) {
                    continue 2;
                }
            }

            foreach (file($fichero->getPathname()) as $n => $linea) {
                if (preg_match('/\\\\App\\\\Models\\\\\w+\\\\\w+::' . $consulta . '\b/', $linea)) {
                    $culpables[] = str_replace(resource_path('views'), 'views', $fichero->getPathname()) . ':' . ($n + 1);
                }
            }
        }

        $this->assertSame(
            [],
            $culpables,
            "Hay una plantilla que consulta un modelo directamente:\n  " . implode("\n  ", $culpables)
        );
    }

    public function test_busca_por_nombre_y_por_shortname(): void
    {
        // Por shortname porque quien lo tiene en la cabeza lo escribe, y es lo que ya
        // hacían los buscadores de cliente de las pantallas de licencias.
        $this->editor();

        $this->cliente('Ayuntamiento de Reus', 'reus');
        $this->cliente('Universitat de Girona', 'udg');

        $componente = Livewire::test(EntornoEdit::class, ['environment' => $this->entorno()]);

        $porNombre = $componente->set('clientSearch', 'girona')->instance()->sugerenciasDeClientes()['filas'];
        $this->assertCount(1, $porNombre);
        $this->assertSame('Universitat de Girona', $porNombre->first()->name);

        $porShortname = $componente->set('clientSearch', 'reus')->instance()->sugerenciasDeClientes()['filas'];
        $this->assertCount(1, $porShortname);
        $this->assertSame('Ayuntamiento de Reus', $porShortname->first()->name);
    }

    public function test_sin_escribir_nada_se_ofrecen_los_primeros(): void
    {
        // Es un selector: abrirlo y no ver nada hasta escribir dos letras sería peor que
        // el desplegable completo que había.
        $this->editor();

        $this->cliente('Un cliente');

        $sugerencias = Livewire::test(EntornoEdit::class, ['environment' => $this->entorno()])
            ->instance()
            ->sugerenciasDeClientes();

        $this->assertCount(1, $sugerencias['filas']);
    }

    public function test_se_recorta_a_doce_y_se_dice_cuantos_quedan(): void
    {
        $this->editor();

        for ($i = 1; $i <= 20; $i++) {
            $this->cliente('Cliente ' . str_pad((string) $i, 2, '0', STR_PAD_LEFT));
        }

        $sugerencias = Livewire::test(EntornoEdit::class, ['environment' => $this->entorno()])
            ->instance()
            ->sugerenciasDeClientes();

        $this->assertCount(SugerenciasDeClientes::CUANTAS, $sugerencias['filas']);
        $this->assertSame(20, $sugerencias['total'], 'Hay que saber cuántos quedan fuera.');
    }

    public function test_elegir_un_cliente_lo_pone_y_cierra_el_buscador(): void
    {
        $this->editor();

        $cliente = $this->cliente('Un cliente');

        Livewire::test(EntornoEdit::class, ['environment' => $this->entorno()])
            ->set('clientSearch', 'clien')
            ->call('elegirCliente', $cliente->id)
            ->assertSet('client_id', $cliente->id)
            // Dejar escrito lo que se buscaba confunde con lo que está puesto.
            ->assertSet('clientSearch', '');
    }

    public function test_se_puede_quitar_el_cliente(): void
    {
        // **`null` es un valor válido**: un entorno puede no tener cliente, y el
        // desplegable tenía su «Sin cliente».
        $this->editor();

        $cliente = $this->cliente('Un cliente');
        $entorno = $this->entorno($cliente);

        Livewire::test(EntornoEdit::class, ['environment' => $entorno])
            ->assertSet('client_id', $cliente->id)
            ->call('elegirCliente', null)
            ->assertSet('client_id', null)
            ->call('update')
            ->assertHasNoErrors();

        $this->assertNull($entorno->fresh()->client_id);
    }

    public function test_el_cliente_elegido_se_puede_pintar_sin_consultar_en_la_vista(): void
    {
        $this->editor();

        $cliente = $this->cliente('Un cliente');

        $elegido = Livewire::test(EntornoEdit::class, ['environment' => $this->entorno($cliente)])
            ->instance()
            ->clienteElegido();

        $this->assertNotNull($elegido);
        $this->assertSame('Un cliente', $elegido->name);
    }

    public function test_guardar_con_un_cliente_elegido_lo_asocia(): void
    {
        // De punta a punta: que el selector nuevo siga alimentando la validación
        // `exists:clients,id` y el guardado.
        $this->editor();

        $cliente = $this->cliente('Un cliente');
        $entorno = $this->entorno();

        Livewire::test(EntornoEdit::class, ['environment' => $entorno])
            ->call('elegirCliente', $cliente->id)
            ->call('update')
            ->assertHasNoErrors();

        $this->assertSame($cliente->id, $entorno->fresh()->client_id);
    }
}
