<?php

namespace App\Http\Middleware;

use App\Support\ApiDeAdministracion;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Sanctum\PersonalAccessToken;
use Symfony\Component\HttpFoundation\Response;

/**
 * La puerta de la API de administración (`/api/admin/v1`, PRODSECU-215).
 *
 * Comprueba, por este orden:
 *
 * 1. **Que hay un token de Sanctum válido** —existe, no ha caducado (`expires_at`), no
 *    está revocado—. Si no, 401.
 * 2. **Que ha entrado con un token de verdad, no con la sesión del panel.** El guard de
 *    Sanctum mira primero el guard `web` (`config/sanctum.php`) y, si hay sesión, devuelve
 *    el usuario con un `TransientToken`, que **responde que sí a cualquier `can()`**. Sin
 *    esta comprobación, quien tuviera el panel abierto podría llamar a la API con sus
 *    cookies y todos los permisos. También 401: no es «no puedes», es «no te has
 *    identificado como pide esta API».
 * 3. **Que el token es del usuario de servicio.** Si mañana alguien crea tokens de Sanctum
 *    para otra cosa, no abren esta API. 403.
 * 4. **Que el token lleva el permiso de la ruta** (sus *abilities*, ver
 *    {@see ApiDeAdministracion::PERMISOS}). Los roles de Spatie no pintan nada aquí. 403.
 *
 * ## Por qué autentica aquí y no con `auth:sanctum`
 *
 * El orden de la cadena tiene que ser throttle → logger → autenticación → modelos de la
 * ruta: el throttle primero para que un ataque no llene `api_request_logs`, y el logger
 * antes de autenticar para que **los 401 queden registrados**, que es justo lo que se mira
 * cuando una integración deja de funcionar. Pero `auth:sanctum` está en la lista de
 * prioridad del kernel (`AuthenticatesRequests`) y Laravel lo **adelanta** por delante del
 * throttle y del logger, se escriba donde se escriba. Este middleware no está en esa lista,
 * así que se queda donde se pone.
 */
class AdminApiAbility
{
    /**
     * @param  string  ...$abilities  **Todas** son obligatorias. Las rutas de escritura piden
     *                                también la de lectura: responden con la ficha, y si no,
     *                                un token de solo escritura leería cualquier registro
     *                                con un `PATCH` vacío (auditoría PRODSECU-215).
     */
    public function handle(Request $request, Closure $next, string ...$abilities): Response
    {
        $usuario = Auth::guard('sanctum')->user();
        $token = $usuario?->currentAccessToken();

        if (! $token instanceof PersonalAccessToken) {
            return response()->json(['message' => 'Unauthenticated.'], 401);
        }

        if (! ApiDeAdministracion::esUsuarioDeServicio($usuario)) {
            return response()->json(['message' => 'Este token no es de la API de administración.'], 403);
        }

        // **Explícito, y no `$token->can()`**: `can()` da por buena la ability comodín
        // `*`, que es la que pone Sanctum cuando `createToken()` se llama sin lista. Un
        // token creado así por descuido abriría la API entera; aquí cada permiso tiene que
        // estar marcado.
        $faltan = array_values(array_diff($abilities, (array) $token->abilities));

        if ($abilities === [] || $faltan !== []) {
            return response()->json([
                'message' => 'El token no tiene permiso para esta operación (' . implode(', ', $faltan ?: ['sin permiso declarado']) . ').',
            ], 403);
        }

        // A partir de aquí `$request->user()` y `Auth::id()` son el usuario de servicio:
        // `Log::db()` lo anota solo y los controladores lo usan como `assigned_by`.
        Auth::shouldUse('sanctum');
        $request->setUserResolver(fn () => $usuario);

        return $next($request);
    }
}
