<?php

namespace App\Providers;

use Carbon\Carbon;
use Illuminate\Auth\Events\Login;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Livewire\Livewire;
use Spatie\Permission\Middleware\PermissionMiddleware;
use Spatie\Permission\Middleware\RoleMiddleware;
use Spatie\Permission\Middleware\RoleOrPermissionMiddleware;
use App\Models\Auth\User;
use App\Models\Scss\ScssVersion;
use App\Models\Js\JsVersion;
use App\Models\ScssCdn\ScssCdnBundle;
use App\Models\Tutorials\TutorialVersion;
use App\Models\Tutorials\Tutorial;
use App\Models\Features\FeatureVersion;
use App\Models\Resources\ResourceVersion;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->ponerLasFechasEnEspanol();

        $this->hacerPersistenteElGateDePermisos();
        $this->anotarElUltimoAcceso();

        // Configurar route model binding para ScssVersion
        Route::bind('scssVersion', function ($value) {
            return ScssVersion::findOrFail($value);
        });

        // Configurar route model binding para JsVersion
        Route::bind('jsVersion', function ($value) {
            return JsVersion::findOrFail($value);
        });

        // Configurar route model binding para ScssCdnBundle
        Route::bind('bundle', function ($value) {
            return ScssCdnBundle::findOrFail($value);
        });

        // Configurar route model binding para TutorialVersion
        Route::bind('tutorialVersion', function ($value) {
            return TutorialVersion::findOrFail($value);
        });

        // Configurar route model binding para FeatureVersion
        Route::bind('featureVersion', function ($value) {
            return FeatureVersion::findOrFail($value);
        });

        // Configurar route model binding para ResourceVersion
        Route::bind('resourceVersion', function ($value) {
            return ResourceVersion::findOrFail($value);
        });

        // Configurar route model binding para Tutorial
        Route::bind('tutorial', function ($value) {
            return Tutorial::findOrFail($value);
        });
    }

    /**
     * Hace que el `permission:` de las rutas viaje también con las llamadas de
     * Livewire.
     *
     * El problema, en corto: `Route::middleware(['permission:admin.x.edit'])` protege
     * el **GET de la página** y nada más. Cuando se pulsa un botón, Livewire hace un
     * POST a `/livewire/update`, y ahí solo se reaplica el middleware que está en su
     * lista de persistentes
     * (`Livewire\Mechanisms\PersistentMiddleware\PersistentMiddleware`): Sanctum,
     * Jetstream, `Authenticate`, `Authorize`, `SubstituteBindings`… **El
     * `PermissionMiddleware` de Spatie no está en esa lista**, y `bootstrap/app.php`
     * solo lo registra como alias. Resultado: el único filtro que quedaba en las
     * mutaciones era `auth`, o sea "estar dentro". Ver known-issues MGR-005.
     *
     * Registrarlo aquí lo arregla para **todos** los componentes de una vez, incluidos
     * los que se escriban mañana. Livewire guarda el middleware de la ruta original en
     * el snapshot firmado y lo vuelve a ejecutar con sus mismos parámetros, así que el
     * permiso que se comprueba es exactamente el que declara la ruta.
     *
     * **No sustituye al `authorize()` de cada método**, y por eso se hacen las dos
     * cosas. Esto depende del transporte: cubre los componentes que viven en una ruta
     * con `permission:`, pero no a uno anidado en una página con un gate más laxo, ni
     * a uno alcanzado desde una ruta sin gate. El `authorize()` del método no depende
     * de por dónde llegue la llamada.
     */
    private function hacerPersistenteElGateDePermisos(): void
    {
        Livewire::addPersistentMiddleware([
            PermissionMiddleware::class,
            RoleMiddleware::class,
            RoleOrPermissionMiddleware::class,
        ]);
    }

    /**
     * Guarda la fecha del último acceso de cada usuario al entrar.
     *
     * **Por qué no basta con `sessions`.** El «último acceso» de la pantalla de Usuarios
     * salía de un `MAX(last_activity)` sobre `sessions`, y eso responde a «¿está dentro
     * ahora?», no a «¿cuándo entró por última vez?»: las filas de `sessions` se borran a
     * los `SESSION_LIFETIME` minutos. Con el valor de siempre —120— cualquiera que no
     * tuviera una pestaña abierta salía como **«Nunca ha entrado»** (MGR-017).
     *
     * Va con el evento `Login` de Laravel, que es el único sitio por el que se pasa
     * siempre: lo dispara el guard tanto en el formulario como al recordar la sesión con
     * la cookie de *remember*.
     *
     * `withoutTimestamps` a propósito: entrar no es modificar la cuenta, y sin eso cada
     * login movería `updated_at` y la columna «Modificado» diría que a todo el mundo le
     * han cambiado algo. `forceFill` porque la columna **no está en `$fillable`**, que es
     * justo lo que se quiere: no se toca desde ningún formulario.
     */
    private function anotarElUltimoAcceso(): void
    {
        Event::listen(Login::class, function (Login $evento) {
            $usuario = $evento->user;

            if (! $usuario instanceof User) {
                return;
            }

            User::withoutTimestamps(
                fn () => $usuario->forceFill(['last_login_at' => now()])->saveQuietly()
            );
        });
    }

    /**
     * Las fechas relativas, en español.
     *
     * **Lo que se veía.** «1d ago», «12 minutes ago», «2 hours ago» por todo el panel:
     * en los cortes de la API, en el log del sistema, en las fichas. No era una
     * traducción mal hecha, era Carbon hablando en su idioma por defecto porque nadie
     * le había dicho otro.
     *
     * Se pone aquí y no con `APP_LOCALE=es` a propósito. `APP_LOCALE` cambia el idioma
     * de las traducciones de Laravel —la validación incluida— y este proyecto **no
     * tiene carpeta `lang/`**: los mensajes de validación caerían al inglés igualmente
     * y encima el cambio afectaría a cosas que nadie ha revisado. Carbon es lo único
     * que hay que traducir, porque los textos del panel están escritos en español
     * directamente en las plantillas.
     */
    private function ponerLasFechasEnEspanol(): void
    {
        Carbon::setLocale('es');
    }
}
