<?php

namespace Tests\Feature\LicenseTokens;

use App\Models\Clients\Client;
use App\Models\Products\LicenseToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
 * El token de licencia: `3IP-XXXX-XXXX-XXXX-XXXX`.
 *
 * **El formato es contrato público con el plugin**: es la cadena que el cliente pega en su
 * Moodle y la que viaja en cada petición a la API. Estaba generado en **cuatro** pantallas
 * a la vez —`Create`, `CreateWizard`, `Edit` y `LicenseForm`—, con el bucle copiado
 * literal, así que cambiarlo tenía cuatro oportunidades de quedarse a medias y el fallo
 * habría salido **solo en los tokens creados desde una de ellas** (MGR-022).
 *
 * Ahora hay una sola definición, y estos tests son los que la sujetan: si alguien cambia el
 * formato sin querer, aquí se ve.
 *
 * **Y desde el 2026-09-08 quedan dos pantallas, no cuatro**: `Create` y `LicenseForm` eran
 * componentes que no montaba ninguna ruta ni ninguna vista, y se han borrado (MGR-098). Este
 * test las nombraba una a una, así que la lista se ha cambiado por **un barrido**: en vez de
 * enumerar qué pantallas están bien, comprueba que **el formato no se construye en ningún
 * sitio más que el modelo**. Eso es lo que de verdad no puede volver a pasar, y no caduca
 * cuando se añade o se borra una pantalla.
 */
class GeneracionDelTokenTest extends TestCase
{
    use RefreshDatabase;

    private function cliente(): Client
    {
        return Client::create([
            'name' => 'Cliente del token',
            'shortname' => 'token',
            'actived' => true,
        ]);
    }

    public function test_el_formato_es_el_del_contrato(): void
    {
        // Prefijo `3IP-` y cuatro bloques de cuatro, con dígitos y mayúsculas. Ni minúsculas
        // ni símbolos: el token se dicta por teléfono y se pega a mano.
        $this->assertMatchesRegularExpression(
            '/^3IP-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}$/',
            LicenseToken::generarToken()
        );
    }

    public function test_dos_seguidos_no_coinciden(): void
    {
        // Cincuenta de golpe: con 36^16 combinaciones, un duplicado aquí significaría que
        // el generador está roto —o que alguien lo cambió a algo con estado—.
        $tokens = [];

        foreach (range(1, 50) as $ignorado) {
            $tokens[] = LicenseToken::generarToken();
        }

        $this->assertCount(50, array_unique($tokens));
    }

    public function test_no_devuelve_uno_que_ya_existe(): void
    {
        $cliente = $this->cliente();

        foreach (range(1, 5) as $i) {
            LicenseToken::create([
                'client_id' => $cliente->id,
                'name' => 'Licencia ' . $i,
                'token' => LicenseToken::generarToken(),
                'active' => true,
            ]);
        }

        $nuevo = LicenseToken::generarToken();

        $this->assertDatabaseMissing('license_tokens', ['token' => $nuevo]);
    }

    public function test_al_regenerar_el_de_una_licencia_su_propio_token_no_cuenta(): void
    {
        // **La única variante que había entre las cuatro copias**, y se conserva: la
        // pantalla de edición excluye la propia fila del control de unicidad, porque su
        // token actual no es un choque con nadie.
        //
        // No se puede forzar la colisión sin sembrar el generador —son 36^16
        // combinaciones—, así que lo que se comprueba es que el parámetro se acepta y sigue
        // devolviendo un token válido: si alguien rompiera la consulta del `when()`, esto
        // reventaría.
        $cliente = $this->cliente();

        $licencia = LicenseToken::create([
            'client_id' => $cliente->id,
            'name' => 'La que se regenera',
            'token' => LicenseToken::generarToken(),
            'active' => true,
        ]);

        $regenerado = LicenseToken::generarToken(exceptoId: $licencia->id);

        $this->assertMatchesRegularExpression(
            '/^3IP-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}$/',
            $regenerado
        );
    }

    public function test_solo_el_modelo_construye_el_formato(): void
    {
        // **El test que justifica el refactor, y en la forma que no caduca.** Antes
        // enumeraba las cuatro pantallas; al borrar dos de ellas la lista se quedó
        // mintiendo. Lo que hay que impedir no es que una pantalla concreta esté bien: es
        // que **el prefijo del token vuelva a escribirse fuera del modelo**, que es como
        // llegó a estar en cuatro sitios.
        $culpables = [];

        $ficheros = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator(app_path())
        );

        foreach ($ficheros as $fichero) {
            if ($fichero->getExtension() !== 'php') {
                continue;
            }

            // El modelo es el dueño del formato: ahí sí va.
            if ($fichero->getPathname() === app_path('Models' . DIRECTORY_SEPARATOR . 'Products' . DIRECTORY_SEPARATOR . 'LicenseToken.php')) {
                continue;
            }

            foreach (file($fichero->getPathname()) as $n => $linea) {
                // En una cadena, no en un comentario: `'3IP-'` o `"3IP-"`.
                if (preg_match('/[\'"]3IP-/', $linea)) {
                    $culpables[] = str_replace(app_path(), 'app', $fichero->getPathname()) . ':' . ($n + 1);
                }
            }
        }

        $this->assertSame(
            [],
            $culpables,
            "El formato del token se construye fuera de LicenseToken::generarToken():\n  " . implode("\n  ", $culpables)
        );
    }

    public function test_las_pantallas_que_ofrecen_generar_delegan_en_el_modelo(): void
    {
        // Las dos que quedan vivas. Se comprueban dos cosas: que el método sigue siendo
        // **llamable desde la vista** —es un `wire:click`, si deja de ser público el botón
        // deja de funcionar sin que nada más falle— y que **delega**.
        foreach ([
            \App\Livewire\LicenseTokens\CreateWizard::class,
            \App\Livewire\LicenseTokens\Edit::class,
        ] as $componente) {
            $reflexion = new \ReflectionMethod($componente, 'generateToken');

            $this->assertTrue(
                $reflexion->isPublic(),
                "{$componente}::generateToken() tiene que seguir siendo llamable desde la vista"
            );

            $cuerpo = implode('', array_slice(
                file($reflexion->getFileName()),
                $reflexion->getStartLine() - 1,
                $reflexion->getEndLine() - $reflexion->getStartLine() + 1
            ));

            $this->assertStringContainsString(
                'LicenseToken::generarToken(',
                $cuerpo,
                "{$componente}::generateToken() tiene que delegar en el modelo, no generar el token por su cuenta"
            );
        }

        // Y el resultado, que es lo que ve el cliente.
        $this->assertMatchesRegularExpression(
            '/^3IP-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}$/',
            LicenseToken::generarToken()
        );
    }
}
