<?php

namespace Tests\Feature\Plugins;

use App\Livewire\Plugins\Ficha;
use App\Livewire\Plugins\Inventario;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Environments\Plugin;
use App\Models\Products\OwnPlugin;
use App\Models\Products\PluginFicha;
use App\Models\Products\Product;
use App\Support\FamiliaDePlugin;
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;

/**
 * De dónde es cada plugin, la misma regla en la ficha y en el inventario
 * (`FamiliaDePlugin`), y lo que se fija a mano en la ficha: nuestro, terceros y la página
 * del Marketplace.
 */
class ClasificacionDePluginsTest extends TestCase
{
    use RefreshDatabase;

    private Environment $entorno;

    protected function setUp(): void
    {
        parent::setUp();

        $cliente = Client::create(['name' => 'Cliente', 'shortname' => 'cls', 'actived' => true]);
        $this->entorno = Environment::create([
            'name' => 'Campus', 'domain' => 'https://campus.cls.test', 'env' => 'pro',
            'version' => '5.1.1', 'client_id' => $cliente->id, 'active' => true,
        ]);
    }

    private function usuario(bool $edita = true): void
    {
        $rol = Role::firstOrCreate(['name' => $edita ? 'Edita' : 'Mira', 'guard_name' => 'web']);
        foreach (array_filter(['admin.plugins.environment', $edita ? 'admin.configs.edit' : null]) as $nombre) {
            $rol->givePermissionTo(Permission::firstOrCreate(['name' => $nombre, 'guard_name' => 'web'], ['model' => 'plugins', 'description' => $nombre]));
        }
        $user = User::factory()->create();
        $user->assignRole($rol);
        $this->actingAs($user);
        Cache::flush();
    }

    private function plugin(string $component, ?bool $estandar): void
    {
        Plugin::create([
            'environment_id' => $this->entorno->id, 'name' => $component, 'component' => $component,
            'type' => strtok($component, '_'), 'isstandard' => $estandar,
            'versiondisk' => '2026010100', 'versiondb' => '2026010100', 'versionrequires' => '2024100700', 'release' => '1.0',
            'has_updates' => false, 'has_dependencies' => false,
        ]);
    }

    /* ============ La regla ============ */

    public function test_la_precedencia_de_la_familia(): void
    {
        Product::create(['name' => 'Fresk', 'slug' => 'theme_fresk', 'shortname' => 'frk']);
        OwnPlugin::create(['component' => 'local_nuestro', 'name' => 'Nuestro']);
        PluginFicha::create(['component' => 'mod_fijado', 'clasificacion' => 'terceros']);

        $this->plugin('theme_fresk', true);      // producto aunque el sitio diga estándar
        $this->plugin('local_nuestro', false);   // nuestro aunque el sitio diga terceros
        $this->plugin('mod_fijado', true);       // lo fijado a mano manda sobre el sitio
        $this->plugin('mod_assign', true);
        $this->plugin('mod_customcert', false);
        $this->plugin('mod_antiguo', null);

        $this->assertSame([
            'theme_fresk' => 'producto',
            'local_nuestro' => 'nuestro',
            'mod_fijado' => 'terceros',
            'mod_assign' => 'estandar',
            'mod_customcert' => 'terceros',
            'mod_antiguo' => 'sinclas',
        ], FamiliaDePlugin::de(['theme_fresk', 'local_nuestro', 'mod_fijado', 'mod_assign', 'mod_customcert', 'mod_antiguo']));
    }

    public function test_un_propio_borrado_ya_no_cuenta_como_nuestro(): void
    {
        OwnPlugin::create(['component' => 'local_quitado', 'name' => 'Quitado'])->delete();
        $this->plugin('local_quitado', false);

        $this->assertSame('terceros', FamiliaDePlugin::para('local_quitado'));
    }

    /* ============ La ficha: el origen, un solo control ============ */

    public function test_elegir_nuestro_lo_pone_en_el_catalogo_y_avisa(): void
    {
        $this->usuario();
        $this->plugin('mod_customcert', false);

        Livewire::test(Ficha::class, ['component' => 'mod_customcert'])
            ->call('fijarOrigen', 'nuestro')
            ->assertDispatched('ficha-guardada', mensaje: 'Origen: nuestro')
            ->assertSee('NUESTRO');

        $this->assertTrue(OwnPlugin::where('component', 'mod_customcert')->exists());
        $this->assertDatabaseHas('logs', ['code' => '16028']);
    }

