<?php

namespace Tests\Feature\Products;

use App\Livewire\Features\VersionShow as FeaturesVersionShow;
use App\Livewire\Resources\VersionShow as ResourcesVersionShow;
use App\Livewire\Tutorials\VersionShow as TutorialsVersionShow;
use App\Models\Auth\User;
use App\Models\Features\Feature;
use App\Models\Features\FeatureVersion;
use App\Models\Products\Product;
use App\Models\Resources\Resource;
use App\Models\Resources\ResourceVersion;
use App\Models\Tutorials\Tutorial;
use App\Models\Tutorials\TutorialVersion;
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 orden de los elementos de una versión: Funcionalidades, Recursos y Tutoriales.
 *
 * **Estaba roto de dos formas distintas**, y las dos son visibles para el usuario final del
 * Moodle —el orden en que ve las funcionalidades del producto—:
 *
 * 1. **Las flechas no ordenaban.** Intercambiaban el `sort_order` de dos elementos, y con
 *    los valores a `NULL` —como está el único elemento real— intercambiar null por null no
 *    hace nada. La pantalla decía «Orden actualizado correctamente» y la lista se quedaba
 *    igual.
 * 2. **La API servía los sin orden primero.** `scopeOrdered()` hacía
 *    `orderBy('sort_order', 'asc')` a secas y en MySQL un `NULL` ordena **delante**, así
 *    que un elemento recién creado se colaba antes que los ordenados a mano. El contrato
 *    (`api/content-versioning.md`) dice «null al final».
 */
class OrdenDeElementosTest extends TestCase
{
    use RefreshDatabase;

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

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

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

        Cache::flush();

