<?php

namespace App\Http\Requests\Api\Admin\V1;

use App\Models\Products\LicenseToken;
use Carbon\Carbon;
use Illuminate\Validation\Validator;

/**
 * Crear (`POST /clients/{client}/license-tokens`) o editar (`PATCH /license-tokens/{id}`)
 * una licencia: nombre, fechas y los campos de la ficha del panel.
 *
 * **El token `3IP-…` no se acepta nunca**: lo genera el Manager con
 * {@see LicenseToken::generarToken()}. Tampoco `active` —apagar una licencia corta el
 * servicio y va por el panel, con su motivo—, ni el cliente, que es el de la ruta.
 */
class LicenciaRequest extends PeticionDeAdministracion
{
    /**
     * Los de la ficha del panel (`LicenseTokens\Edit`), menos lo que corta servicio. Lo que
     * se escribe se devuelve en {@see \App\Http\Resources\Admin\V1\LicenseTokenResource}.
     */
    public const CAMPOS = ['name', 'end_at', 'observation', 'mode', 'metadata'];

    /**
     * Lo que **solo se fija al crear**, porque en una licencia que ya da servicio lo corta:
     *
     * - `rate_limit_per_minute`: bajarlo por debajo de lo que gasta el cliente deja su Moodle
     *   sin licencias ni contenido. El panel lo cambia en una modal que enseña el consumo
     *   (`RateLimitModal`).
     * - `start_at`: un inicio en el futuro hace que el Moodle reciba «Token aún no válido»
     *   **en el acto** (`ProductToken`). Lo encontró la auditoría de PRODSECU-215.
     *
     * Al editar se ignoran, como cualquier campo que la API no acepta.
     */
    public const SOLO_AL_CREAR = ['start_at', 'rate_limit_per_minute'];

    /** @return list<string> */
    public static function camposAceptados(bool $editando): array
    {
        return $editando ? self::CAMPOS : [...self::CAMPOS, ...self::SOLO_AL_CREAR];
    }

    public function rules(): array
    {
        $prefijo = $this->isMethod('PATCH') ? 'sometimes|' : '';

        return [
            // Opcional, como en el asistente del panel: qué es obligatorio lo dice el panel.
            'name' => 'nullable|string|max:255',
            'observation' => 'nullable|string|max:10000',
            'mode' => 'nullable|string|max:50',
            // Un objeto JSON. El panel lo escribe como texto y lo decodifica; aquí ya llega
            // decodificado. Acotado: sin tope, el único límite era `post_max_size`.
            'metadata' => 'nullable|array|max:50',
            // El mismo rango que la modal del panel.
            'rate_limit_per_minute' => 'nullable|integer|min:1|max:1000000',
            'start_at' => $prefijo . 'nullable|date_format:Y-m-d',
            // **Posterior a hoy, no «hoy en adelante».** `end_at` se guarda como el inicio
            // del día, así que un fin de hoy ya está vencido desde las 00:00 y el Moodle se
            // queda sin servicio en el acto (auditoría PRODSECU-215). Una licencia que
            // termina hoy o ya terminó se mira y se renueva desde el panel.
            'end_at' => $prefijo . 'nullable|date_format:Y-m-d|after:today',
        ];
    }

    /**
     * El fin no puede ir antes del inicio, **contando con las fechas que ya tiene** la
     * licencia: al editar puede llegar solo una de las dos.
     */
    public function withValidator(Validator $validator): void
    {
        $validator->after(function (Validator $validator) {
            // Un objeto JSON libre, pero no de cualquier tamaño: 50 claves podrían anidar
            // megas. Se mide lo que se guardaría.
            if (is_array($this->input('metadata')) && strlen((string) json_encode($this->input('metadata'))) > 16384) {
                $validator->errors()->add('metadata', 'metadata no puede pasar de 16 KB.');
            }

            if ($validator->errors()->hasAny(['start_at', 'end_at'])) {
                return;
            }

            $licencia = $this->route('licenseToken');
            $actual = $licencia instanceof LicenseToken ? $licencia : null;

            // Al editar, `start_at` no se acepta: cuenta el que ya tiene.
            $inicio = $actual === null && $this->has('start_at')
                ? $this->input('start_at')
                : $actual?->start_at?->format('Y-m-d');

            $fin =$this->has('end_at') ? $this->input('end_at') : $actual?->end_at?->format('Y-m-d');

            if ($inicio && $fin && Carbon::parse($fin)->lt(Carbon::parse($inicio))) {
                $validator->errors()->add('end_at', 'La fecha de fin no puede ser anterior a la de inicio (' . $inicio . ').');
            }
        });
    }

    public function messages(): array
    {
        return [
            'start_at.date_format' => 'La fecha de inicio va como AAAA-MM-DD.',
            'end_at.date_format' => 'La fecha de fin va como AAAA-MM-DD.',
            'metadata.array' => 'metadata va como un objeto JSON.',
            'mode.max' => 'mode no puede pasar de 50 caracteres.',
            'rate_limit_per_minute.integer' => 'El tope de peticiones por minuto es un número entero.',
            'rate_limit_per_minute.min' => 'El tope de peticiones por minuto tiene que ser al menos 1. Sin tope propio, no lo envíes.',
            'end_at.after' => 'La fecha de fin tiene que ser posterior a hoy: con la de hoy o una anterior, la licencia dejaría de dar servicio en el acto.',
            'metadata.max' => 'metadata no puede tener más de 50 claves.',
            'observation.max' => 'La observación no puede pasar de 10.000 caracteres.',
        ];
    }
}