    public function test_volver_a_marcar_uno_quitado_lo_restaura_y_no_choca_con_el_indice(): void
    {
        // El índice único de `component` cuenta los borrados.
        $this->usuario();
        $this->plugin('mod_customcert', false);
        OwnPlugin::create(['component' => 'mod_customcert', 'name' => 'Cert'])->delete();

        Livewire::test(Ficha::class, ['component' => 'mod_customcert'])->call('fijarOrigen', 'nuestro');

        $this->assertSame(1, OwnPlugin::withTrashed()->where('component', 'mod_customcert')->count());
        $this->assertFalse(OwnPlugin::withTrashed()->where('component', 'mod_customcert')->first()->trashed());
    }

    public function test_pasar_de_nuestro_a_terceros_lo_saca_del_catalogo(): void
    {
        // Una sola pregunta: elegir una opción deshace la otra.
        $this->usuario();
        $this->plugin('mod_customcert', true);
        OwnPlugin::create(['component' => 'mod_customcert', 'name' => 'Cert']);

        Livewire::test(Ficha::class, ['component' => 'mod_customcert'])
            ->call('fijarOrigen', 'terceros')
            ->assertSee('TERCEROS')
            ->assertSee('Fijado a mano en esta ficha');

        $this->assertFalse(OwnPlugin::where('component', 'mod_customcert')->exists());
        $this->assertSame('terceros', PluginFicha::where('component', 'mod_customcert')->value('clasificacion'));
        $this->assertDatabaseHas('logs', ['code' => '16047']);
    }

    public function test_automatico_vuelve_a_lo_que_diga_el_moodle(): void
    {
        $this->usuario();
        $this->plugin('mod_algo', true);
        PluginFicha::create(['component' => 'mod_algo', 'clasificacion' => 'terceros']);

        Livewire::test(Ficha::class, ['component' => 'mod_algo'])
            ->call('fijarOrigen', 'auto')
            ->assertSee('ESTÁNDAR DE MOODLE')
            ->assertSee('Moodle dice: estándar');

        $this->assertFalse(PluginFicha::where('component', 'mod_algo')->exists(), 'sin nada fijado, la fila sobra');
    }

    public function test_un_origen_inventado_no_cambia_nada(): void
    {
        $this->usuario();
        $this->plugin('mod_algo', false);

        Livewire::test(Ficha::class, ['component' => 'mod_algo'])->call('fijarOrigen', 'inventado');

        $this->assertFalse(PluginFicha::where('component', 'mod_algo')->exists());
        $this->assertFalse(OwnPlugin::where('component', 'mod_algo')->exists());
    }

    public function test_un_producto_no_se_toca_desde_la_ficha(): void
    {
        $this->usuario();
        Product::create(['name' => 'Fresk', 'slug' => 'theme_fresk', 'shortname' => 'frk']);
        $this->plugin('theme_fresk', false);

        Livewire::test(Ficha::class, ['component' => 'theme_fresk'])
            ->assertSee('Es un producto del catálogo')
            ->call('fijarOrigen', 'terceros');

        $this->assertFalse(PluginFicha::where('component', 'theme_fresk')->exists());
    }

    /* ============ La ficha: la página del Marketplace ============ */

    public function test_la_url_buena_del_marketplace_se_guarda_y_sustituye_a_la_busqueda(): void
    {
        $this->usuario();
        $this->plugin('local_tresipuntsepelite', false);

        Livewire::test(Ficha::class, ['component' => 'local_tresipuntsepelite'])
            ->set('marketplaceUrl', ' https://marketplace.moodle.com/plugins/3993 ')
            ->call('guardarUrl')
            ->assertHasNoErrors()
            ->assertDispatched('ficha-guardada', mensaje: 'Página del Marketplace guardada')
            ->assertSee('Página en Moodle Marketplace')
            ->assertSee('https://marketplace.moodle.com/plugins/3993', false)
            ->assertSee('Cambiar')
            ->assertDontSee('marketplace.moodle.com/?q=', false);

        $this->assertSame('https://marketplace.moodle.com/plugins/3993', PluginFicha::where('component', 'local_tresipuntsepelite')->value('marketplace_url'));
    }

    public function test_quitar_la_url_vuelve_a_la_busqueda(): void
    {
        $this->usuario();
        $this->plugin('mod_algo', false);
        PluginFicha::create(['component' => 'mod_algo', 'marketplace_url' => 'https://marketplace.moodle.com/plugins/1']);

        Livewire::test(Ficha::class, ['component' => 'mod_algo'])
            ->call('quitarUrl')
            ->assertSee('https://marketplace.moodle.com/?q=mod_algo', false);

        $this->assertFalse(PluginFicha::where('component', 'mod_algo')->exists());
    }

