<?php

namespace Tests\Feature\Environments;

use App\Livewire\Environments\MoodleConnectionModal;
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 Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * Poner a mano el token de servicios web del Moodle de un cliente.
 *
 * **El hueco que tapa.** El Manager ya sabía pedirle a un Moodle que se sincronizara,
 * pero solo si ese sitio le había dado antes su token —y el token llegaba únicamente
 * dentro del primer `sync`, que el plugin manda con su tarea programada **una vez al
 * día, sobre la 01:00**—. En un alta nueva: se da de alta todo, se instala el plugin, la
 * licencia se valida al momento… y en el Manager no hay plugins ni datos hasta la
 * madrugada, sin ninguna forma de adelantarlo.
 *
 * Y como es una **credencial de administración del Moodle de un tercero** (por eso se
 * cifró en reposo, MGR-038), lo que este fichero fija sobre todo es que no se filtre:
 * ni en la pantalla, ni en el HTML, ni a quien no le corresponde.
 */
class MoodleConnectionTest extends TestCase
{
    use RefreshDatabase;

    private const TOKEN = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4';

    private function usuario(array $permisos): User
    {
        foreach ($permisos as $nombre) {
            Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'environments', 'description' => $nombre]
            );
        }

        $user = User::factory()->create();
        $user->givePermissionTo($permisos);
        $this->actingAs($user);
        Cache::flush();

        return $user;
    }

    private function entorno(?string $token = null): Environment
    {
        $cliente = Client::create([
            'name' => 'Cliente Conexión',
            'shortname' => 'conexion' . uniqid(),
            'actived' => true,
        ]);

        return Environment::create([
            'client_id' => $cliente->id,
            'name' => 'Sitio del cliente',
            'domain' => 'https://sitio-cliente.test',
            'version' => '5.1.1',
            'env' => 'local',
            'active' => true,
            'moodletoken' => $token,
        ]);
    }

    /* ===================== Permisos ===================== */

    public function test_sin_permiso_de_edicion_no_se_abre(): void
    {
        $this->usuario(['admin.environments.index']);
        $entorno = $this->entorno();

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->assertForbidden();
    }

    public function test_sin_permiso_de_edicion_no_se_guarda_el_token(): void
    {
        // MGR-005: el método viaja por /livewire/update. Y aquí lo que se escribiría es
        // una credencial del Moodle de un cliente.
        $this->usuario(['admin.environments.index']);
        $entorno = $this->entorno();

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->assertForbidden();

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

    /* ===================== El token no se filtra ===================== */

    public function test_la_pantalla_no_muestra_el_token_guardado(): void
    {
        $this->usuario(['admin.environments.edit', 'admin.environments.refresh']);
        $entorno = $this->entorno(self::TOKEN);

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->assertDontSee(self::TOKEN)
            ->assertSee('Hay un token guardado')
            // El campo es de solo escritura: nunca se rellena con el guardado.
            ->assertSet('token', '');
    }

    public function test_el_token_no_se_queda_en_el_estado_despues_de_guardarlo(): void
    {
        // Mientras esté en una propiedad pública viaja en CADA petición de Livewire, en
        // claro y en el HTML.
        $this->usuario(['admin.environments.edit', 'admin.environments.refresh']);
        $entorno = $this->entorno();

        // El sobre que devuelve el plugin: success, error, code, data.
        Http::fake(['*' => Http::response(['success' => true, 'data' => []], 200)]);

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->set('token', self::TOKEN)
            ->call('guardarYProbar')
            ->assertSet('token', '')
            ->assertDontSee(self::TOKEN);
    }

    public function test_el_token_se_guarda_cifrado(): void
    {
        // MGR-038: en la columna no puede quedar en claro.
        $this->usuario(['admin.environments.edit', 'admin.environments.refresh']);
        $entorno = $this->entorno();

        // El sobre que devuelve el plugin: success, error, code, data.
        Http::fake(['*' => Http::response(['success' => true, 'data' => []], 200)]);

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->set('token', self::TOKEN)
            ->call('guardarYProbar');

        $enBruto = DB::table('environments')->where('id', $entorno->id)->value('moodletoken');

        $this->assertNotSame(self::TOKEN, $enBruto);
        $this->assertStringNotContainsString(self::TOKEN, (string) $enBruto);
        // Y el modelo lo devuelve descifrado.
        $this->assertSame(self::TOKEN, $entorno->fresh()->moodletoken);
    }

    /* ===================== Validación ===================== */

    public function test_un_token_con_mala_forma_no_llega_al_moodle(): void
    {
        // Ahorra un viaje al sitio del cliente para descubrir que se pegó media cadena.
        $this->usuario(['admin.environments.edit', 'admin.environments.refresh']);
        $entorno = $this->entorno();

        Http::fake();

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->set('token', 'esto-no-es-un-token')
            ->call('guardarYProbar')
            ->assertHasErrors(['token']);

        $this->assertNull($entorno->fresh()->moodletoken);
        Http::assertNothingSent();
    }

    /* ===================== La conexión ===================== */

    public function test_guardar_el_token_permite_sincronizar_en_el_momento(): void
    {
        // Es el objetivo de todo esto: no esperar a la tarea de la 01:00.
        $this->usuario(['admin.environments.edit', 'admin.environments.refresh']);
        $entorno = $this->entorno();

        // El sobre que devuelve el plugin: success, error, code, data.
        Http::fake(['*' => Http::response(['success' => true, 'data' => []], 200)]);

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->set('token', self::TOKEN)
            ->call('guardarYProbar')
            ->assertSet('ok', true);

        // Se ha llamado al web service del plugin, con el token pegado.
        Http::assertSent(function ($peticion) {
            return str_contains($peticion->url(), '/webservice/rest/server.php')
                && $peticion['wstoken'] === self::TOKEN
                && $peticion['wsfunction'] === 'local_tresipunt_auth_sync';
        });

        // Y queda constancia de quién puso la credencial.
        $this->assertDatabaseHas('logs', ['code' => '16017']);
    }

    public function test_un_error_del_moodle_se_explica_con_su_detalle(): void
    {
        // "No se ha podido contactar" a secas no lleva a ninguna parte: el detalle es de
        // un sistema nuestro hablando con el del cliente.
        $this->usuario(['admin.environments.edit', 'admin.environments.refresh']);
        $entorno = $this->entorno(self::TOKEN);

        Http::fake(['*' => Http::response('Service not available', 503)]);

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->call('probar')
            ->assertSet('ok', false)
            ->assertSee('503');
    }

    public function test_borrar_el_token_deja_al_entorno_sin_sincronizacion_desde_el_manager(): void
    {
        $this->usuario(['admin.environments.edit', 'admin.environments.refresh']);
        $entorno = $this->entorno(self::TOKEN);

        Livewire::test(MoodleConnectionModal::class, ['environmentId' => $entorno->id])
            ->call('borrarToken')
            ->assertSet('ok', false);

        $this->assertNull($entorno->fresh()->moodletoken);
        $this->assertDatabaseHas('logs', ['code' => '16017']);
    }
}
