<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Spatie\Permission\Middleware\PermissionMiddleware;
use Spatie\Permission\Middleware\RoleMiddleware;
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;
use App\Http\Middleware\ProductToken;
use App\Http\Middleware\ApiRequestLogger;
use App\Http\Middleware\ThrottleApiRequests;
use App\Http\Middleware\AdminApiAbility;
use App\Http\Middleware\AdminApiBindings;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // Middlewares de Spatie Permission
        $middleware->alias([
            'role' => RoleMiddleware::class,
            'permission' => PermissionMiddleware::class,
            'role_or_permission' => RoleOrPermissionMiddleware::class,
            'product_token' => ProductToken::class,
            'api_request_logger' => ApiRequestLogger::class,
            'api_throttle' => ThrottleApiRequests::class,
            // La API de administración (PRODSECU-215): token de verdad, del usuario de
            // servicio y con la ability de la ruta. Ver `AdminApiAbility`.
            'admin_api' => AdminApiAbility::class,
            'admin_api_bindings' => AdminApiBindings::class,
        ]);
    })

    ->withExceptions(function (Exceptions $exceptions): void {
        // Para rutas API, siempre devolver JSON en lugar de vistas HTML
        $exceptions->shouldRenderJsonWhen(function ($request, \Throwable $e) {
            return $request->is('api/*');
        });

        /**
         * Un 404 de la API de administración no dice cómo se llaman nuestras clases.
         *
         * El render por defecto de un `ModelNotFoundException` en JSON es «No query results
         * for model [App\Models\Clients\Client] 99»: le cuenta a quien llama la estructura
         * interna del código. A Sistemas le basta con saber que no existe. Acotado a
         * `api/admin/*` para no cambiar nada del resto de la aplicación.
         *
         * Se engancha a `NotFoundHttpException` y no a `ModelNotFoundException` porque el
         * handler convierte la segunda en la primera **antes** de llamar a estos callbacks.
         * De paso cubre también una ruta que no existe bajo ese prefijo.
         */
        $exceptions->render(function (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e, $request) {
            if ($request->is('api/admin/*')) {
                return response()->json(['message' => 'No existe.'], 404);
            }
        });

        /**
         * Un fallo inesperado en la API de administración responde **sin tripas**, también con
         * `APP_DEBUG` encendido (pre y local lo tienen): ni traza, ni rutas del servidor, ni
         * SQL. La respuesta además se guarda en `api_request_logs`, que ven Support y Manager.
         * La excepción se sigue registrando entera en el log del servidor, que es donde se
         * investiga; la respuesta lleva el `request_uuid` para encontrarla (auditoría
         * PRODSECU-215).
         *
         * Solo los errores sin estado HTTP propio: 401, 403, 404, 409, 422 y 429 tienen su
         * render y no pasan por aquí.
         */
        $exceptions->render(function (\Throwable $e, $request) {
            if (! $request->is('api/admin/*')
                || $e instanceof \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface
                || $e instanceof \Illuminate\Validation\ValidationException
                || $e instanceof \Illuminate\Auth\AuthenticationException
                || $e instanceof \Illuminate\Http\Exceptions\HttpResponseException) {
                return null;
            }

            return response()->json([
                'message' => 'Error interno. Si se repite, avisa con este identificador.',
                'request_uuid' => $request->attributes->get('api_request_uuid'),
            ], 500);
        });

        /**
         * El fichero pasa de `post_max_size`: se dice, en vez de un 413 pelado.
         *
         * **Esta es la capa intermedia de PRODSECU-156.** Cuando el cuerpo de la petición
         * supera `post_max_size`, PHP descarta `$_POST` y `$_FILES` enteros y Laravel lanza
         * `PostTooLargeException` desde `ValidatePostSize`, **antes de que ninguna regla de
         * validación se ejecute**. Así que el mensaje bonito de «la imagen no puede superar
         * los X» no llega nunca: sale la página de error genérica del framework, y quien
         * sube la imagen no tiene forma de saber que el problema es el tamaño.
         *
         * Se distingue de la capa de nginx —que corta antes y **no es detectable desde
         * PHP**, ver `resources/js/subidas.js`— y de la validación, que es la única que da
         * un mensaje por sí sola.
         *
         * El número que se dice sale de `LimiteDeSubida`, o sea de la configuración real de
         * la máquina, no de una constante: es el punto de todo el ticket.
         */
        $exceptions->render(function (\Illuminate\Http\Exceptions\PostTooLargeException $e, $request) {
            $mensaje = 'El archivo es demasiado grande: el máximo que admite este servidor es '
                . \App\Support\LimiteDeSubida::comoTexto() . '.';

            // Las subidas de Livewire y del editor van por XHR y esperan JSON; una vista
            // les llegaría como texto que no saben interpretar.
            if ($request->expectsJson() || $request->is('livewire/*')) {
                return response()->json(['message' => $mensaje], 413);
            }

            return back()
                ->withInput($request->except(['thumbnail_image', 'header_image', 'image']))
                ->with('error', $mensaje);
        });
    })->create();
