<?php

namespace App\Livewire\Users;

use App\Models\Auth\User;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
use Spatie\Permission\Models\Role;

/**
 * Listado de usuarios del panel.
 *
 * Implementa `Usuarios.dc.html` del sistema de diseño (Claude Design).
 *
 * **Lo que había**: una tabla de ocho columnas —ID, Nombre, Email, Rol, Última actividad,
 * Creado, Modificado, acciones— para tres filas, con el rol como una etiqueta gris que no
 * dice nada y la cuenta de la API mezclada con las personas.
 *
 * Lo que cambia, y todo sale del brief:
 *
 * - **Las cuentas de servicio van aparte.** «Web Services» no es una persona: es la
 *   credencial con la que opera la API. No se le mira la última sesión ni se le da de baja
 *   «porque se fue».
 * - **El rol dice qué alcanza.** Con 6 roles y 126 permisos, «Manager» no informa a nadie.
 * - **Se ve quien no puede trabajar**: sin rol, o con un rol sin permisos.
 * - **«¿Sigue entrando?» en primer plano**, y distinguiendo «nunca ha entrado» —un alta
 *   sin estrenar— de «hace seis meses» —alguien que quizá ya no está—.
 *
 * **No hay paginado.** Son 3 usuarios hoy y 10–20 en producción: el equipo de Tresipunt.
 * Paginar de 10 en 10 partía el listado en dos por nada.
 */
class Index extends Component
{
    public string $search = '';

    protected $queryString = ['search'];

    /**
     * El rol que marca una cuenta de servicio.
     *
     * **Por el rol y no por el nombre**: el diseño distinguía «Web Services» por cómo se
     * llama, y eso se rompe en cuanto alguien crea una segunda cuenta de API. El rol
     * `WebService` es el dato, y es el que usa la propia API.
     */
    private const ROL_DE_SERVICIO = 'WebService';

    /** Un acceso más antiguo que esto ya no es «sigue entrando». */
    private const DIAS_PARA_DUDAR = 30;

    public function delete($id)
    {
        $this->authorize('admin.users.destroy');

        $user = User::findOrFail($id);

        if (!$user->is_deletable()) {
            session()->flash('error', 'Este usuario no se puede borrar porque es un administrador.');

            return;
        }

        $name = $user->name;
        $email = $user->email;

        $user->delete();

        session()->flash('success', "El usuario \"$name\" ($email) se eliminó con éxito.");
    }

    /**
     * Abre la modal de borrado.
     *
     * **Antes era un borrado directo** y el «no se puede borrar a un administrador»
     * llegaba después de pulsar, como un mensaje de error. La modal lo dice antes, y pide
     * escribir el correo: quitar el acceso a alguien no debería ser un clic.
     */
    public function abrirBorrado(int $userId): void
    {
        $this->authorize('admin.users.destroy');

        $this->dispatch('openModal',
            component: \App\Livewire\Users\UserDeleteModal::class,
            arguments: ['userId' => $userId]
        );
    }

    #[\Livewire\Attributes\On('usersChanged')]
    public function refrescar(): void
    {
        // El listado se repinta solo al volver del modal.
    }

    /**
     * Los seis roles con su alcance y cuánta gente los usa.
     *
     * **Es la parte que el brief pedía hacer visible**: cuatro de los seis roles no tienen
     * ni un usuario y `Support` tenía cero permisos. Un problema que solo se veía
     * consultando la base de datos.
     *
     * El número de permisos sale de la base, no de una lista escrita aquí: si mañana
     * alguien cambia el reparto, esta pantalla lo refleja.
     *
     * @return array<int, array<string, mixed>>
     */
    private function roles(): array
    {
        $total = \Spatie\Permission\Models\Permission::count();

        return Role::withCount(['permissions', 'users'])
            ->orderByDesc('permissions_count')
            ->get()
            ->map(function (Role $rol) use ($total) {
                return [
                    'nombre' => $rol->name,
                    'permisos' => $rol->permissions_count,
                    'usuarios' => $rol->users_count,
                    // Un rol sin permisos es un rol que no sirve para nada: quien lo
                    // tenga entra y no ve una sola pantalla.
                    'sinPermisos' => $rol->permissions_count === 0,
                    'sinUsuarios' => $rol->users_count === 0,
                    'esTodo' => $rol->permissions_count === $total,
                    'esDeServicio' => $rol->name === self::ROL_DE_SERVICIO,
                ];
            })
            ->all();
    }

