<?php

namespace Tests\Feature\Users;

use App\Livewire\Users\Index;
use App\Livewire\Users\UserDeleteModal;
use App\Models\Auth\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Listado de usuarios, desde `Usuarios.dc.html`.
 *
 * Lo que se fija: que la cuenta de la API no se cuente como persona, que se vea quien no
 * puede trabajar, y que borrar a alguien exija escribir su correo.
 */
class UsersScreenTest extends TestCase
{
    use RefreshDatabase;

    private function permiso(string $nombre): Permission
    {
        return Permission::firstOrCreate(
            ['name' => $nombre, 'guard_name' => 'web'],
            ['model' => 'users', 'description' => $nombre]
        );
    }

    private function admin(): User
    {
        $rol = Role::firstOrCreate(['name' => 'Admin', 'guard_name' => 'web']);

        foreach (['admin.users.index', 'admin.users.create', 'admin.users.edit',
                  'admin.users.destroy', 'admin.roles.index', 'admin.logs.index',
                  'admin.api-logs.index'] as $nombre) {
            $rol->givePermissionTo($this->permiso($nombre));
        }

        $user = User::factory()->create(['name' => 'Antonio Manzano']);
        $user->assignRole($rol);
        $this->actingAs($user);

        return $user;
    }

    private function conRol(string $nombreDelRol, array $permisos = [], array $extra = []): User
    {
        $rol = Role::firstOrCreate(['name' => $nombreDelRol, 'guard_name' => 'web']);

        foreach ($permisos as $nombre) {
            $rol->givePermissionTo($this->permiso($nombre));
        }

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

        return $user;
    }

    private function conSesion(User $user, \DateTimeInterface $cuando): void
    {
        DB::table('sessions')->insert([
            'id' => \Illuminate\Support\Str::random(40),
            'user_id' => $user->id,
            'ip_address' => '127.0.0.1',
            'user_agent' => 'test',
            'payload' => '',
            'last_activity' => $cuando->getTimestamp(),
        ]);
    }

    /* ===================== Personas y cuentas de servicio ===================== */

    public function test_la_cuenta_de_la_api_no_se_cuenta_como_persona(): void
    {
        // Estaba en la misma lista que las personas, con su «última actividad» —que no
        // significa nada para una credencial— y su botón de borrar.
        $this->admin();
        $this->conRol('WebService', ['api.datas.store'], ['name' => 'Web Services']);

        $componente = Livewire::test(Index::class);

        $personas = $componente->viewData('personas')->pluck('nombre')->all();
        $servicio = $componente->viewData('cuentasDeServicio')->pluck('nombre')->all();

        $this->assertSame(['Antonio Manzano'], $personas);
        $this->assertSame(['Web Services'], $servicio);
        $this->assertSame(1, $componente->viewData('totalPersonas'));
    }

    public function test_se_reconoce_la_cuenta_de_servicio_por_su_rol_y_no_por_su_nombre(): void
    {
        // El diseño la distinguía por cómo se llama, y eso se rompe con una segunda
        // cuenta de API.
        $this->admin();
        $this->conRol('WebService', [], ['name' => 'Integracion Zoho']);

        $servicio = Livewire::test(Index::class)->viewData('cuentasDeServicio')->pluck('nombre')->all();

        $this->assertSame(['Integracion Zoho'], $servicio);
    }

    /* ===================== Quien no puede trabajar ===================== */

    public function test_un_usuario_sin_rol_se_marca_y_sale_arriba(): void
    {
        // No puede abrir ninguna pantalla, y era invisible: su celda de rol salía vacía,
        // igual que si la etiqueta no hubiera cargado.
        $this->admin();
        $this->conRol('Manager', ['admin.products.index'], ['name' => 'Zzz Con rol']);
        User::factory()->create(['name' => 'Aaa Sin rol']);

        $componente = Livewire::test(Index::class);
        $personas = $componente->viewData('personas');

        // El primero de la lista es el que hay que atender, aunque alfabéticamente no.
        $this->assertTrue($personas->first()['sinRol']);
        $this->assertSame('Aaa Sin rol', $personas->first()['nombre']);
        $this->assertSame(1, $componente->viewData('conProblema'));

        $this->get(route('users.index'))
            ->assertOk()
            ->assertSee('no puede abrir ninguna pantalla');
    }

