<?php

namespace Tests\Feature\Configurations;

use App\Livewire\Configurations\OwnPlugins;
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\Product;
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;

/**
 * Catálogo de plugins propios — Configuración → Catálogos.
 *
 * Lo que se fija: que **quién es nuestro se declare y no se adivine**, que declararlo sea
 * poner un identificador y nada más, y que el catálogo de Productos siga contando sin
 * tener que repetir nada aquí.
 */
class OwnPluginsTest extends TestCase
{
    use RefreshDatabase;

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

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

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

        Cache::flush();

        return $user;
    }

    private function pluginEnUnEntorno(string $component, string $nombre = 'Un plugin'): Plugin
    {
        $cliente = Client::firstOrCreate(
            ['shortname' => 'op'],
            ['name' => 'Cliente OP', 'actived' => true]
        );

        $entorno = Environment::firstOrCreate(
            ['domain' => 'https://campus.op.test'],
            [
                'name' => 'Campus OP',
                'env' => 'pro',
                'version' => '5.1.1',
                'client_id' => $cliente->id,
                'active' => true,
                'refresh_at' => now()->subHour(),
            ]
        );

        return Plugin::create([
            'environment_id' => $entorno->id,
            'name' => $nombre,
            'component' => $component,
            'type' => explode('_', $component)[0],
            'versiondisk' => '2026010100',
            'versiondb' => '2026010100',
            'versionrequires' => '2025041400',
            'release' => '1.0.0',
            'has_updates' => false,
            'has_dependencies' => false,
        ]);
    }

    /* ===================== Declarar uno ===================== */

    public function test_declarar_un_plugin_solo_pide_el_identificador(): void
    {
        // **Es la razón de que este catálogo exista aparte del de Productos.** Un producto
        // tiene ficha, licencias y contenido versionado, y su alta es un formulario largo.
        // La mayoría de nuestros plugins no son productos comerciales, y para decir «esto
        // es nuestro» basta el identificador.
        $this->admin();

        Livewire::test(OwnPlugins::class)
            ->set('component', 'local_tresipuntmarket')
            ->call('anadir')
            ->assertHasNoErrors();

        $this->assertDatabaseHas('own_plugins', ['component' => 'local_tresipuntmarket']);
    }

    public function test_si_no_se_pone_nombre_se_coge_el_que_declara_el_plugin(): void
    {
        // Mejor el que trae el inventario que dejarlo vacío: es el que el propio plugin
        // dice de sí mismo.
        $this->admin();
        $this->pluginEnUnEntorno('local_tresipunt', '3iPunt');

        Livewire::test(OwnPlugins::class)
            ->set('component', 'local_tresipunt')
            ->call('anadir');

        $this->assertSame('3iPunt', OwnPlugin::where('component', 'local_tresipunt')->value('name'));
    }

    public function test_un_identificador_con_mala_forma_no_se_acepta(): void
    {
        // **Un identificador mal escrito no empareja con nada y el error es silencioso**:
        // el plugin simplemente no sale como nuestro, sin que nada falle. Por eso se
        // valida la forma `tipo_nombre`.
        $this->admin();

        foreach (['Local_Tresipunt', 'local tresipunt', 'tresipunt', '3ip-tools'] as $malo) {
            Livewire::test(OwnPlugins::class)
                ->set('component', $malo)
                ->call('anadir')
                ->assertHasErrors(['component']);
        }

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

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

        OwnPlugin::create(['component' => 'theme_fresk']);

        Livewire::test(OwnPlugins::class)
            ->set('component', 'theme_fresk')
            ->call('anadir')
            ->assertHasErrors(['component']);

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

    public function test_quitar_uno_es_logico_y_queda_en_el_log(): void
    {
        // Quitarlo de la lista no debería perder quién lo puso ni cuándo, y volver a
        // añadirlo es un clic.
        $this->admin();

        $plugin = OwnPlugin::create(['component' => 'format_fresk']);

        Livewire::test(OwnPlugins::class)->call('quitar', $plugin->id);

        $this->assertNull(OwnPlugin::find($plugin->id));
        $this->assertNotNull(OwnPlugin::withTrashed()->find($plugin->id));
        $this->assertDatabaseHas('logs', ['code' => '16029']);
    }

    /* ===================== Los dos catálogos ===================== */

    public function test_un_producto_cuenta_como_nuestro_sin_declararlo_aqui(): void
    {
        // Un producto es nuestro por definición: tiene ficha, licencias y contenido. Pedir
        // que además se declare aquí sería repetir el dato en dos sitios.
        $this->admin();

        Product::create([
            'slug' => 'report_fundae',
            'name' => 'Report Fundae',
            'tech' => 'moodle',
            'actived' => true,
            'enabled' => true,
        ]);

        $this->assertContains('report_fundae', OwnPlugin::sonNuestros());
    }

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

        Product::create(['slug' => 'theme_fresk', 'name' => 'Theme Fresk', 'tech' => 'moodle', 'actived' => true, 'enabled' => true]);
        // El mismo, declarado también a mano: no debe salir dos veces.
        OwnPlugin::create(['component' => 'theme_fresk']);
        OwnPlugin::create(['component' => 'local_tresipunt']);

        $nuestros = OwnPlugin::sonNuestros();

        $this->assertCount(2, $nuestros);
        $this->assertContains('theme_fresk', $nuestros);
        $this->assertContains('local_tresipunt', $nuestros);
    }

    public function test_los_productos_se_enseñan_para_no_declararlos_dos_veces(): void
    {
        $this->admin();

        Product::create(['slug' => 'report_fundae', 'name' => 'Report Fundae', 'tech' => 'moodle', 'actived' => true, 'enabled' => true]);

        $componente = Livewire::test(OwnPlugins::class);

        $this->assertSame(
            ['report_fundae'],
            collect($componente->viewData('porSerProducto'))->pluck('component')->all()
        );
        $componente->assertSee('ya cuentan, por ser productos');
    }

    /* ===================== La caché ===================== */

    public function test_declarar_uno_se_nota_en_el_momento(): void
    {
        // La lista se cachea diez minutos porque la lee el inventario de cada entorno en
        // cada render. Sin invalidarla al guardar, un plugin recién declarado tardaría en
        // aparecer como nuestro y parecería que el alta no ha funcionado.
        $this->admin();

        $this->assertSame([], OwnPlugin::sonNuestros());

        OwnPlugin::create(['component' => 'local_tresipunt']);

        $this->assertSame(['local_tresipunt'], OwnPlugin::sonNuestros());
    }

    /* ===================== Las sugerencias ===================== */

    public function test_se_sugieren_los_instalados_que_suenan_a_nuestros(): void
    {
        // En un Moodle hay 464 plugins y casi ninguno es nuestro: una lista alfabética no
        // serviría de nada, así que los que suenan van primero. Es una ayuda para no
        // escribir el identificador a mano, no una identificación.
        $this->admin();

        $this->pluginEnUnEntorno('mod_customcert', 'Custom certificate');
        $this->pluginEnUnEntorno('local_tresipuntmarket', 'Marketplace');

        $sugerencias = collect(Livewire::test(OwnPlugins::class)->viewData('sugerencias'));

        $this->assertSame('local_tresipuntmarket', $sugerencias->first()['component']);
        $this->assertTrue($sugerencias->first()['suena']);
        $this->assertFalse($sugerencias->firstWhere('component', 'mod_customcert')['suena']);
    }

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

        $this->pluginEnUnEntorno('local_tresipunt', '3iPunt');
        OwnPlugin::create(['component' => 'local_tresipunt']);

        $sugerencias = collect(Livewire::test(OwnPlugins::class)->viewData('sugerencias'));

        $this->assertFalse($sugerencias->contains('component', 'local_tresipunt'));
    }

    public function test_se_puede_declarar_un_sugerido_sin_escribir_nada(): void
    {
        $this->admin();
        $this->pluginEnUnEntorno('local_tresipuntsepelite', 'SEPE Lite');

        Livewire::test(OwnPlugins::class)
            ->call('anadirSugerido', 'local_tresipuntsepelite')
            ->assertHasNoErrors();

        $this->assertDatabaseHas('own_plugins', [
            'component' => 'local_tresipuntsepelite',
            'name' => 'SEPE Lite',
        ]);
    }

    /* ===================== Permisos ===================== */

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

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

        $plugin = OwnPlugin::create(['component' => 'theme_fresk']);

        Livewire::test(OwnPlugins::class)
            ->set('component', 'local_tresipunt')
            ->call('anadir')
            ->assertForbidden();

        Livewire::test(OwnPlugins::class)
            ->call('quitar', $plugin->id)
            ->assertForbidden();

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