<?php

namespace Tests\Feature\Features;

use App\Livewire\Features\Create as FeatureCreate;
use App\Livewire\Features\Edit as FeatureEdit;
use App\Models\Auth\User;
use App\Models\Features\Feature;
use App\Models\Features\FeatureVersion;
use App\Models\Products\Product;
use App\Services\Features\HtmlSanitizer;
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;

/**
 * El editor no deja guardar contenido que el Moodle del cliente va a borrar (MGR-002).
 *
 * **El problema no era de seguridad.** El autor pegaba un vídeo de YouTube, el editor lo
 * aceptaba sin protestar y en el Moodle no se pintaba **nunca**: sin error y sin hueco, el
 * bloque desaparecía. Se descubría al mirar la pantalla del producto, que puede ser una
 * semana después.
 *
 * Y **no se arregla relajando nuestra lista**, que es lo que uno intenta primero: hay dos
 * saneadores en serie y el decisivo es el del plugin, que usa el purificador del core de
 * Moodle sin `HTML.SafeIframe`. Aunque abriéramos la puerta de aquí, la de allí no es
 * nuestra. Lo único que se puede cambiar es **cuándo se entera el autor**.
 */
class ContenidoQueNoSeVeriaTest extends TestCase
{
    use RefreshDatabase;

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

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

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

        Cache::flush();

        return $user;
    }

    /**
     * Producto y versión.
     *
     * Los dos componentes montan con los dos y comprueban que la versión pertenece al
     * producto —si no, 404—, así que no vale pasar solo la versión.
     *
     * @return array{Product, FeatureVersion}
     */
    private function productoYVersion(): array
    {
        $producto = Product::create([
            'name' => 'Producto de prueba',
            'slug' => 'prueba',
            'actived' => true,
        ]);

        $version = FeatureVersion::create([
            'product_id' => $producto->id,
            'version' => '2026010100',
            'description' => 'Versión de prueba',
        ]);

        return [$producto, $version];
    }

    /** El vídeo de YouTube que un autor pega de verdad. */
    private const CON_IFRAME = '<p class="lead">Mira el vídeo:</p>'
        . '<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" width="560"></iframe>';

    public function test_no_se_puede_guardar_una_funcionalidad_con_iframe(): void
    {
        $this->autor();
        [$producto, $version] = $this->productoYVersion();

        Livewire::test(FeatureCreate::class, ['product' => $producto, 'featureVersion' => $version])
            ->set('title', 'Con vídeo')
            ->set('status', 'draft')
            ->set('content_html', self::CON_IFRAME)
            ->call('save')
            ->assertHasErrors('content_html');

        $this->assertSame(0, Feature::count(),
            'guardar una funcionalidad a la que le falta el vídeo es peor que no guardarla');
    }

    public function test_el_mensaje_dice_que_hacer_y_no_solo_que_esta_mal(): void
    {
        // Para vídeo la vía es Tutoriales, que no pasa por el saneado de HTML remoto. Sin
        // esa frase el autor quita el iframe y se queda sin saber cómo poner el vídeo.
        $this->autor();
        [$producto, $version] = $this->productoYVersion();

        $componente = Livewire::test(FeatureCreate::class, ['product' => $producto, 'featureVersion' => $version])
            ->set('title', 'Con vídeo')
            ->set('status', 'draft')
            ->set('content_html', self::CON_IFRAME)
            ->call('save');

        $mensaje = $componente->errors()->first('content_html');

        $this->assertStringContainsString('<iframe>', $mensaje);
        $this->assertStringContainsString('Tutorial', $mensaje);
        $this->assertStringContainsString('tampoco daría error', $mensaje,
            'lo que hay que explicar es justo eso: que no avisa');
    }

    public function test_tambien_al_editar(): void
    {
        // Las dos puertas. Una sola protegida deja el agujero entero.
        $this->autor();
        [$producto, $version] = $this->productoYVersion();

        $feature = Feature::create([
            'feature_version_id' => $version->id,
            'title' => 'Ya existía',
            'content_html' => '<p>Limpio</p>',
            'status' => 'draft',
            'sort_order' => 0,
        ]);

        Livewire::test(FeatureEdit::class, [
            'product' => $producto,
            'featureVersion' => $version,
            'feature' => $feature,
        ])
            ->set('content_html', self::CON_IFRAME)
            ->call('save')
            ->assertHasErrors('content_html');

        $this->assertSame('<p>Limpio</p>', $feature->fresh()->content_html);
    }

    public function test_el_contenido_normal_se_guarda_igual(): void
    {
        // **Lo que no se puede romper.** La maquetación con componentes de Bootstrap por
        // clase es justamente la vía buena (PRODSECU-193).
        $this->autor();
        [$producto, $version] = $this->productoYVersion();

        $bueno = '<div class="alert alert-info"><h5 class="mb-2">Aviso</h5>'
            . '<p>Texto con <strong>énfasis</strong> y una <a href="https://tresipunt.com">enlace</a>.</p>'
            . '<ul class="list-group"><li class="list-group-item">Uno</li></ul></div>';

        Livewire::test(FeatureCreate::class, ['product' => $producto, 'featureVersion' => $version])
            ->set('title', 'Bien maquetada')
            ->set('status', 'draft')
            ->set('content_html', $bueno)
            ->call('save')
            ->assertHasNoErrors('content_html');

        $this->assertSame(1, Feature::count());
    }

    public function test_el_detector_y_el_saneador_comparten_la_lista(): void
    {
        // **La trampa que esto evita**: con la lista en dos sitios, el día que se añada
        // una etiqueta el saneador la quitaría y el aviso no la mencionaría, así que el
        // autor volvería a perder contenido en silencio.
        foreach (HtmlSanitizer::PROHIBIDAS as $etiqueta) {
            $this->assertSame(
                [$etiqueta],
                HtmlSanitizer::prohibidasPresentes('<p>hola</p><' . $etiqueta . ' foo="1">'),
                "la etiqueta {$etiqueta} está en la lista del saneador y el detector no la ve"
            );
        }
    }

    public function test_el_detector_no_se_deja_enganar_por_las_mayusculas(): void
    {
        $this->assertSame(['iframe'], HtmlSanitizer::prohibidasPresentes('<IFrame src="x">'));
        $this->assertSame(['iframe'], HtmlSanitizer::prohibidasPresentes('<  iframe src="x">'));

        // Y no salta con una palabra que solo empieza igual.
        $this->assertSame([], HtmlSanitizer::prohibidasPresentes('<p>hablemos de iframes</p>'));
        $this->assertSame([], HtmlSanitizer::prohibidasPresentes('<formulario-x></formulario-x>'));
    }

    public function test_un_contenido_vacio_no_da_error(): void
    {
        $this->assertSame([], HtmlSanitizer::prohibidasPresentes(null));
        $this->assertSame([], HtmlSanitizer::prohibidasPresentes(''));
        $this->assertSame([], HtmlSanitizer::prohibidasPresentes('   '));
    }
}
