<?php

namespace Tests\Feature\Configurations;

use App\Livewire\Configurations\Environments\Create as TipoCreate;
use App\Livewire\Configurations\Environments\Edit as TipoEdit;
use App\Models\Auth\User;
use App\Models\Environments\Type;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * Crear un tipo de entorno (MGR-097).
 *
 * **El fallo que arregla**: el componente eran ocho líneas —`render()` y nada más— y su
 * vista un formulario completo. La pantalla se abría bien y **reventaba al usarla**: sin
 * llegar a enviarla, escribir en el primer campo lanzaba
 * `PublicPropertyNotFoundException: Public property [$name] not found`. Nunca ha
 * funcionado, y por eso los cuatro tipos que existen están puestos desde el seeder.
 *
 * Es la peor de las dos formas de estar roto: una pantalla que no existe se nota; una que
 * se abre y falla al escribir se descubre el día que alguien la necesita.
 */
class TipoDeEntornoCreateTest extends TestCase
{
    use RefreshDatabase;

    private function conPermiso(string $permiso): User
    {
        // La tabla `permissions` de este proyecto tiene `model` NOT NULL.
        Permission::firstOrCreate(
            ['name' => $permiso, 'guard_name' => 'web'],
            ['model' => 'configurations', 'description' => $permiso]
        );

        $usuario = User::factory()->create();
        $usuario->givePermissionTo($permiso);
        $this->actingAs($usuario);

        Cache::flush();

        return $usuario;
    }

    public function test_escribir_en_el_formulario_ya_no_revienta(): void
    {
        // **El caso literal del ticket.** Antes de esto, este `set` lanzaba.
        $this->conPermiso('admin.configurations.environments-types.create');

        Livewire::test(TipoCreate::class)
            ->set('name', 'Moodle Workplace')
            ->set('shortname', 'workplace')
            ->set('description', 'Entorno desarrollado en Moodle Workplace')
            ->assertHasNoErrors();
    }

    public function test_crea_el_tipo_y_vuelve_al_catalogo(): void
    {
        $this->conPermiso('admin.configurations.environments-types.create');

        Livewire::test(TipoCreate::class)
            ->set('name', 'Un tipo nuevo')
            ->set('shortname', 'tiponuevo')
            ->set('description', 'Para la prueba')
            ->call('store')
            ->assertHasNoErrors()
            ->assertRedirect(route('configurations.catalogs'));

        $this->assertDatabaseHas('types', [
            'name' => 'Un tipo nuevo',
            'shortname' => 'tiponuevo',
            'description' => 'Para la prueba',
        ]);
    }

    public function test_el_nombre_y_el_shortname_son_obligatorios(): void
    {
        $this->conPermiso('admin.configurations.environments-types.create');

        Livewire::test(TipoCreate::class)
            ->call('store')
            ->assertHasErrors(['name' => 'required', 'shortname' => 'required']);

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

    public function test_no_se_repite_un_shortname_vivo(): void
    {
        // Es la clave con la que `SyncAction` resuelve el tipo que manda el plugin: dos
        // tipos con el mismo shortname harían la resolución ambigua.
        $this->conPermiso('admin.configurations.environments-types.create');

        Type::create(['name' => 'Moodle LMS', 'shortname' => 'moodle']);

        Livewire::test(TipoCreate::class)
            ->set('name', 'Otro Moodle')
            ->set('shortname', 'moodle')
            ->call('store')
            ->assertHasErrors(['shortname' => 'unique']);

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

    public function test_el_shortname_de_un_tipo_borrado_sigue_ocupado_y_se_dice(): void
    {
        // **Lo impone el esquema, no una decisión de la pantalla.** `Type` tiene borrado
        // lógico —la fila sigue ahí— y `create_types_table` declara `shortname` con un
        // `unique()` normal, que no distingue borrados. Excluir la papelera en la
        // validación solo cambiaba este mensaje por una
        // `UniqueConstraintViolationException` de la base: se probó, y es lo que pasa.
        //
        // Así que lo que se arregla es el mensaje: **nombra la papelera**, porque si no
        // el error señala una fila que no se ve en ninguna pantalla.
        $this->conPermiso('admin.configurations.environments-types.create');

        Type::create(['name' => 'Goodle', 'shortname' => 'goodle'])->delete();

        $prueba = Livewire::test(TipoCreate::class)
            ->set('name', 'Goodle otra vez')
            ->set('shortname', 'goodle')
            ->call('store')
            ->assertHasErrors(['shortname' => 'unique']);

        $this->assertStringContainsString('borrado', $prueba->errors()->first('shortname'));

        $this->assertSame(1, Type::withTrashed()->where('shortname', 'goodle')->count());
    }

    public function test_editar_no_choca_consigo_mismo(): void
    {
        // La misma regla en las dos pantallas: dos criterios distintos para la misma
        // columna es una confusión esperando su turno. Y guardar sin cambiar el shortname
        // no puede rechazarse por estar «en uso» por uno mismo.
        $this->conPermiso('admin.configurations.environments-types.edit');

        $tipo = Type::create(['name' => 'Un tipo', 'shortname' => 'untipo']);

        Livewire::test(TipoEdit::class, ['type' => $tipo])
            ->set('name', 'Un tipo con otro nombre')
            ->call('update')
            ->assertHasNoErrors();

        $this->assertSame('Un tipo con otro nombre', $tipo->fresh()->name);
    }

    public function test_la_imagen_rechaza_un_svg(): void
    {
        Storage::fake('public');
        $this->conPermiso('admin.configurations.environments-types.create');

        $svg = UploadedFile::fake()->createWithContent(
            'icono.svg',
            '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>'
        );

        Livewire::test(TipoCreate::class)
            ->set('name', 'Con icono')
            ->set('shortname', 'conicono')
            ->set('image', $svg)
            ->call('store')
            ->assertHasErrors(['image' => 'mimes']);

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

    public function test_la_imagen_se_guarda_en_el_disco_publico(): void
    {
        Storage::fake('public');
        $this->conPermiso('admin.configurations.environments-types.create');

        Livewire::test(TipoCreate::class)
            ->set('name', 'Con icono')
            ->set('shortname', 'conicono')
            ->set('image', UploadedFile::fake()->image('icono.png', 64, 64))
            ->call('store')
            ->assertHasNoErrors();

        $guardada = Type::where('shortname', 'conicono')->first()->image;

        $this->assertNotNull($guardada, 'No se ha guardado la ruta de la imagen.');
        $this->assertStringStartsWith('images/environments/', $guardada);
        Storage::disk('public')->assertExists($guardada);
    }

    public function test_sin_permiso_no_se_entra_ni_se_guarda(): void
    {
        // Las dos puertas: el permiso de la ruta no se reaplica en /livewire/update
        // (MGR-005), así que se comprueba al entrar y al guardar.
        $this->actingAs(User::factory()->create());
        Cache::flush();

        Livewire::test(TipoCreate::class)->assertForbidden();
    }
}
