<?php

namespace Tests\Feature\Environments;

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

/**
 * Por dónde se entra a cada Moodle.
 *
 * **El caso real.** Un sitio con SSO —SAML, CAS, OAuth2— manda a todo el mundo al proveedor
 * de identidad en cuanto se pulsa «Entrar», y nosotros no tenemos cuenta ahí. La forma de
 * entrar con la cuenta manual de soporte es una URL distinta que **cada sitio tiene a su
 * manera**, y hasta ahora vivía en la cabeza de quien lo configuró.
 *
 * Lo que se fija aquí:
 *
 * 1. Que el campo sea **solo para las excepciones**: sin nada puesto, el panel construye el
 *    de siempre. Rellenarlo en los sitios normales serían veinte copias del mismo dato.
 * 2. Que **no se pueda colar un `javascript:`**. Esto se pinta como enlace en el panel: la
 *    validación `url` por defecto de Laravel acepta cualquier esquema, y con uno de esos
 *    dentro cualquiera con permiso de edición deja un clic que ejecuta código en la sesión
 *    de quien lo pulse.
 */
class EnlaceDeLoginTest extends TestCase
{
    use RefreshDatabase;

    private function admin(): User
    {
        $rol = Role::firstOrCreate(['name' => 'RolDeLogin', 'guard_name' => 'web']);

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

        $usuario = User::factory()->create();
        $usuario->assignRole($rol);
        $this->actingAs($usuario);

        Cache::flush();

        return $usuario;
    }

    private function entorno(array $atributos = []): Environment
    {
        $cliente = Client::firstOrCreate(['shortname' => 'log'], ['name' => 'Cliente', 'actived' => true]);

        return Environment::create(array_merge([
            'name' => 'Campus',
            'version' => '5.1.1',
            'domain' => 'https://campus.cliente.test',
            'env' => 'pro',
            'active' => true,
            'client_id' => $cliente->id,
        ], $atributos));
    }

    public function test_sin_nada_puesto_se_construye_el_de_siempre(): void
    {
        // La inmensa mayoría de los sitios entran por aquí, y el panel ya lo sabe: pedir que
        // se rellene en los veinte normales sería copiar el mismo dato veinte veces y tener
        // veinte sitios donde se desincronice el día que alguien cambie un dominio.
        $entorno = $this->entorno();

        $this->assertSame('https://campus.cliente.test/login/index.php', $entorno->enlaceDeLogin());
        $this->assertFalse($entorno->tieneLoginPropio());
    }

    public function test_la_barra_final_del_dominio_no_duplica_la_del_enlace(): void
    {
        // Los dominios se guardan normalizados, pero no todos: el normalizador quita la
        // barra al guardar desde el formulario y hay filas anteriores a eso.
        $entorno = $this->entorno(['domain' => 'https://campus.cliente.test/']);

        $this->assertSame('https://campus.cliente.test/login/index.php', $entorno->enlaceDeLogin());
    }

    public function test_con_enlace_propio_se_usa_ese_y_se_dice_que_lo_es(): void
    {
        // Y se dice, porque quien lo ve tiene que saber que ahí hay una decisión de alguien
        // y no algo que el panel se ha inventado.
        $entorno = $this->entorno(['login_url' => 'https://campus.cliente.test/login/index.php?saml=off']);

        $this->assertSame('https://campus.cliente.test/login/index.php?saml=off', $entorno->enlaceDeLogin());
        $this->assertTrue($entorno->tieneLoginPropio());
    }

    public function test_un_entorno_sin_dominio_no_ofrece_ningun_enlace(): void
    {
        // Un `/login/index.php` a secas llevaría al propio panel, que es peor que no ofrecer
        // nada: parece que funciona.
        //
        // Sin fila en la base: la columna es NOT NULL, así que este caso no llega de un
        // `select` — llega de un modelo a medio construir, que es donde de verdad puede
        // pasar.
        $this->assertNull((new Environment(['name' => 'A medias']))->enlaceDeLogin());
    }

