<?php

namespace Tests\Feature\Environments;

use App\Livewire\Environments\Edit;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Monitoring\Log as MonitoringLog;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * Cambiar el dominio de un entorno.
 *
 * **El dominio no es un dato descriptivo: es la llave.** La API encuentra el entorno
 * comparando el `host` que manda el plugin con `environments.domain`, así que cambiarlo
 * **corta el servicio de ese sitio en la siguiente petición** —`Environment not found for
 * this host`— hasta que su Moodle responda en el dominio nuevo.
 *
 * Lo que faltaba:
 *
 * 1. **Ningún rastro.** `Environments\Edit` no escribía en el log del panel. Si un
 *    cliente decía «llevamos dos días sin contenido», no había forma de saber que alguien
 *    le había tocado el dominio el martes.
 * 2. **La validación de unicidad no veía los entornos borrados**, y el índice único de la
 *    base sí los cuenta: se pasaba la validación y reventaba con un 500.
 * 3. **La validación comparaba distinto que la API**: solo quitaba la barra final. El
 *    índice de MySQL es insensible a mayúsculas y el de sqlite no, así que la misma regla
 *    se comportaba distinto según el motor.
 */
class DomainChangeTest extends TestCase
{
    use RefreshDatabase;

    private function usuario(): User
    {
        foreach (['admin.environments.index', 'admin.environments.edit'] as $nombre) {
            Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'environments', 'description' => $nombre]
            );
        }

        $user = User::factory()->create();
        $user->givePermissionTo(['admin.environments.index', 'admin.environments.edit']);
        $this->actingAs($user);
        Cache::flush();

        return $user;
    }

    private function entorno(string $dominio, string $env = 'pro'): Environment
    {
        $cliente = Client::create([
            'name' => 'Cliente Dominio ' . uniqid(),
            'shortname' => 'dom' . uniqid(),
            'actived' => true,
        ]);

        return Environment::create([
            'client_id' => $cliente->id,
            'name' => 'Sitio ' . uniqid(),
            'domain' => $dominio,
            'version' => '5.1.1',
            'env' => $env,
            'active' => true,
        ]);
    }

    /** @return \Livewire\Features\SupportTesting\Testable */
    private function editar(Environment $entorno, string $dominio)
    {
        return Livewire::test(Edit::class, ['environment' => $entorno])
            ->set('domain', $dominio)
            ->call('update');
    }

    /* ===================== El rastro ===================== */

    public function test_el_cambio_de_dominio_queda_registrado_con_los_dos_valores(): void
    {
        $usuario = $this->usuario();
        $entorno = $this->entorno('https://viejo.example');

        $this->editar($entorno, 'https://nuevo.example')->assertHasNoErrors();

        $mensaje = MonitoringLog::where('code', '16020')->value('msg');

        $this->assertStringContainsString('https://viejo.example', $mensaje);
        $this->assertStringContainsString('https://nuevo.example', $mensaje);
        $this->assertStringContainsString($usuario->name, $mensaje);
    }

    public function test_guardar_sin_cambiar_el_dominio_no_registra_nada(): void
    {
        // Editar el nombre de un entorno no es un cambio de dominio, y un log que avisa
        // de todo no avisa de nada.
        $this->usuario();
        $entorno = $this->entorno('https://igual.example');

        Livewire::test(Edit::class, ['environment' => $entorno])
            ->set('name', 'Otro nombre')
            ->call('update')
            ->assertHasNoErrors();

        $this->assertSame(0, MonitoringLog::where('code', '16020')->count());
    }

    public function test_el_aviso_explica_que_el_sitio_se_queda_sin_servicio(): void
    {
        // Quien cambia un dominio suele saber lo que hace, pero no siempre sabe que el
        // corte es inmediato.
        $this->usuario();
        $entorno = $this->entorno('https://antes.example');

        $this->editar($entorno, 'https://despues.example');

        $aviso = session('warning');

        $this->assertStringContainsString('dejará de recibir', $aviso);
        // Y dice el dominio anterior, para poder volver atrás sin buscarlo.
        $this->assertStringContainsString('https://antes.example', $aviso);
    }

    /* ===================== La unicidad ===================== */

    public function test_no_se_puede_repetir_el_dominio_de_otro_entorno(): void
    {
        $this->usuario();
        $this->entorno('https://ocupado.example');
        $mio = $this->entorno('https://mio.example');

        $this->editar($mio, 'https://ocupado.example')
            ->assertHasErrors(['domain']);

        $this->assertSame('https://mio.example', $mio->fresh()->domain);
    }

    public function test_un_dominio_que_solo_cambia_en_mayusculas_tambien_esta_ocupado(): void
    {
        // La regla comparaba solo quitando la barra final. El índice único de MySQL es
        // insensible a mayúsculas: pasaba la validación y lo rechazaba la base.
        $this->usuario();
        $this->entorno('https://ocupado.example');
        $mio = $this->entorno('https://mio.example');

        $this->editar($mio, 'https://OCUPADO.example/')
            ->assertHasErrors(['domain']);
    }

    public function test_un_entorno_borrado_sigue_ocupando_su_dominio(): void
    {
        // La regla hacía `Environment::where(...)`, que con SoftDeletes NO ve los
        // borrados; el índice único de la base sí los cuenta. Se pasaba la validación y
        // reventaba con un 500.
        $this->usuario();
        $borrado = $this->entorno('https://borrado.example');
        $borrado->delete();

        $mio = $this->entorno('https://mio.example');

        $this->editar($mio, 'https://borrado.example')
            ->assertHasErrors(['domain']);

        // Y el mensaje dice que es un borrado: «ya existe» sobre algo que no aparece en
        // ningún listado hace perder media hora.
        $this->editar($mio, 'https://borrado.example')->assertSee('BORRADO');
    }

    public function test_los_entornos_locales_si_pueden_repetir_dominio(): void
    {
        // Varios desarrolladores con el mismo `moodle.test`. Ver fixes/PRODSECU-192.
        $this->usuario();
        $this->entorno('https://moodle.test', env: 'local');
        $otro = $this->entorno('https://otro.test', env: 'local');

        $this->editar($otro, 'https://moodle.test')->assertHasNoErrors();

        $this->assertSame('https://moodle.test', $otro->fresh()->domain);
    }

    public function test_el_propio_entorno_no_choca_consigo_mismo(): void
    {
        $this->usuario();
        $entorno = $this->entorno('https://elmio.example');

        Livewire::test(Edit::class, ['environment' => $entorno])
            ->set('name', 'Renombrado')
            ->call('update')
            ->assertHasNoErrors();
    }
}
