<?php

namespace Tests\Feature\Setups;

use App\Livewire\Setups\Create as CrearSetup;
use App\Livewire\Setups\Edit as EditarSetup;
use App\Models\Auth\User;
use App\Models\Products\Product;
use App\Models\Setups\Setup;
use App\Services\Setups\RevisionDelSetup;
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;

/**
 * La cabecera del YAML tiene que decir lo mismo que la fila (MGR-096).
 *
 * Todo setup empieza por `plugin:` y `version:`, y **nadie las miraba**. Lo que decide a qué
 * producto pertenece el setup y qué clientes lo reciben son el `product_id` y la `version` de
 * la fila, que se eligen en el formulario, así que las dos podían contradecirla sin que nada
 * avisara.
 *
 * El caso probable es el más común: **copiar el setup de otro producto o de otra versión**,
 * cambiar el formulario y no la cabecera. No rompe el servicio —se sirve el contenido de la
 * fila, que es el correcto— y por eso es de severidad baja; lo que queda mal es el dato que
 * documenta el fichero, y eso se paga meses después al diagnosticar.
 *
 * **Avisa y no bloquea**, a propósito: puede haber un motivo, y decidirlo por quien escribe
 * el setup sería peor que el problema.
 */
class CabeceraDelSetupTest extends TestCase
{
    use RefreshDatabase;

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

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

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

        Cache::flush();