    /**
     * Un usuario, con lo que la pantalla necesita saber de él.
     *
     * @return array<string, mixed>
     */
    private function pintar(User $user): array
    {
        $rol = $user->roles->first();
        $acceso = $user->last_access_at;

        // **Tres estados y no dos.** «Nunca ha entrado» y «no lo sabemos» se pintaban
        // igual, y no son lo mismo: el segundo es una cuenta anterior a que existiera
        // `last_login_at`. Decirle «alta sin estrenar» a alguien que entra todos los días
        // es afirmar lo que no se sabe (MGR-017, y la lección de MGR-076).
        $sinRegistro = $acceso === null && ! $user->tieneRegistroDeAcceso();
        $nunca = $acceso === null && ! $sinRegistro;
        $viejo = $acceso !== null && $acceso->diffInDays(now()) >= self::DIAS_PARA_DUDAR;

        return [
            'id' => $user->id,
            'nombre' => $user->name,
            'correo' => $user->email,
            'iniciales' => $this->iniciales($user->name),
            'rol' => $rol?->name,
            'permisosDelRol' => $rol?->permissions_count ?? 0,
            // Las dos formas de no poder trabajar, y hay que distinguirlas: una se
            // arregla asignando un rol y la otra arreglando el rol.
            'sinRol' => $rol === null,
            'rolSinPermisos' => $rol !== null && (int) $rol->permissions_count === 0,
            'esDeServicio' => $rol?->name === self::ROL_DE_SERVICIO,
            // Los administradores no se pueden borrar: se dice antes, no después.
            'protegido' => ! $user->is_deletable(),
            'sinRegistro' => $sinRegistro,
            'nunca' => $nunca,
            'viejo' => $viejo,
            'actividad' => $acceso?->diffForHumans(),
        ];
    }

    private function iniciales(string $nombre): string
    {
        $palabras = preg_split('/\s+/', trim($nombre), -1, PREG_SPLIT_NO_EMPTY) ?: [];

        if ($palabras === []) {
            return '?';
        }

        return collect($palabras)
            ->take(2)
            ->map(fn ($palabra) => mb_strtoupper(mb_substr($palabra, 0, 1)))
            ->implode('');
    }

    public function render()
    {
        $usuarios = User::query()
            ->with(['roles' => fn ($q) => $q->withCount('permissions')])
            ->addSelect([
                // La sesión viva, que dice si está dentro **ahora**. No es el último
                // acceso: eso lo guarda `users.last_login_at`, porque las filas de
                // `sessions` se borran a los `SESSION_LIFETIME` minutos y sin la columna
                // cualquiera que no tuviera una pestaña abierta salía como «nunca ha
                // entrado» (MGR-017). Se usa la más reciente de las dos: ver
                // `User::getLastAccessAtAttribute()`.
                //
                // Depende del driver de sesión: con las sesiones en Redis esto se queda a
                // null y el dato pasa a salir solo de la columna, que es lo correcto.
                'last_activity_at' => DB::table('sessions')
                    ->selectRaw('MAX(last_activity)')
                    ->whereColumn('sessions.user_id', 'users.id'),
            ])
            ->when($this->search !== '', function ($query) {
                $query->where(function ($sub) {
                    $termino = '%' . $this->search . '%';

                    $sub->where('name', 'like', $termino)
                        ->orWhere('email', 'like', $termino);
                });
            })
            ->orderBy('name')
            ->get()
            ->map(fn (User $user) => $this->pintar($user));

        // Las personas primero y las cuentas de servicio aparte: no son lo mismo y no se
        // miran igual.
        [$servicio, $personas] = $usuarios->partition(fn ($u) => $u['esDeServicio']);

        // Y dentro de las personas, **quien no puede trabajar arriba**: sin rol o con un
        // rol sin permisos. Es lo único de esta pantalla que hay que atender.
        $personas = $personas->sortBy(fn ($u) => $u['sinRol'] || $u['rolSinPermisos'] ? 0 : 1)
            ->values();

        return view('livewire.users.index', [
            'personas' => $personas,
            'cuentasDeServicio' => $servicio->values(),
            'roles' => $this->roles(),
            'conProblema' => $personas->filter(fn ($u) => $u['sinRol'] || $u['rolSinPermisos'])->count(),
            'totalPersonas' => User::whereDoesntHave(
                'roles',
                fn ($q) => $q->where('name', self::ROL_DE_SERVICIO)
            )->count(),
        ])->layout('layouts.app');
    }
}
