<?php

namespace Tests\Feature\Products;

use App\Livewire\Js\Create as JsCreate;
use App\Livewire\Scss\Create as ScssCreate;
use App\Models\Auth\User;
use App\Models\Js\JsFile;
use App\Models\Js\JsVersion;
use App\Models\Products\Product;
use App\Models\Scss\ScssFile;
use App\Models\Scss\ScssVersion;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Escribir un archivo SCSS o JS desde el panel.
 *
 * **Lo que se rompía.** El `<textarea>` salía oculto de entrada y el contenido solo llegaba
 * al servidor por el evento `change` de CodeMirror. Si el editor no llegaba a montarse —y se
 * montaba desde tres sitios a la vez: un `livewire:navigated`, un `morph.updated` con
 * `setTimeout(…, 150)` y, en las pantallas de edición, un `MutationObserver`— la pantalla se
 * quedaba sin dónde escribir, sin error y sin forma de guardar.
 *
 * Ahora el `<textarea>` es real y está enganchado con `wire:model`, así que **el guardado no
 * depende de que CodeMirror exista**: si no aparece, se escribe en el textarea de siempre.
 * Estos tests comprueban justo eso, que es lo que un test de PHP puede comprobar: que el
 * camino sin JavaScript funciona de punta a punta.
 */
class EditorDeCodigoTest extends TestCase
{
    use RefreshDatabase;