    /* ===================== El logo de la plataforma ===================== */

    public function test_el_listado_ensena_el_logo_de_la_plataforma(): void
    {
        // **Un Moodle, un Workplace, un Goodle y un WordPress no se administran igual**, y
        // ese dato viajaba en un texto que compartía línea con la release: «Moodle Workplace
        // 4.5.2+» se lee como una versión larga y no como dos cosas distintas.
        $this->admin();

        $tipo = \App\Models\Environments\Type::create([
            'name' => 'Moodle Workplace',
            'shortname' => 'workplace',
            'image' => 'images_static/environments/workplace.svg',
        ]);

        $this->entorno(['type_id' => $tipo->id]);

        Livewire::test(\App\Livewire\Environments\Index::class)
            ->assertSee('images_static/environments/workplace.svg', false)
            // El nombre no se va: el logo es el atajo, no el sustituto.
            ->assertSee('Moodle Workplace');
    }

    public function test_un_tipo_sin_imagen_no_deja_un_hueco(): void
    {
        // Ni un icono roto: se queda el texto solo, que es lo que había antes.
        $this->admin();

        $tipo = \App\Models\Environments\Type::create(['name' => 'Plataforma rara', 'shortname' => 'rara']);
        $this->entorno(['type_id' => $tipo->id]);

        Livewire::test(\App\Livewire\Environments\Index::class)
            ->assertSee('Plataforma rara')
            ->assertDontSee('images_static/environments', false);
    }

    /* ===================== El formulario ===================== */

    public function test_se_guarda_desde_la_edicion(): void
    {
        $this->admin();
        $entorno = $this->entorno();

        Livewire::test(EnvironmentsEdit::class, ['environment' => $entorno])
            ->set('loginUrl', 'https://campus.cliente.test/login/index.php?authCAS=NOCAS')
            ->call('update');

        $this->assertSame(
            'https://campus.cliente.test/login/index.php?authCAS=NOCAS',
            $entorno->fresh()->login_url
        );
    }

    public function test_vaciarlo_vuelve_al_de_siempre(): void
    {
        // Vacío se guarda como null, no como cadena vacía: las dos significan lo mismo aquí
        // —«el de siempre»— y tener las dos son dos formas de decirlo que acaban divergiendo.
        $this->admin();
        $entorno = $this->entorno(['login_url' => 'https://otro.test/entrar']);

        Livewire::test(EnvironmentsEdit::class, ['environment' => $entorno])
            ->set('loginUrl', '')
            ->call('update');

        $entorno->refresh();

        $this->assertNull($entorno->login_url);
        $this->assertSame('https://campus.cliente.test/login/index.php', $entorno->enlaceDeLogin());
    }

    /**
     * @dataProvider esquemasPeligrososProvider
     */
    public function test_no_se_puede_guardar_un_esquema_que_no_sea_http(string $peligroso): void
    {
        // **El que de verdad importa.** Esto se pinta como un `<a href>` en tres pantallas
        // del panel. La regla `url` de Laravel, a secas, acepta cualquier esquema: con un
        // `javascript:` dentro, cualquiera con permiso de edición deja un clic que ejecuta
        // código en la sesión de quien lo pulse —y quien lo pulsa suele ser un Admin—.
        $this->admin();
        $entorno = $this->entorno();

        Livewire::test(EnvironmentsEdit::class, ['environment' => $entorno])
            ->set('loginUrl', $peligroso)
            ->call('update')
            ->assertHasErrors('loginUrl');

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

    public static function esquemasPeligrososProvider(): array
    {
        return [
            'javascript' => ['javascript:alert(document.cookie)'],
            'data' => ['data:text/html,<script>alert(1)</script>'],
            'file' => ['file:///etc/passwd'],
            'sin esquema' => ['campus.cliente.test/login'],
        ];
    }
}
