<?php

namespace Tests\Feature\Exportaciones;

use App\Livewire\Clients\Index as ListadoDeClientes;
use App\Livewire\Environments\Index as ListadoDeEntornos;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Products\LicenseToken;
use App\Support\ExportacionCsv;
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;

/**
 * Descargar en CSV los listados de clientes y entornos (PRODSECU-153).
 *
 * **Lo que más importa es lo que no sale**: tokens, credenciales, datos personales de
 * contacto y notas de texto libre. Por eso los tests meten esos datos con valores
 * reconocibles y comprueban que no aparecen en ninguna parte del fichero.
 */
class DescargarListadosTest extends TestCase
{
    use RefreshDatabase;

    private const TOKEN_DE_LICENCIA = '3IP-SECRETO-DE-LICENCIA-0001';
    private const TOKEN_DEL_MOODLE = 'moodletoken-secreto-abcdef123456';

    private function usuario(array $permisos = ['admin.clients.index', 'admin.environments.index']): User
    {
        $rol = Role::firstOrCreate(['name' => 'Exportador', 'guard_name' => 'web']);

        foreach ($permisos as $nombre) {
            $rol->givePermissionTo(Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'exports', 'description' => $nombre]
            ));
        }

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

        Cache::flush();

        return $user;
    }

    /** El contenido del CSV que ha devuelto la acción. */
    private function contenido($test): string
    {
        $descarga = $test->effects['download'] ?? null;
        $this->assertNotNull($descarga, 'la acción no ha devuelto ninguna descarga');

        return base64_decode($descarga['content']);
    }

    private function cliente(array $extra = []): Client
    {
        return Client::create(array_merge([
            'name' => 'Ayuntamiento Demo',
            'shortname' => 'ayto' . uniqid(),
            'crm_id' => 'CRM-' . uniqid(),
            'cif' => 'P0800000A',
            'razon_social' => 'Ayuntamiento de Demo',
            'actived' => true,
            'contacts' => [['name' => 'Persona Contacto', 'email' => 'persona.privada@cliente.test', 'phone' => '600111222']],
            'observation' => 'NOTA-INTERNA-PRIVADA',
            'description' => 'DESCRIPCION-PRIVADA',
        ], $extra));
    }

    /* ============ Clientes ============ */

    public function test_los_clientes_se_descargan_con_sus_datos(): void
    {
        $this->usuario();
        $this->cliente();

        $csv = $this->contenido(Livewire::test(ListadoDeClientes::class)->call('descargar'));

        $this->assertStringStartsWith("\xEF\xBB\xBF", $csv, 'sin BOM, Excel rompe los acentos');
        $this->assertStringContainsString('Razón social', $csv);
        $this->assertStringContainsString('Ayuntamiento Demo', $csv);
        $this->assertStringContainsString('P0800000A', $csv);
    }

    public function test_de_los_clientes_no_salen_contactos_ni_notas(): void
    {
        $this->usuario();
        $this->cliente(['deactivation_reason' => null]);

        $csv = $this->contenido(Livewire::test(ListadoDeClientes::class)->call('descargar'));

        foreach (['persona.privada@cliente.test', 'Persona Contacto', '600111222', 'NOTA-INTERNA-PRIVADA', 'DESCRIPCION-PRIVADA'] as $privado) {
            $this->assertStringNotContainsString($privado, $csv, "«{$privado}» no debe salir en la descarga");
        }
    }

    public function test_los_clientes_se_descargan_con_los_filtros_puestos(): void
    {
        $this->usuario();
        $this->cliente(['name' => 'Cliente De Alta']);
        $deBaja = $this->cliente(['name' => 'Cliente De Baja']);
        $deBaja->forceFill(['actived' => false])->save();

        // La pestaña por defecto es «alta».
        $csv = $this->contenido(Livewire::test(ListadoDeClientes::class)->call('descargar'));

        $this->assertStringContainsString('Cliente De Alta', $csv);
        $this->assertStringNotContainsString('Cliente De Baja', $csv);
    }

    public function test_la_descarga_deja_rastro_con_los_filtros(): void
    {
        $this->usuario();
        $this->cliente();

        Livewire::test(ListadoDeClientes::class)->set('search', 'Demo')->call('descargar');

        $this->assertDatabaseHas('logs', ['code' => '16046']);
        $this->assertStringContainsString('Demo', \App\Models\Monitoring\Log::where('code', '16046')->value('msg'));
    }

    public function test_sin_permiso_no_se_descargan_los_clientes(): void
    {
        $this->usuario(['admin.environments.index']);

        Livewire::test(ListadoDeClientes::class)->call('descargar')->assertForbidden();
    }

    /* ============ Entornos ============ */

    private function entornoConSecretos(): Environment
    {
        $cliente = $this->cliente();

        $token = LicenseToken::create([
            'client_id' => $cliente->id,
            'name' => 'Licencia Campus',
            'token' => self::TOKEN_DE_LICENCIA,
            'active' => true,
        ]);

        return Environment::create([
            'name' => 'Campus Demo',
            'domain' => 'https://campus.demo.test',
            'login_url' => 'https://campus.demo.test/login/index.php?saml=off',
            'version' => '5.1.1',
            'env' => 'pro',
            'active' => true,
            'key' => 'PROJ-123',
            'moodletoken' => self::TOKEN_DEL_MOODLE,
            'client_id' => $cliente->id,
            'license_token_id' => $token->id,
            'review_note' => 'NOTA-DE-REVISION-PRIVADA',
        ]);
    }

    public function test_los_entornos_se_descargan_con_sus_datos(): void
    {
        $this->usuario();
        $this->entornoConSecretos();

        $csv = $this->contenido(Livewire::test(ListadoDeEntornos::class)->set('activeFilter', '')->call('descargar'));

        $this->assertStringContainsString('Campus Demo', $csv);
        $this->assertStringContainsString('https://campus.demo.test', $csv);
        $this->assertStringContainsString('PROJ-123', $csv);
        $this->assertStringContainsString('Licencia Campus', $csv, 'la licencia sale por su nombre');
    }

    public function test_de_los_entornos_no_salen_tokens_credenciales_ni_notas(): void
    {
        $this->usuario();
        $this->entornoConSecretos();

        $csv = $this->contenido(Livewire::test(ListadoDeEntornos::class)->set('activeFilter', '')->call('descargar'));

        foreach ([self::TOKEN_DE_LICENCIA, self::TOKEN_DEL_MOODLE, 'saml=off', 'NOTA-DE-REVISION-PRIVADA', 'persona.privada@cliente.test'] as $privado) {
            $this->assertStringNotContainsString($privado, $csv, "«{$privado}» no debe salir en la descarga");
        }
    }

    public function test_los_entornos_se_descargan_con_los_filtros_puestos(): void
    {
        $this->usuario();
        $this->entornoConSecretos();
        $cliente = Client::first();
        Environment::create([
            'name' => 'Otro Sitio', 'domain' => 'https://otro.demo.test', 'version' => '4.5',
            'env' => 'pre', 'active' => true, 'client_id' => $cliente->id,
        ]);

        $csv = $this->contenido(Livewire::test(ListadoDeEntornos::class)->set('activeFilter', '')->set('search', 'Campus')->call('descargar'));

        $this->assertStringContainsString('Campus Demo', $csv);
        $this->assertStringNotContainsString('Otro Sitio', $csv);
    }

    public function test_sin_permiso_no_se_descargan_los_entornos(): void
    {
        $this->usuario(['admin.environments.index']);
        $this->actingAs(User::factory()->create());

        Livewire::test(ListadoDeEntornos::class)->call('descargar')->assertForbidden();
    }

    /* ============ El CSV ============ */

    public function test_una_celda_que_parece_una_formula_se_neutraliza(): void
    {
        // El nombre de un entorno lo manda el Moodle del cliente: no se puede fiar.
        $this->assertSame("'=HYPERLINK(\"http://x\")", ExportacionCsv::celda('=HYPERLINK("http://x")'));
        $this->assertSame("'+34 600", ExportacionCsv::celda('+34 600'));
        $this->assertSame("'-1+1", ExportacionCsv::celda('-1+1'));
        $this->assertSame("'@SUM(A1)", ExportacionCsv::celda('@SUM(A1)'));
    }

    public function test_lo_normal_se_deja_como_esta(): void
    {
        $this->assertSame('Campus', ExportacionCsv::celda('Campus'));
        $this->assertSame('-3', ExportacionCsv::celda(-3), 'un número negativo es un número');
        $this->assertSame('', ExportacionCsv::celda(null));
        $this->assertSame('Sí', ExportacionCsv::celda(true));
    }
}