    private function usuario(array $permisos): User
    {
        $rol = Role::firstOrCreate(['name' => 'Editor de codigo', 'guard_name' => 'web']);

        foreach ($permisos as $nombre) {
            $rol->givePermissionTo(Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'products', 'description' => $nombre]
            ));
        }

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

        return $usuario;
    }

    private function producto(): Product
    {
        return Product::create(['name' => 'Tema editor', 'slug' => 'theme_edi', 'shortname' => 'edi']);
    }

    /* ===================== SCSS ===================== */

    public function test_se_escribe_y_se_guarda_un_scss(): void
    {
        Storage::fake('public');

        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->set('files.0.filename', 'variables')
            // Por donde escribe el textarea: la propiedad que lleva el `wire:model`, sin
            // pasar por ningún evento de CodeMirror.
            ->set('fileContents.0', '$primario: #ff6a00;')
            ->call('save');

        $archivo = ScssFile::where('scss_version_id', $version->id)->first();

        $this->assertNotNull($archivo, 'Debía crearse el archivo.');
        $this->assertSame('variables', $archivo->filename);
        $this->assertSame('$primario: #ff6a00;', $archivo->content);
    }

    public function test_el_editor_sale_en_la_pantalla_con_su_textarea(): void
    {
        // **El textarea tiene que estar en el HTML**, no crearlo el JavaScript: es lo que
        // permite escribir aunque CodeMirror no monte. Si algún día vuelve a ser un `div`
        // vacío que rellena un script, este test lo dice.
        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->assertSeeHtml('id="scss-editor-0"')
            ->assertSeeHtml('wire:model="fileContents.0"')
            // Sin `wire:ignore` Livewire recompone el DOM en cada respuesta y se lleva el
            // editor por delante, con lo que hubiera escrito dentro.
            ->assertSeeHtml('wire:ignore');
    }

    public function test_sin_contenido_no_se_guarda_y_se_dice(): void
    {
        // Antes esto era el síntoma de que el editor no había sincronizado: el usuario
        // veía su código en pantalla y el servidor recibía vacío.
        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->set('files.0.filename', 'variables')
            ->call('save')
            ->assertHasErrors('files.0.content');

        $this->assertSame(0, ScssFile::count());
    }

    public function test_se_pueden_escribir_dos_archivos_a_la_vez(): void
    {
        // Cada uno con su editor: era donde el montaje por eventos globales se liaba, porque
        // el segundo aparecía después de que el script ya hubiera pasado.
        Storage::fake('public');

        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->set('files.0.filename', 'variables')
            ->set('fileContents.0', '$a: 1;')
            ->call('addFile')
            ->assertSeeHtml('id="scss-editor-1"')
            ->set('files.1.filename', 'mixins')
            ->set('fileContents.1', '@mixin b { color: red; }')
            ->call('save');

        $this->assertSame(2, ScssFile::where('scss_version_id', $version->id)->count());

        // Y el primero no se pierde al añadir el segundo, que es lo que pasaba cuando el
        // contenido solo vivía en el editor del navegador.
        $this->assertSame(
            '$a: 1;',
            ScssFile::where('filename', 'variables')->value('content')
        );
    }

    /* ===================== La revisión ===================== */

    public function test_una_llave_sin_cerrar_no_se_guarda_y_se_dice_donde(): void
    {
        // **Un SCSS que no compila deja al cliente sin nada de ese producto**, no solo sin
        // la regla mal escrita. Es lo más caro que puede pasar aquí, así que frena.
        Storage::fake('public');

        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->set('files.0.filename', 'roto')
            ->set('fileContents.0', ".boton {\n    color: red;\n")
            ->call('save')
            ->assertSee('Falta cerrar la llave')
            ->assertSee('Línea 1');

        $this->assertSame(0, ScssFile::count(), 'No debía guardarse nada.');
    }

    public function test_un_import_que_falta_avisa_pero_deja_guardar(): void
    {
        // Puede ser el archivo que se sube justo después: frenarlo obligaría a subirlos en
        // un orden concreto. Se dice y se pasa.
        Storage::fake('public');

        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->set('files.0.filename', 'principal')
            ->set('fileContents.0', "@import 'variables';\n.a { color: red; }\n")
            ->call('save');

        $this->assertSame(1, ScssFile::count(), 'El aviso no debía frenar el guardado.');
    }

    public function test_los_archivos_del_mismo_formulario_cuentan_como_hermanos(): void
    {
        // Se suben varios de una vez: avisar de que falta el que se está subiendo al lado
        // sería un aviso falso, y un aviso falso se deja de leer.
        Storage::fake('public');

        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->set('files.0.filename', 'principal')
            ->set('fileContents.0', "@import 'variables';\n.a { color: red; }\n")
            ->call('addFile')
            ->set('files.1.filename', 'variables')
            ->set('fileContents.1', '$rojo: red;')
            ->call('save')
            ->assertDontSee('no hay ningún archivo con ese nombre');

        $this->assertSame(2, ScssFile::count());
    }

    /* ===================== El nombre ===================== */

    public function test_se_acepta_el_nombre_con_extension_y_se_le_quita(): void
    {
        // **Es lo que escribe cualquiera** en un campo que pide el nombre de un archivo
        // SCSS, y se rechazaba con «solo puede contener letras, números, guiones y guiones
        // bajos» —un aviso que no menciona ni el punto ni la extensión—. La capa que lo
        // escribe en disco ya quitaba el `.scss` si venía puesto.
        Storage::fake('public');

        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->set('files.0.filename', 'variables.scss')
            ->set('fileContents.0', '$a: 1;')
            ->call('save')
            ->assertHasNoErrors();

        $this->assertSame('variables', ScssFile::where('scss_version_id', $version->id)->value('filename'));
    }

    public function test_los_espacios_de_alrededor_no_cuentan(): void
    {
        Storage::fake('public');

        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
            ->set('files.0.filename', '  mixins  ')
            ->set('fileContents.0', '$a: 1;')
            ->call('save')
            ->assertHasNoErrors();

        $this->assertSame('mixins', ScssFile::where('scss_version_id', $version->id)->value('filename'));
    }

    public function test_un_nombre_con_carpeta_o_acento_se_rechaza_diciendo_por_que(): void
    {
        // El nombre acaba siendo una ruta en disco y una clave en la API, así que el patrón
        // se queda estrecho a propósito. Lo que tiene que hacer es explicarse.
        $usuario = $this->usuario(['admin.scss.file.create']);
        $producto = $this->producto();

        $version = ScssVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        foreach (['componentes/boton', 'botón', 'dos palabras'] as $malo) {
            Livewire::test(ScssCreate::class, ['product' => $producto, 'scssVersion' => $version])
                ->set('files.0.filename', $malo)
                ->set('fileContents.0', '$a: 1;')
                ->call('save')
                ->assertHasErrors('files.0.filename');
        }

        $this->assertSame(0, ScssFile::count());
    }

    public function test_el_js_tambien_se_queda_sin_su_extension(): void
    {
        Storage::fake('public');

        $usuario = $this->usuario(['admin.js.file.create']);
        $producto = $this->producto();

        $version = JsVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(JsCreate::class, ['product' => $producto, 'jsVersion' => $version])
            ->set('files.0.filename', 'main.js')
            ->set('fileContents.0', 'const a = 1;')
            ->call('save')
            ->assertHasNoErrors();

        $this->assertSame('main', JsFile::where('js_version_id', $version->id)->value('filename'));
    }

    /* ===================== JS ===================== */

    public function test_se_escribe_y_se_guarda_un_js(): void
    {
        Storage::fake('public');

        $usuario = $this->usuario(['admin.js.file.create']);
        $producto = $this->producto();

        $version = JsVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'status' => 'active',
            'created_by' => $usuario->id,
        ]);

        Livewire::test(JsCreate::class, ['product' => $producto, 'jsVersion' => $version])
            ->set('files.0.filename', 'main')
            ->set('fileContents.0', "console.log('hola');")
            ->call('save');

        $archivo = JsFile::where('js_version_id', $version->id)->first();

        $this->assertNotNull($archivo, 'Debía crearse el archivo.');
        $this->assertSame("console.log('hola');", $archivo->content);
    }
}
