<?php

namespace Tests\Feature\Clients;

use App\Livewire\Clients\Create as ClienteCreate;
use App\Livewire\Clients\Edit as ClienteEdit;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * El formulario de un cliente: los contactos y los mensajes.
 *
 * **El fallo que arregla**: `contacts.*.name` estaba como `required_with:contacts.*`, que
 * expandido a la fila actual apunta al **propio array de esa fila** — siempre presente—, así
 * que el nombre era **siempre obligatorio**. Pulsar «Añadir contacto» y dejar la fila en
 * blanco, que es lo que pasa cuando alguien la añade y cambia de idea, **impedía guardar el
 * cliente**, con un «The contacts.0.name field is required when contacts.0 is present».
 *
 * Y lo irónico: el filtrado de filas vacías **ya existía** en `normalizeContacts()`, pero
 * corría después de validar, o sea cuando el formulario ya estaba rechazado.
 */
class FormularioDeClienteTest 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' => 'clients', 'description' => $permiso]
        );

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

        Cache::flush();

        return $usuario;
    }

    public function test_una_fila_de_contacto_en_blanco_no_impide_guardar(): void
    {
        // **El caso literal**: añadir el contacto, cambiar de idea, guardar.
        $this->conPermiso('admin.clients.create');

        Livewire::test(ClienteCreate::class)
            ->set('name', 'Ayuntamiento de Reus')
            ->set('shortname', 'reus')
            ->call('addContact')
            ->call('store')
            ->assertHasNoErrors();

        $cliente = Client::where('shortname', 'reus')->first();

        $this->assertNotNull($cliente, 'No se ha creado el cliente.');
        // La fila vacía no se guarda. Se comprueba «vacío» y no «null» porque el modelo
        // tiene un accessor que garantiza que `contacts` siempre sea un array: en la
        // columna hay null y se lee como `[]`.
        $this->assertEmpty($cliente->contacts);
    }

    public function test_varias_filas_en_blanco_tampoco(): void
    {
        $this->conPermiso('admin.clients.create');

        Livewire::test(ClienteCreate::class)
            ->set('name', 'Un cliente')
            ->set('shortname', 'uncliente')
            ->call('addContact')
            ->call('addContact')
            ->call('addContact')
            ->call('store')
            ->assertHasNoErrors();

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

    public function test_una_fila_a_medias_si_se_rechaza(): void
    {
        // **La distinción que importa.** Una fila con el correo puesto y el nombre en
        // blanco no es una fila olvidada: es un contacto mal rellenado, y callarlo
        // guardaría un contacto sin nombre.
        $this->conPermiso('admin.clients.create');

        Livewire::test(ClienteCreate::class)
            ->set('name', 'Un cliente')
            ->set('shortname', 'uncliente')
            ->call('addContact')
            ->set('contacts.0.email', 'alguien@example.test')
            ->call('store')
            ->assertHasErrors('contacts.0.name');

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

    public function test_los_contactos_rellenados_se_guardan(): void
    {
        $this->conPermiso('admin.clients.create');

        Livewire::test(ClienteCreate::class)
            ->set('name', 'Un cliente')
            ->set('shortname', 'uncliente')
            ->call('addContact')
            ->set('contacts.0.name', 'Marta')
            ->set('contacts.0.email', 'marta@example.test')
            ->call('addContact')
            ->call('store')
            ->assertHasNoErrors();

        $contactos = Client::where('shortname', 'uncliente')->first()->contacts;

        // El relleno se guarda; la fila vacía que iba detrás, no.
        $this->assertCount(1, $contactos);
        $this->assertSame('Marta', $contactos[0]['name']);
    }

    public function test_al_editar_pasa_lo_mismo(): void
    {
        // El trait lo comparten las dos pantallas, y el arreglo tiene que estar en las dos:
        // era el mismo `required_with`.
        $this->conPermiso('admin.clients.edit');

        $cliente = Client::create([
            'name' => 'Un cliente',
            'shortname' => 'uncliente',
            'actived' => true,
        ]);

        Livewire::test(ClienteEdit::class, ['client' => $cliente])
            ->call('addContact')
            ->call('update')
            ->assertHasNoErrors();
    }

    public function test_los_mensajes_estan_en_castellano(): void
    {
        // **Este proyecto no tiene carpeta `lang/`** —decisión documentada—, así que sin
        // mensajes explícitos los errores salen en inglés y con el nombre técnico del campo
        // en medio de un panel en castellano.
        $this->conPermiso('admin.clients.create');

        $prueba = Livewire::test(ClienteCreate::class)->call('store');

        $this->assertStringContainsString('obligatorio', $prueba->errors()->first('name'));
        $this->assertStringContainsString('obligatorio', $prueba->errors()->first('shortname'));
    }

    public function test_el_shortname_de_un_cliente_de_baja_sigue_ocupado_y_se_dice(): void
    {
        // `Client` tiene borrado lógico y el índice de la base no distingue borrados, así
        // que no se puede reutilizar. Lo que sí se puede es **decirlo**: si no, el error
        // señala una fila que no se ve en ninguna pantalla.
        $this->conPermiso('admin.clients.create');

        Client::create(['name' => 'El de antes', 'shortname' => 'repetido', 'actived' => true])->delete();

        $prueba = Livewire::test(ClienteCreate::class)
            ->set('name', 'El nuevo')
            ->set('shortname', 'repetido')
            ->call('store')
            ->assertHasErrors(['shortname' => 'unique']);

        $this->assertStringContainsString('de baja', $prueba->errors()->first('shortname'));
    }
}