    public function test_un_rol_sin_permisos_se_dice_en_la_fila(): void
    {
        // Es el caso que tenía `Support` hasta hoy: el rol existe, la persona lo tiene, y
        // entra sin poder abrir nada.
        $this->admin();
        $this->conRol('Support', [], ['name' => 'Tamara Gil']);

        $tamara = Livewire::test(Index::class)->viewData('personas')->firstWhere('nombre', 'Tamara Gil');

        $this->assertTrue($tamara['rolSinPermisos']);
        $this->assertFalse($tamara['sinRol']);

        $this->get(route('users.index'))->assertOk()->assertSee('0 permisos');
    }

    public function test_los_roles_dicen_cuantos_permisos_y_cuantos_usuarios_tienen(): void
    {
        // Cuatro de los seis roles no tenían ni un usuario, y era un problema que solo se
        // veía consultando la base de datos.
        $this->admin();
        Role::firstOrCreate(['name' => 'Developer', 'guard_name' => 'web']);

        $roles = collect(Livewire::test(Index::class)->viewData('roles'))->keyBy('nombre');

        $this->assertTrue($roles['Developer']['sinUsuarios']);
        $this->assertTrue($roles['Developer']['sinPermisos']);
        $this->assertFalse($roles['Admin']['sinUsuarios']);
    }

    public function test_distingue_nunca_ha_entrado_de_hace_meses(): void
    {
        // No es lo mismo: uno es un alta sin estrenar y el otro alguien que quizá ya no
        // está en el equipo.
        $this->admin();

        $reciente = $this->conRol('Manager', ['admin.products.index'], ['name' => 'Entra a diario']);
        $this->conSesion($reciente, now()->subHour());

        $antiguo = $this->conRol('Manager', [], ['name' => 'No entra hace meses']);
        $this->conSesion($antiguo, now()->subMonths(6));

        $this->conRol('Manager', [], ['name' => 'Nunca entro']);

        $personas = Livewire::test(Index::class)->viewData('personas')->keyBy('nombre');

        $this->assertFalse($personas['Entra a diario']['nunca']);
        $this->assertFalse($personas['Entra a diario']['viejo']);

        $this->assertFalse($personas['No entra hace meses']['nunca']);
        $this->assertTrue($personas['No entra hace meses']['viejo']);

        $this->assertTrue($personas['Nunca entro']['nunca']);
    }

    /* ===================== Borrar ===================== */

    public function test_borrar_exige_escribir_el_correo(): void
    {
        $this->admin();
        $victima = $this->conRol('Manager', [], ['name' => 'Se va', 'email' => 'seva@tresipunt.com']);

        Livewire::test(UserDeleteModal::class, ['userId' => $victima->id])
            ->set('confirmacion', 'otro@correo.com')
            ->call('borrar')
            ->assertHasErrors(['confirmacion']);

        $this->assertNotNull($victima->fresh());
    }

    public function test_con_el_correo_correcto_se_borra_y_queda_registrado(): void
    {
        $this->admin();
        $victima = $this->conRol('Manager', [], ['name' => 'Se va', 'email' => 'seva@tresipunt.com']);

        Livewire::test(UserDeleteModal::class, ['userId' => $victima->id])
            ->set('confirmacion', 'seva@tresipunt.com')
            ->call('borrar')
            ->assertHasNoErrors();

        // Borrado lógico: el histórico del panel no se pierde.
        $this->assertNull(User::find($victima->id));
        $this->assertNotNull(User::withTrashed()->find($victima->id));

        $log = \App\Models\Monitoring\Log::where('code', '16023')->first();
        $this->assertNotNull($log);
        $this->assertStringContainsString('seva@tresipunt.com', $log->msg);
    }

    public function test_a_un_administrador_no_se_le_puede_quitar_el_acceso(): void
    {
        // Y se dice **antes** de intentarlo, no con un mensaje de error después.
        $this->admin();
        $otroAdmin = $this->conRol('Admin', [], ['name' => 'Sergio Rojo']);

        Livewire::test(UserDeleteModal::class, ['userId' => $otroAdmin->id])
            ->assertSee('no se le puede quitar el acceso')
            ->call('borrar');

        $this->assertNotNull($otroAdmin->fresh());
    }

    public function test_el_correo_se_compara_sin_distinguir_mayusculas(): void
    {
        $this->admin();
        $victima = $this->conRol('Manager', [], ['name' => 'Se va', 'email' => 'seva@tresipunt.com']);

        Livewire::test(UserDeleteModal::class, ['userId' => $victima->id])
            ->set('confirmacion', '  SEVA@Tresipunt.com  ')
            ->call('borrar')
            ->assertHasNoErrors();

        $this->assertNull(User::find($victima->id));
    }
}
