<?php

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

use App\Http\Resources\Admin\V1\EnvironmentResource;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Services\Api\HostNormalizer;
use Illuminate\Validation\Rule;

/**
 * Crear (`POST /clients/{client}/environments`) o editar (`PATCH /environments/{id}`) un
 * entorno.
 *
 * **Las reglas de `env` y del dominio son las del panel**: {@see Environment::reglaDeEnv()}
 * y {@see Environment::reglasDelDominio()}. El dominio es la llave con la que la API de los
 * Moodles encuentra el sitio, y `env` decide si está exento del índice único: no pueden
 * validarse distinto según por dónde se dé de alta.
 *
 * Solo se aceptan `name`, `domain`, `env`, `type_id` y `license_token_id`. Lo demás se
 * ignora: `active` (apagar corta el servicio), `client_id` (moverlo de cliente se hace en
 * el panel), y las credenciales y datos que manda el propio Moodle al sincronizar.
 */
class EntornoRequest extends PeticionDeAdministracion
{
    /**
     * Los del formulario del panel, menos lo que corta servicio. `description`, `key` (la
     * clave del proyecto de Jira), `version` y `login_url` **se escriben pero no se
     * devuelven**: la respuesta sigue siendo la mínima de {@see EnvironmentResource}.
     */
    public const CAMPOS = ['name', 'domain', 'env', 'type_id', 'license_token_id', 'description', 'key', 'version', 'login_url'];

    private function entorno(): ?Environment
    {
        $entorno = $this->route('environment');

        return $entorno instanceof Environment ? $entorno : null;
    }

    /** El `env` con el que se valida el dominio: el que llega o, al editar, el que tiene. */
    private function envEfectivo(): ?string
    {
        $env = $this->input('env', $this->entorno()?->env);

        return is_string($env) ? $env : null;
    }

    protected function prepareForValidation(): void
    {
        $entorno = $this->entorno();

        // Al editar, cambiar `env` cambia lo que se exige al dominio —pasar de `local` a
        // `pro` exige https:// y que no esté repetido—, así que el dominio que tiene se
        // valida otra vez aunque no llegue.
        if ($entorno !== null && $this->has('env') && ! $this->has('domain')) {
            $this->merge(['domain' => $entorno->domain]);
        }

        $this->conflictoSiElDominioEstaOcupado();
    }

    /**
     * Un dominio que ya usa otro entorno visible es un 409 **con ese entorno**, antes de
     * validar: un script que reintenta un alta recibe el sitio en vez de un error.
     *
     * Se compara con el mismo normalizador que usa la API de los Moodles para encontrar el
     * sitio ({@see HostNormalizer}). Si el que lo ocupa está **borrado**, no se resuelve
     * aquí: lo dice la regla del dominio con un 422, que explica que hay que restaurarlo o
     * borrarlo del todo desde el panel.
     */
    private function conflictoSiElDominioEstaOcupado(): void
    {
        $dominio = $this->input('domain');

        if (! is_string($dominio) || trim($dominio) === '' || $this->envEfectivo() === Environment::USO_EXENTO_DEL_INDICE) {
            return;
        }

        $normalizado = HostNormalizer::normalize($dominio);
        $propio = $this->entorno()?->id;

        $existente = Environment::query()
            ->where('env', '!=', Environment::USO_EXENTO_DEL_INDICE)
            ->when($propio !== null, fn ($q) => $q->whereKeyNot($propio))
            ->get(['id', 'domain', 'client_id'])
            ->first(fn (Environment $e) => $e->domain !== null && HostNormalizer::normalize($e->domain) === $normalizado);

        if ($existente === null) {
            return;
        }

        // **Si es de otro cliente, sin el entorno.** Devolverlo sería enseñar a quien da de
        // alta un sitio los datos de un cliente que no es el suyo, por el solo hecho de
        // escribir su dominio. Basta con saber que está ocupado: quién lo tiene se mira en
        // el panel.
        $cliente = $this->route('client');
        $clienteId = $cliente instanceof Client ? $cliente->id : $this->entorno()?->client_id;

        if ($existente->client_id !== $clienteId) {
            $this->conflicto('Ese dominio ya lo usa un entorno de otro cliente. Se revisa desde el panel.');
        }

        $this->conflicto(
            'Ese dominio ya lo usa otro entorno.',
            EnvironmentResource::make(Environment::find($existente->id))->resolve($this)
        );
    }

    public function rules(): array
    {
        $entorno = $this->entorno();
        $editando = $entorno !== null;
        $prefijo = $editando ? ['sometimes'] : [];

        return [
            'name' => [...$prefijo, 'required', 'string', 'max:255'],
            'domain' => [...$prefijo, ...Environment::reglasDelDominio($this->envEfectivo(), $entorno?->id)],
            'env' => [...$prefijo, ...explode('|', Environment::reglaDeEnv())],
            // **Qué es obligatorio lo dice el formulario del panel** (`CreateFromClient` y
            // `Edit`), no la API: la plataforma es opcional y la versión obligatoria, igual
            // que allí.
            'type_id' => [...$prefijo, 'nullable', 'integer', Rule::exists('types', 'id')->whereNull('deleted_at')],
            'license_token_id' => [...$prefijo, 'nullable', 'integer', Rule::exists('license_tokens', 'id')->whereNull('deleted_at')],
            'description' => ['nullable', 'string', 'max:10000'],
            'key' => ['nullable', 'string', 'max:255'],
            'version' => [...$prefijo, 'required', 'string', 'max:50'],
            'login_url' => ['nullable', 'url:http,https', 'max:500'],
        ];
    }

    public function messages(): array
    {
        return [
            'name.required' => 'El nombre del entorno es obligatorio.',
            'domain.required' => 'El dominio es obligatorio: la URL completa, con https://.',
            'env.required' => 'Falta el uso del entorno (env). Valores válidos: ' . implode(', ', Environment::entornos()) . '.',
            'env.in' => 'El uso (env) no es válido. Valores válidos: ' . implode(', ', Environment::entornos()) . '.',
            'type_id.required' => 'Falta la plataforma (type_id). Las válidas salen en GET /types.',
            'type_id.exists' => 'Esa plataforma (type_id) no existe. Las válidas salen en GET /types.',
            'license_token_id.exists' => 'Esa licencia no existe.',
            'login_url.url' => 'La URL de acceso tiene que ser una dirección completa, con http:// o https://.',
            'version.max' => 'La versión no puede pasar de 50 caracteres.',
        ];
    }
}