    public function test_una_url_a_medio_escribir_no_borra_la_buena_al_cambiar_el_origen(): void
    {
        // Lo que se teclea y no se guarda no puede pisar lo guardado.
        $this->usuario();
        $this->plugin('mod_algo', false);
        PluginFicha::create(['component' => 'mod_algo', 'marketplace_url' => 'https://marketplace.moodle.com/plugins/1']);

        Livewire::test(Ficha::class, ['component' => 'mod_algo'])
            ->set('marketplaceUrl', 'https://marketpl')
            ->call('fijarOrigen', 'terceros');

        $ficha = PluginFicha::where('component', 'mod_algo')->first();
        $this->assertSame('https://marketplace.moodle.com/plugins/1', $ficha->marketplace_url);
        $this->assertSame('terceros', $ficha->clasificacion);
    }

    public function test_guardar_la_url_no_toca_el_origen(): void
    {
        $this->usuario();
        $this->plugin('mod_algo', false);
        PluginFicha::create(['component' => 'mod_algo', 'clasificacion' => 'estandar']);

        Livewire::test(Ficha::class, ['component' => 'mod_algo'])
            ->set('marketplaceUrl', 'https://marketplace.moodle.com/plugins/2')
            ->call('guardarUrl');

        $this->assertSame('estandar', PluginFicha::where('component', 'mod_algo')->value('clasificacion'));
    }

    /** @return array<string, array{string}> */
    public static function urlsQueNoValen(): array
    {
        return [
            'vacía' => [''],
            'otro dominio' => ['https://example.com/plugins/3993'],
            'sin https' => ['http://marketplace.moodle.com/plugins/3993'],
            'javascript' => ['javascript:alert(1)'],
            'imita el dominio' => ['https://marketplace.moodle.com.evil.test/x'],
        ];
    }

    /**
     * @dataProvider urlsQueNoValen
     */
    public function test_una_url_que_no_es_del_marketplace_no_se_guarda(string $url): void
    {
        $this->usuario();
        $this->plugin('mod_algo', false);

        Livewire::test(Ficha::class, ['component' => 'mod_algo'])
            ->set('marketplaceUrl', $url)
            ->call('guardarUrl')
            ->assertHasErrors('marketplaceUrl')
            ->assertNotDispatched('ficha-guardada');

        $this->assertFalse(PluginFicha::where('component', 'mod_algo')->exists());
    }

    public function test_sin_permiso_de_edicion_no_se_ve_ni_se_puede_guardar(): void
    {
        $this->usuario(edita: false);
        $this->plugin('mod_algo', false);

        Livewire::test(Ficha::class, ['component' => 'mod_algo'])->assertDontSee('Clasificación y enlaces');

        // Cada uno en su componente: tras un 403, Livewire no deja seguir con el mismo.
        Livewire::test(Ficha::class, ['component' => 'mod_algo'])->call('fijarOrigen', 'terceros')->assertForbidden();
        Livewire::test(Ficha::class, ['component' => 'mod_algo'])
            ->set('marketplaceUrl', 'https://marketplace.moodle.com/plugins/1')->call('guardarUrl')->assertForbidden();
        $this->assertFalse(PluginFicha::where('component', 'mod_algo')->exists());
    }

    /* ============ El inventario ============ */

    public function test_el_inventario_filtra_por_origen(): void
    {
        $this->usuario();
        OwnPlugin::create(['component' => 'local_nuestro', 'name' => 'Nuestro']);
        $this->plugin('local_nuestro', false);
        $this->plugin('mod_assign', true);
        $this->plugin('mod_customcert', false);
        $this->plugin('mod_antiguo', null);

        $componentes = fn (string $origen) => Livewire::test(Inventario::class)->set('familia', $origen)
            ->viewData('filas')->pluck('component')->sort()->values()->all();

        $this->assertSame(['local_nuestro'], $componentes('nuestros'));
        $this->assertSame(['mod_assign'], $componentes('estandar'));
        $this->assertSame(['mod_customcert'], $componentes('terceros'));
        $this->assertSame(['mod_antiguo'], $componentes('sinclas'));
    }

    public function test_el_desplegable_de_origen_cuenta_cada_uno(): void
    {
        $this->usuario();
        $this->plugin('mod_assign', true);
        $this->plugin('mod_forum', true);
        $this->plugin('mod_customcert', false);

        $porOrigen = Livewire::test(Inventario::class)->viewData('porOrigen');

        $this->assertSame(['nuestros' => 0, 'estandar' => 2, 'terceros' => 1, 'sinclas' => 0], $porOrigen);
    }

    public function test_un_origen_inventado_en_la_url_no_filtra(): void
    {
        $this->usuario();
        $this->plugin('mod_assign', true);

        $this->assertSame(1, Livewire::test(Inventario::class)->set('familia', 'inventado')->viewData('filas')->count());
    }
}
