<?php

namespace Tests\Feature\LicenseTokens;

use App\Livewire\Environments\Edit as EditarEntorno;
use App\Livewire\Environments\Index as ListadoDeEntornos;
use App\Livewire\LicenseTokens\Index as ListadoDeLicencias;
use App\Livewire\LicenseTokens\Show as FichaDeLicencia;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Products\LicenseToken;
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 clave del proyecto en Jira (PRODSECU-182): se puede poner en el Manager, el Moodle la
 * pisa en cada sync, y se ve en las licencias como chapas.
 */
class ClavesDeProyectoTest extends TestCase
{
    use RefreshDatabase;

    private const TOKEN = '3IP-TEST-CLAVES-0001';

    private LicenseToken $licencia;

    private Client $cliente;

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

        $rol = Role::firstOrCreate(['name' => 'Claves', 'guard_name' => 'web']);
        foreach (['admin.environments.index', 'admin.environments.edit', 'admin.license-tokens.index', 'admin.license-tokens.show'] as $nombre) {
            $rol->givePermissionTo(Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'tests', 'description' => $nombre]
            ));
        }
        $user = User::factory()->create();
        $user->assignRole($rol);
        $this->actingAs($user);
        Cache::flush();

        $this->cliente = Client::create(['name' => 'Cliente Claves', 'shortname' => 'clv', 'actived' => true]);
        $this->licencia = LicenseToken::create([
            'client_id' => $this->cliente->id,
            'name' => 'Licencia Claves',
            'token' => self::TOKEN,
            'active' => true,
        ]);
    }

    private function entorno(string $nombre, ?string $clave, string $env = 'pro'): Environment
    {
        return Environment::create([
            'name' => $nombre,
            'domain' => 'https://' . str()->slug($nombre) . '.claves.test',
            'version' => '5.1.1',
            'env' => $env,
            'active' => true,
            'key' => $clave,
            'client_id' => $this->cliente->id,
            'license_token_id' => $this->licencia->id,
        ]);
    }

    private function sincronizar(Environment $entorno, ?string $clave): void
    {
        $site = ['version' => '2025100601.03', 'release' => '5.1.1', 'type' => 'moodle', 'env' => 'pro'];
        if ($clave !== null) {
            $site['key'] = $clave;
        }

        $this->withHeaders(['Authorization' => 'Bearer ' . self::TOKEN, 'Accept' => 'application/json'])
            ->postJson('/api/v1', [
                'action' => 'sync',
                'host' => $entorno->domain,
                'plugin' => 'local_tresipunt',
                'version' => '2026080604',
                'site' => $site,
                'data' => ['plugins' => [['name' => 'P', 'component' => 'mod_p', 'type' => 'mod', 'versiondisk' => '1', 'versiondb' => '1']]],
            ])->assertSuccessful();
    }

    /* ============ Se pone en el Manager, el Moodle la pisa ============ */

    public function test_se_puede_poner_desde_el_manager(): void
    {
        $entorno = $this->entorno('Campus', null);

        Livewire::test(EditarEntorno::class, ['environment' => $entorno])
            ->set('key', '  PROJ-1  ')
            ->call('update')
            ->assertHasNoErrors();

        $this->assertSame('PROJ-1', $entorno->fresh()->key);
    }

    public function test_vaciarla_la_deja_en_null(): void
    {
        $entorno = $this->entorno('Campus', 'PROJ-1');

        Livewire::test(EditarEntorno::class, ['environment' => $entorno])
            ->set('key', '')
            ->call('update');

        $this->assertNull($entorno->fresh()->key);
    }

    public function test_el_moodle_la_pisa_si_la_manda(): void
    {
        $entorno = $this->entorno('Campus', 'PUESTA-A-MANO');

        $this->sincronizar($entorno, 'DEL-MOODLE');

        $this->assertSame('DEL-MOODLE', $entorno->fresh()->key);
    }

    public function test_si_el_moodle_no_la_manda_se_queda_la_del_manager(): void
    {
        $entorno = $this->entorno('Campus', 'PUESTA-A-MANO');

        $this->sincronizar($entorno, null);
        $this->assertSame('PUESTA-A-MANO', $entorno->fresh()->key);

        // Vacía cuenta como no mandarla: no borra la buena.
        $this->sincronizar($entorno, '');
        $this->assertSame('PUESTA-A-MANO', $entorno->fresh()->key);
    }

    /* ============ Se ve en las licencias ============ */

    public function test_la_licencia_junta_las_claves_distintas_de_sus_entornos(): void
    {
        $this->entorno('Pre', 'PROJ-1', 'pre');
        $this->entorno('Pro', 'PROJ-1');
        $this->entorno('Otro', 'PROJ-2');
        $this->entorno('Sin clave', null);
        $this->entorno('Con espacios', '   ');

        $this->assertSame(['PROJ-1', 'PROJ-2'], $this->licencia->fresh()->clavesDeProyecto());
    }

    public function test_el_listado_de_licencias_las_ensena(): void
    {
        $this->entorno('Pro', 'PROJ-1');
        $this->entorno('Otro', 'PROJ-2');

        Livewire::test(ListadoDeLicencias::class)
            ->assertSee('PROJ-1')
            ->assertSee('PROJ-2');
    }

    public function test_la_ficha_de_la_licencia_las_ensena(): void
    {
        $this->entorno('Pro', 'PROJ-1');

        Livewire::test(FichaDeLicencia::class, ['license_token' => $this->licencia])
            ->assertSee('PROJ-1');
    }

    public function test_el_listado_de_licencias_no_hace_una_consulta_por_fila(): void
    {
        // Diez licencias con su entorno: las claves salen de una precarga, no de diez
        // consultas.
        for ($i = 1; $i <= 10; $i++) {
            $otra = LicenseToken::create([
                'client_id' => $this->cliente->id, 'name' => 'L' . $i,
                'token' => '3IP-TEST-CLAVES-1' . str_pad((string) $i, 3, '0', STR_PAD_LEFT), 'active' => true,
            ]);
            Environment::create([
                'name' => 'E' . $i, 'domain' => 'https://e' . $i . '.claves.test', 'version' => '5.1.1',
                'env' => 'pro', 'active' => true, 'key' => 'K-' . $i,
                'client_id' => $this->cliente->id, 'license_token_id' => $otra->id,
            ]);
        }

        \Illuminate\Support\Facades\DB::enableQueryLog();
        Livewire::test(ListadoDeLicencias::class)->assertSee('K-10');
        $deEntornos = collect(\Illuminate\Support\Facades\DB::getQueryLog())
            ->filter(fn ($q) => str_starts_with(strtolower($q['query']), 'select "id", "license_token_id", "key" from "environments"')
                || str_contains(strtolower($q['query']), 'select "id", "key" from "environments"'));
        \Illuminate\Support\Facades\DB::disableQueryLog();

        $this->assertLessThanOrEqual(1, $deEntornos->count(), 'las claves se están consultando por fila');
    }

    /* ============ Al crear el entorno ============ */

    public function test_se_puede_poner_al_crear_desde_el_cliente(): void
    {
        $this->darPermiso('admin.environments.create');

        Livewire::test(\App\Livewire\Environments\CreateFromClient::class, ['client' => $this->cliente])
            ->set('name', 'Nuevo')
            ->set('env', 'pro')
            ->set('version', '5.1.1')
            ->set('domain', 'https://nuevo.claves.test')
            ->set('key', ' PROJ-NUEVO ')
            ->call('save')
            ->assertHasNoErrors();

        $this->assertSame('PROJ-NUEVO', Environment::where('name', 'Nuevo')->value('key'));
    }

    public function test_se_puede_poner_en_el_asistente_de_alta(): void
    {
        $this->darPermiso('admin.environments.create');

        Livewire::test(\App\Livewire\Environments\CreateWizard::class)
            ->call('selectClient', $this->cliente->id)
            ->set('formData.name', 'Del asistente')
            ->set('formData.env', 'pro')
            ->set('formData.version', '5.1.1')
            ->set('formData.domain', 'https://asistente.claves.test')
            ->set('formData.key', 'PROJ-ASIS')
            ->call('saveEnvironment')
            ->assertHasNoErrors();

        $this->assertSame('PROJ-ASIS', Environment::where('name', 'Del asistente')->value('key'));
    }

    public function test_al_crear_sin_clave_queda_en_null(): void
    {
        $this->darPermiso('admin.environments.create');

        Livewire::test(\App\Livewire\Environments\CreateFromClient::class, ['client' => $this->cliente])
            ->set('name', 'Sin clave')
            ->set('env', 'pro')
            ->set('version', '5.1.1')
            ->set('domain', 'https://sinclave.claves.test')
            ->call('save');

        $this->assertNull(Environment::where('name', 'Sin clave')->value('key'));
    }

    private function darPermiso(string $permiso): void
    {
        Role::findByName('Claves')->givePermissionTo(Permission::firstOrCreate(
            ['name' => $permiso, 'guard_name' => 'web'],
            ['model' => 'tests', 'description' => $permiso]
        ));
        app(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions();
    }

    /* ============ En la fila del entorno ============ */

    public function test_la_tarjeta_del_entorno_la_ensena_junto_al_cliente(): void
    {
        $entorno = $this->entorno('Campus', 'PROJ-1');

        Livewire::test(\App\Livewire\Environments\Components\EnvironmentCard::class, [
            'environment' => $entorno, 'client' => $this->cliente, 'showClient' => true,
        ])->assertSee('PROJ-1');
    }

    /* ============ Se filtra por ella ============ */

    public function test_los_entornos_se_filtran_por_proyecto(): void
    {
        $this->entorno('Uno', 'PROJ-1');
        $this->entorno('Dos', 'PROJ-1', 'pre');
        $this->entorno('Tres', 'PROJ-2');

        $test = Livewire::test(ListadoDeEntornos::class)->set('env', '')->set('proyecto', 'PROJ-1');

        $this->assertEqualsCanonicalizing(['Uno', 'Dos'], $test->viewData('environments')->pluck('name')->all());
        $this->assertTrue($test->instance()->hayFiltros());
    }

    public function test_el_desplegable_de_proyectos_cuenta_los_entornos(): void
    {
        $this->entorno('Uno', 'PROJ-1');
        $this->entorno('Dos', 'PROJ-1');
        $this->entorno('Tres', 'PROJ-2');
        $this->entorno('Sin', null);

        $proyectos = Livewire::test(ListadoDeEntornos::class)->viewData('proyectos');

        $this->assertSame(['PROJ-1' => 2, 'PROJ-2' => 1], $proyectos);
    }

    public function test_limpiar_filtros_quita_el_de_proyecto(): void
    {
        $test = Livewire::test(ListadoDeEntornos::class)->set('proyecto', 'PROJ-1')->call('limpiarFiltros');

        $this->assertSame('', $test->get('proyecto'));
    }

    public function test_las_licencias_se_filtran_por_proyecto(): void
    {
        $this->entorno('Pro', 'PROJ-1');
        $otra = LicenseToken::create(['client_id' => $this->cliente->id, 'name' => 'Licencia Otra', 'token' => '3IP-TEST-OTRA-0001', 'active' => true]);
        Environment::create([
            'name' => 'De la otra', 'domain' => 'https://otra.claves.test', 'version' => '5.1.1', 'env' => 'pro',
            'active' => true, 'key' => 'PROJ-2', 'client_id' => $this->cliente->id, 'license_token_id' => $otra->id,
        ]);

        Livewire::test(ListadoDeLicencias::class)
            ->assertViewHas('proyectos', ['PROJ-1', 'PROJ-2'])
            ->set('proyecto', 'PROJ-2')
            ->assertSee('Licencia Otra')
            ->assertDontSee('Licencia Claves');
    }

    /* ============ Y se busca por ella ============ */

    public function test_las_licencias_se_buscan_por_clave(): void
    {
        $this->entorno('Pro', 'PROJ-777');
        LicenseToken::create(['client_id' => $this->cliente->id, 'name' => 'Licencia Ajena', 'token' => '3IP-TEST-AJENA-0001', 'active' => true]);

        Livewire::test(ListadoDeLicencias::class)
            ->set('search', 'PROJ-777')
            ->assertSee('Licencia Claves')
            ->assertDontSee('Licencia Ajena');
    }

    public function test_los_entornos_se_buscan_por_clave(): void
    {
        $this->entorno('Campus Buscado', 'PROJ-777');
        $this->entorno('Campus Otro', 'PROJ-1');

        // Cada fila es un componente hijo, que el test del listado no pinta: se mira lo que
        // devuelve la consulta.
        $nombres = Livewire::test(ListadoDeEntornos::class)
            ->set('search', 'PROJ-777')
            ->viewData('environments')
            ->pluck('name')
            ->all();

        $this->assertSame(['Campus Buscado'], $nombres);
    }
}