        return $user;
    }

    private function producto(): Product
    {
        return Product::create([
            'slug' => 'block_tresipuntsepe',
            'name' => 'Bloque SEPE',
            'tech' => 'moodle',
            'actived' => true,
            'enabled' => true,
        ]);
    }

    /** Una versión con tres elementos, todos **sin orden asignado**: el caso que no iba. */
    private function tresSinOrden(Product $producto): array
    {
        $version = FeatureVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);

        foreach (['Primera', 'Segunda', 'Tercera'] as $i => $titulo) {
            Feature::create([
                'feature_version_id' => $version->id,
                'title' => $titulo,
                'status' => 'published',
                'sort_order' => null,
                'created_by' => 1,
                'created_at' => now()->subMinutes(10 - $i),
            ]);
        }

        return [$version, Feature::where('feature_version_id', $version->id)->pluck('id', 'title')];
    }

    private function orden(\Livewire\Features\SupportTesting\Testable $componente): string
    {
        return collect($componente->viewData('elementos'))->pluck('title')->implode(' > ');
    }

    /* ===================== Que las flechas ordenen ===================== */

    public function test_subir_un_elemento_lo_sube_aunque_ninguno_tenga_orden(): void
    {
        // **El caso que estaba roto.** Con todos los `sort_order` a null, intercambiar dos
        // valores nulos no hace nada; la pantalla decía que sí y la lista se quedaba igual.
        $this->admin();
        $producto = $this->producto();
        [$version, $ids] = $this->tresSinOrden($producto);

        $componente = Livewire::test(FeaturesVersionShow::class, ['product' => $producto, 'featureVersion' => $version]);

        $this->assertSame('Primera > Segunda > Tercera', $this->orden($componente));

        $componente->call('moveUp', $ids['Tercera']);

        $this->assertSame('Primera > Tercera > Segunda', $this->orden($componente));
    }

    public function test_el_orden_se_normaliza_en_vez_de_intercambiarse(): void
    {
        // Reasignar 1..N deja el orden explícito, que es el que la API usa para servir. Con
        // el intercambio, los nulls seguían siendo nulls y el orden dependía de la fecha de
        // creación —o sea, de nada que nadie hubiera decidido—.
        $this->admin();
        $producto = $this->producto();
        [$version, $ids] = $this->tresSinOrden($producto);

        Livewire::test(FeaturesVersionShow::class, ['product' => $producto, 'featureVersion' => $version])
            ->call('moveUp', $ids['Tercera']);

        $ordenes = Feature::where('feature_version_id', $version->id)->orderBy('sort_order')->pluck('sort_order');

        $this->assertSame([1, 2, 3], $ordenes->all());
    }

    public function test_en_el_extremo_no_se_mueve_ni_se_dice_que_se_movio(): void
    {
        // **Un mensaje de éxito que aparece sin que pase nada enseña a no leer los
        // mensajes.** Antes salía siempre, incluso pulsando «subir» en el primero.
        $this->admin();
        $producto = $this->producto();
        [$version, $ids] = $this->tresSinOrden($producto);

        $componente = Livewire::test(FeaturesVersionShow::class, ['product' => $producto, 'featureVersion' => $version]);

        $componente->call('moveUp', $ids['Primera']);

        $this->assertSame('Primera > Segunda > Tercera', $this->orden($componente));
        $this->assertNull(session('success'));
    }

    public function test_bajar_el_ultimo_tampoco_hace_nada(): void
    {
        $this->admin();
        $producto = $this->producto();
        [$version, $ids] = $this->tresSinOrden($producto);

        $componente = Livewire::test(FeaturesVersionShow::class, ['product' => $producto, 'featureVersion' => $version]);
        $componente->call('moveDown', $ids['Tercera']);

        $this->assertSame('Primera > Segunda > Tercera', $this->orden($componente));
    }

    public function test_no_se_puede_ordenar_un_elemento_de_otra_version(): void
    {
        // El id llega del navegador (MGR-006): mover el elemento de otra versión
        // reescribiría el orden de una lista que no es esta.
        $this->admin();
        $producto = $this->producto();
        [$version, $ids] = $this->tresSinOrden($producto);

        $otra = FeatureVersion::create(['product_id' => $producto->id, 'version' => '2026020200', 'created_by' => 1, 'updated_by' => 1]);
        $ajena = Feature::create([
            'feature_version_id' => $otra->id,
            'title' => 'De otra versión',
            'status' => 'published',
            'sort_order' => 5,
            'created_by' => 1,
        ]);

        Livewire::test(FeaturesVersionShow::class, ['product' => $producto, 'featureVersion' => $version])
            ->call('moveUp', $ajena->id);

        // Su orden no se ha tocado.
        $this->assertSame(5, $ajena->fresh()->sort_order);
    }

    /* ===================== El orden que sirve la API ===================== */

    public function test_los_elementos_sin_orden_van_al_final(): void
    {
        // **Es el orden que ve el usuario del Moodle.** `orderBy('sort_order','asc')` a
        // secas ponía los NULL delante en MySQL, así que un elemento nuevo se colaba antes
        // que los ordenados a mano. El contrato dice «null al final».
        $this->admin();
        $producto = $this->producto();

        $version = FeatureVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);

        Feature::create(['feature_version_id' => $version->id, 'title' => 'Sin orden', 'status' => 'published', 'sort_order' => null, 'created_by' => 1]);
        Feature::create(['feature_version_id' => $version->id, 'title' => 'La segunda', 'status' => 'published', 'sort_order' => 2, 'created_by' => 1]);
        Feature::create(['feature_version_id' => $version->id, 'title' => 'La primera', 'status' => 'published', 'sort_order' => 1, 'created_by' => 1]);

        // El scope del modelo es el que usan las tres Actions de la API: se comprueba ahí,
        // no en la pantalla, porque es el que decide lo que recibe el cliente.
        $this->assertSame(
            ['La primera', 'La segunda', 'Sin orden'],
            Feature::where('feature_version_id', $version->id)->ordered()->pluck('title')->all()
        );
    }

    public function test_la_pantalla_ensena_el_mismo_orden_que_sirve_la_api(): void
    {
        // Si la pantalla ordenara por su cuenta, enseñaría un orden y el Moodle del cliente
        // recibiría otro: es la clase de discrepancia que nadie descubre hasta que un
        // cliente pregunta.
        $this->admin();
        $producto = $this->producto();

        $version = FeatureVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);
        Feature::create(['feature_version_id' => $version->id, 'title' => 'Sin orden', 'status' => 'published', 'sort_order' => null, 'created_by' => 1]);
        Feature::create(['feature_version_id' => $version->id, 'title' => 'Con orden', 'status' => 'published', 'sort_order' => 1, 'created_by' => 1]);

        $enPantalla = collect(
            Livewire::test(FeaturesVersionShow::class, ['product' => $producto, 'featureVersion' => $version])
                ->viewData('elementos')
        )->pluck('title')->all();

        $enLaApi = Feature::where('feature_version_id', $version->id)->ordered()->pluck('title')->all();

        $this->assertSame($enLaApi, $enPantalla);
    }

    public function test_se_dice_cuantos_no_tienen_orden_asignado(): void
    {
        // Es el estado real de los elementos que existen, y van al final: si no se dice,
        // el orden parece arbitrario.
        $this->admin();
        $producto = $this->producto();

        $version = FeatureVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);
        Feature::create(['feature_version_id' => $version->id, 'title' => 'Sin orden', 'status' => 'published', 'sort_order' => null, 'created_by' => 1]);

        Livewire::test(FeaturesVersionShow::class, ['product' => $producto, 'featureVersion' => $version])
            ->assertSee('sin orden asignado');
    }

    /* ===================== Lo que se sirve y lo que no ===================== */

    public function test_una_version_con_todo_en_borrador_avisa_de_que_no_entrega_nada(): void
    {
        // **El peor caso silencioso de estas pantallas**: tiene contenido, y por la API no
        // sale nada. Quien la reciba se lleva una respuesta vacía sin ningún error.
        $this->admin();
        $producto = $this->producto();

        $version = FeatureVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);
        Feature::create(['feature_version_id' => $version->id, 'title' => 'A medias', 'status' => 'draft', 'sort_order' => 1, 'created_by' => 1]);

        $componente = Livewire::test(FeaturesVersionShow::class, ['product' => $producto, 'featureVersion' => $version]);

        $this->assertSame(1, $componente->viewData('elementos')->count());
        $this->assertSame(0, $componente->viewData('publicados'));
        $componente->assertSee('Esta versión no entrega nada');
    }

    /* ===================== Y lo mismo en los otros dos tipos ===================== */

    public function test_el_orden_funciona_igual_en_recursos(): void
    {
        $this->admin();
        $producto = $this->producto();

        $version = ResourceVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);

        foreach (['Guía', 'Plantilla'] as $i => $titulo) {
            Resource::create([
                'resource_version_id' => $version->id,
                'title' => $titulo,
                'type' => 'external',
                'url' => 'https://tresipunt.com/' . $i,
                'status' => 'published',
                'is_public' => true,
                'sort_order' => null,
                'created_by' => 1,
                'created_at' => now()->subMinutes(10 - $i),
            ]);
        }

        $ids = Resource::where('resource_version_id', $version->id)->pluck('id', 'title');

        $componente = Livewire::test(ResourcesVersionShow::class, ['product' => $producto, 'resourceVersion' => $version]);
        $componente->call('moveUp', $ids['Plantilla']);

        $this->assertSame(
            'Plantilla > Guía',
            collect($componente->viewData('elementos'))->pluck('title')->implode(' > ')
        );
    }

    public function test_el_orden_funciona_igual_en_tutoriales(): void
    {
        $this->admin();
        $producto = $this->producto();

        $version = TutorialVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);

        foreach (['Primeros pasos', 'Configurar'] as $i => $titulo) {
            Tutorial::create([
                'tutorial_version_id' => $version->id,
                'title' => $titulo,
                'platform' => 'youtube',
                'videoid' => 'abc' . $i,
                'status' => 'published',
                'sort_order' => null,
                'created_by' => 1,
                'created_at' => now()->subMinutes(10 - $i),
            ]);
        }

        $ids = Tutorial::where('tutorial_version_id', $version->id)->pluck('id', 'title');

        $componente = Livewire::test(TutorialsVersionShow::class, ['product' => $producto, 'tutorialVersion' => $version]);
        $componente->call('moveUp', $ids['Configurar']);

        $this->assertSame(
            'Configurar > Primeros pasos',
            collect($componente->viewData('elementos'))->pluck('title')->implode(' > ')
        );
    }

    /* ===================== Lo propio de cada tipo ===================== */

    public function test_un_recurso_publicado_y_no_publico_avisa_de_que_no_se_sirve(): void
    {
        // **Son dos condiciones distintas** —publicado y público— y solo se veía la primera:
        // el chip decía «Se sirve» y el archivo no llegaba.
        $this->admin();
        $producto = $this->producto();

        $version = ResourceVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);
        Resource::create([
            'resource_version_id' => $version->id,
            'title' => 'Checklist interna',
            'type' => 'file',
            'path' => 'resources/checklist.docx',
            'original_filename' => 'checklist.docx',
            'mime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'size' => 41000,
            'status' => 'published',
            'is_public' => false,
            'sort_order' => 1,
            'created_by' => 1,
        ]);

        Livewire::test(ResourcesVersionShow::class, ['product' => $producto, 'resourceVersion' => $version])
            ->assertSee('Publicado pero no público: no se sirve');
    }

    public function test_un_tutorial_sin_identificador_de_video_se_señala(): void
    {
        // Es el único campo que puede estar mal y romper el tutorial en el Moodle del
        // cliente sin que nada falle aquí.
        $this->admin();
        $producto = $this->producto();

        $version = TutorialVersion::create(['product_id' => $producto->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);
        Tutorial::create([
            'tutorial_version_id' => $version->id,
            'title' => 'Sin vídeo',
            'platform' => 'youtube',
            'videoid' => '',
            'status' => 'published',
            'sort_order' => 1,
            'created_by' => 1,
        ]);

        Livewire::test(TutorialsVersionShow::class, ['product' => $producto, 'tutorialVersion' => $version])
            ->assertSee('Sin identificador de vídeo');
    }

    public function test_no_se_puede_ver_la_version_de_otro_producto(): void
    {
        // Los dos llegan de la URL y hay que cruzarlos (MGR-006).
        $this->admin();
        $mio = $this->producto();
        $ajeno = Product::create(['slug' => 'theme_fresk', 'name' => 'Fresk', 'tech' => 'moodle', 'actived' => true, 'enabled' => true]);

        $suVersion = FeatureVersion::create(['product_id' => $ajeno->id, 'version' => '2026010100', 'created_by' => 1, 'updated_by' => 1]);

        $this->get(route('products.features.version.show', [$mio, $suVersion]))->assertNotFound();
    }
}