        return $user;
    }

    private function producto(string $slug = 'theme_fresk'): Product
    {
        return Product::create(['name' => 'Producto ' . $slug, 'slug' => $slug, 'actived' => true]);
    }

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

    public function test_avisa_cuando_la_cabecera_es_de_otro_producto(): void
    {
        $avisos = RevisionDelSetup::revisar(
            ['plugin' => 'block_tresipuntsepe', 'version' => '2026090800'],
            'theme_fresk',
            '2026090800'
        )['avisos'];

        $este = collect($avisos)->first(fn (string $a) => str_contains($a, 'plugin: block_tresipuntsepe'));

        $this->assertNotNull($este, 'no avisa de que la cabecera es de otro producto');
        $this->assertStringContainsString('theme_fresk', $este);
        // **Y dice que no cambia a quién se sirve**, que es lo que evita el susto: el aviso
        // no puede leerse como «este setup le está llegando al producto equivocado».
        $this->assertStringContainsString('No cambia a quién se sirve', $este);
    }

    public function test_avisa_cuando_la_cabecera_es_de_otra_version(): void
    {
        // El caso del issue, literal: registrado como 2026090800 y con 2025112800 dentro.
        $avisos = RevisionDelSetup::revisar(
            ['plugin' => 'theme_fresk', 'version' => '2025112800'],
            'theme_fresk',
            '2026090800'
        )['avisos'];

        $este = collect($avisos)->first(fn (string $a) => str_contains($a, 'version: 2025112800'));

        $this->assertNotNull($este);
        $this->assertStringContainsString('2026090800', $este);
        $this->assertStringContainsString('no cambia lo que recibe nadie', $este);
    }

    public function test_una_cabecera_que_cuadra_no_genera_aviso(): void
    {
        $avisos = RevisionDelSetup::revisar(
            ['plugin' => 'theme_fresk', 'version' => '2026090800'],
            'theme_fresk',
            '2026090800'
        )['avisos'];

        $this->assertSame([], $avisos);
    }

    public function test_sin_contexto_se_comprueba_la_forma_pero_no_contra_que(): void
    {
        // **La API también revisa setups y allí no hay formulario del que sacar el
        // contexto.** Sin él no se puede comparar, y una comparación contra nada sería un
        // aviso inventado.
        $avisos = RevisionDelSetup::revisar(['plugin' => 'cualquiera', 'version' => '2020010100'])['avisos'];

        $this->assertSame([], $avisos);

        // Lo que sí se sigue comprobando sin contexto: que estén y que tengan forma.
        $sinNada = RevisionDelSetup::revisar(['roles' => []])['avisos'];

        $this->assertNotSame([], $sinNada);
    }

    /* ============ El arreglo ============ */

    public function test_rellenar_la_cabecera_reescribe_las_dos_lineas(): void
    {
        $yaml = RevisionDelSetup::conLaCabeceraPuesta(
            "plugin: block_otro\nversion: 2020010100\nroles:\n  - shortname: alumno\n",
            'theme_fresk',
            '2026090800'
        );

        $this->assertStringContainsString("plugin: theme_fresk\n", $yaml);
        $this->assertStringContainsString("version: 2026090800\n", $yaml);
        $this->assertStringNotContainsString('block_otro', $yaml);

        // **Y no se lleva el resto por delante.** Se trabaja sobre el texto justamente para
        // no reordenar claves, perder comentarios ni cambiar el estilo de las comillas.
        $this->assertStringContainsString('- shortname: alumno', $yaml);
    }

    public function test_si_la_cabecera_no_estaba_se_pone_delante(): void
    {
        // Un `plugin:` al final del fichero se lee como parte del último bloque.
        $yaml = RevisionDelSetup::conLaCabeceraPuesta("roles:\n  - shortname: alumno\n", 'theme_fresk', '2026090800');

        $this->assertStringStartsWith('version: 2026090800', $yaml);
        $this->assertStringContainsString("plugin: theme_fresk\n", $yaml);
    }

    public function test_rellenar_no_toca_los_comentarios_del_fichero(): void
    {
        $yaml = RevisionDelSetup::conLaCabeceraPuesta(
            "# Setup copiado del tema anterior\nplugin: block_otro\nversion: 2020010100\n",
            'theme_fresk',
            '2026090800'
        );

        $this->assertStringContainsString('# Setup copiado del tema anterior', $yaml);
    }

    /* ============ En la pantalla ============ */

    public function test_la_pantalla_de_crear_avisa_y_ofrece_arreglarlo(): void
    {
        $this->admin();

        $producto = $this->producto();

        Livewire::test(CrearSetup::class, ['product' => $producto])
            ->set('version', '2026090800')
            ->set('yaml', "plugin: block_otro\nversion: 2020010100\n")
            // El editor valida al pulsar su botón —el textarea no es `.live`, que es lo
            // correcto para un editor grande—, así que el test hace lo mismo. `save()`
            // también llama a `validateYaml()`, así que el aviso sale igual al guardar.
            ->call('validateYaml')
            ->assertSee('plugin: block_otro')
            ->assertSee('Poner en la cabecera el producto y la versión de este setup');
    }

    public function test_el_boton_deja_la_cabecera_puesta_y_el_aviso_se_va(): void
    {
        $this->admin();

        $producto = $this->producto();

        $componente = Livewire::test(CrearSetup::class, ['product' => $producto])
            ->set('version', '2026090800')
            ->set('yaml', "plugin: block_otro\nversion: 2020010100\n")
            ->call('rellenarCabecera');

        $this->assertStringContainsString('plugin: theme_fresk', $componente->get('yaml'));
        // El aviso desaparece en el momento: dejarlo puesto después de arreglarlo hace dudar
        // de si el botón ha hecho algo.
        $this->assertSame([], $componente->get('avisosDelSetup'));
    }

    public function test_al_editar_se_compara_con_la_version_guardada(): void
    {
        $this->admin();

        $producto = $this->producto();

        $setup = Setup::create([
            'product_id' => $producto->id,
            'version' => '2026090800',
            'yaml' => "plugin: theme_fresk\nversion: 2026090800\n",
            'created_by' => 1,
            'updated_by' => 1,
        ]);

        Livewire::test(EditarSetup::class, ['product' => $producto, 'setup' => $setup])
            ->set('yaml', "plugin: theme_fresk\nversion: 2019010100\n")
            ->assertSee('version: 2019010100')
            ->assertSee('2026090800');
    }

    public function test_sin_el_permiso_no_se_puede_rellenar(): void
    {
        // El gate de la ruta no se reaplica en /livewire/update (MGR-005).
        $rol = Role::firstOrCreate(['name' => 'Mirón', 'guard_name' => 'web']);
        $rol->givePermissionTo(Permission::firstOrCreate(
            ['name' => 'admin.products.index', 'guard_name' => 'web'],
            ['model' => 'products', 'description' => 'ver']
        ));

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

        Livewire::test(CrearSetup::class, ['product' => $this->producto()])
            ->set('yaml', "plugin: block_otro\n")
            ->call('rellenarCabecera')
            ->assertForbidden();
    }
}
