<?php

namespace Tests\Feature\Environments;

use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Products\LicenseToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

/**
 * El token de servicios web del Moodle del cliente, cifrado en reposo (MGR-038).
 *
 * `environments.moodletoken` no es una credencial nuestra: es la que da control de
 * administración sobre el Moodle **del cliente**. Llega dentro del `sync` y desde la
 * 2.0.0 se usa de verdad —el botón "Sincronizar ahora" llama con ella—, así que un
 * volcado de la base, una copia para depurar o una consulta de más entregaría el
 * control de los Moodles de todos los clientes.
 */
class MoodleTokenAtRestTest extends TestCase
{
    use RefreshDatabase;

    private const TOKEN_MOODLE = '0136cc0e9f3b095f8789496d2c42fc77';
    private const TOKEN_LICENCIA = '3IP-TEST-CIFRADO-01';
    private const DOMAIN = 'https://cifrado.test';

    private function crearEntorno(?string $moodletoken = self::TOKEN_MOODLE): Environment
    {
        $cliente = Client::create(['name' => 'Cliente Cifrado', 'shortname' => 'cifrado', 'actived' => true]);

        $token = LicenseToken::create([
            'client_id' => $cliente->id,
            'name' => 'Licencia Cifrado',
            'token' => self::TOKEN_LICENCIA,
            'active' => true,
        ]);

        return Environment::create([
            'name' => 'Entorno Cifrado',
            'domain' => self::DOMAIN,
            'version' => '5.1.1',
            'env' => 'local',
            'client_id' => $cliente->id,
            'license_token_id' => $token->id,
            'active' => true,
            'moodletoken' => $moodletoken,
        ]);
    }

    /** El valor tal cual está en la columna, sin pasar por el modelo. */
    private function valorEnLaBase(Environment $entorno): ?string
    {
        return DB::table('environments')->where('id', $entorno->id)->value('moodletoken');
    }

    public function test_el_token_no_se_guarda_en_claro(): void
    {
        $entorno = $this->crearEntorno();

        $enLaBase = $this->valorEnLaBase($entorno);

        $this->assertNotSame(self::TOKEN_MOODLE, $enLaBase);
        $this->assertStringNotContainsString(
            self::TOKEN_MOODLE,
            (string) $enLaBase,
            'El token aparece en claro dentro del valor guardado.'
        );
    }

    public function test_el_modelo_lo_devuelve_en_claro(): void
    {
        // Lo que hace que el cifrado sea transparente: quien lo usa —el servicio de
        // sincronización y la tarjeta del entorno— no se enteran.
        $entorno = $this->crearEntorno();

        $this->assertSame(self::TOKEN_MOODLE, $entorno->fresh()->moodletoken);
    }

    public function test_la_columna_aguanta_el_valor_cifrado(): void
    {
        // La columna era `varchar(255)` y el cifrado de un token de 32 caracteres ocupa
        // 256: sin ensanchar, MariaDB lo rechaza en modo estricto y lo trunca sin él.
        // Un token truncado no se puede descifrar, o sea que se pierde.
        $entorno = $this->crearEntorno();

        $this->assertGreaterThan(255, strlen((string) $this->valorEnLaBase($entorno)));
        $this->assertSame(self::TOKEN_MOODLE, $entorno->fresh()->moodletoken, 'Se ha truncado.');
    }

    public function test_no_sale_en_las_serializaciones_del_modelo(): void
    {
        // El modelo lo descifra al leerlo, así que sin `$hidden` un `return $environment`
        // de un endpoint, un `toJson()` o un volcado en una vista lo imprimirían.
        $entorno = $this->crearEntorno();

        $this->assertArrayNotHasKey('moodletoken', $entorno->toArray());
        $this->assertStringNotContainsString(self::TOKEN_MOODLE, $entorno->toJson());
    }

    public function test_un_entorno_sin_token_sigue_funcionando(): void
    {
        // La mayoría de los entornos no tienen token —solo los que han sincronizado con
        // una versión del plugin que lo envía—, y el cast no puede romper ese caso.
        $entorno = $this->crearEntorno(moodletoken: null);

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

    public function test_el_sync_guarda_cifrado_el_token_que_envia_el_moodle(): void
    {
        // El camino real por el que entra el token: `site.token` dentro del `sync`.
        $entorno = $this->crearEntorno(moodletoken: null);
        $nuevo = 'ffffeeee11112222333344445555aaaa';

        $this->withHeaders([
            'Authorization' => 'Bearer ' . self::TOKEN_LICENCIA,
            'Accept' => 'application/json',
        ])->postJson('/api/v1', [
            'action' => 'sync',
            'host' => self::DOMAIN,
            'plugin' => 'local_tresipunt',
            'version' => '2026080604',
            'site' => [
                'version' => '2025100601.03',
                'release' => '5.1.1+ (Build: 20251219)',
                'type' => 'moodle',
                'env' => 'local',
                'token' => $nuevo,
            ],
            // `data.plugins` no puede ir vacío: la regla `required` trata un array
            // vacío como ausente y devuelve 422.
            'data' => ['plugins' => [['name' => 'local_tresipunt', 'component' => 'local_tresipunt']]],
        ])->assertOk();

        $this->assertSame($nuevo, $entorno->fresh()->moodletoken, 'No se ha guardado el token nuevo.');
        $this->assertStringNotContainsString($nuevo, (string) $this->valorEnLaBase($entorno));
    }

    public function test_la_respuesta_del_sync_no_devuelve_el_token(): void
    {
        // Retrocompatibilidad al revés: la respuesta de `sync` no lo incluía y no debe
        // empezar a incluirlo por haber tocado el modelo.
        $entorno = $this->crearEntorno();

        $respuesta = $this->withHeaders([
            'Authorization' => 'Bearer ' . self::TOKEN_LICENCIA,
            'Accept' => 'application/json',
        ])->postJson('/api/v1', [
            'action' => 'sync',
            'host' => self::DOMAIN,
            'plugin' => 'local_tresipunt',
            'version' => '2026080604',
            'site' => [
                'version' => '2025100601.03',
                'release' => '5.1.1+ (Build: 20251219)',
                'type' => 'moodle',
                'env' => 'local',
            ],
            // `data.plugins` no puede ir vacío: la regla `required` trata un array
            // vacío como ausente y devuelve 422.
            'data' => ['plugins' => [['name' => 'local_tresipunt', 'component' => 'local_tresipunt']]],
        ])->assertOk();

        $this->assertStringNotContainsString(self::TOKEN_MOODLE, $respuesta->getContent());
        $respuesta->assertJsonMissingPath('data.moodletoken');
    }
}
