<?php

namespace App\Livewire\Environments;

use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Environments\Type;
use App\Models\Monitoring\Log as MonitoringLog;
use App\Rules\UniqueEnvironmentDomain;
use App\Support\SugerenciasDeClientes;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class Edit extends Component
{
    public Environment $environment;
    public string $name = '';
    public string $domain = '';

    /** Solo si no es el de siempre. Ver `Environment::enlaceDeLogin()`. */
    public string $loginUrl = '';
    public ?string $description = null;
    public string $version = '';
    public string $env = 'local';
    public ?int $client_id = null;
    public ?int $type_id = null;
    public bool $active = true;

    /**
     * Lo que se está escribiendo para buscar el cliente.
     *
     * **El desplegable de clientes era la tabla entera**, y encima consultada desde la
     * plantilla, así que se leía y se serializaba en cada render. Con los clientes de hoy
     * no se nota; no escala, y el resto de pantallas del panel que eligen cliente —las de
     * licencias— ya buscaban contra el servidor. Esta se había quedado sola.
     */
    public string $clientSearch = '';

    public function mount(Environment $environment)
    {
        $this->environment = $environment;
        $this->name = $environment->name ?? '';
        $this->domain = $environment->domain ?? '';
        $this->loginUrl = $environment->login_url ?? '';
        $this->description = $environment->description;
        $this->version = $environment->version ?? '';
        $this->env = $environment->env ?? 'local';
        $this->client_id = $environment->client_id;
        $this->type_id = $environment->type_id;
        $this->active = $environment->active ?? true;
    }

    protected function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'domain' => ['required', 'string', 'max:255', new UniqueEnvironmentDomain($this->env, $this->environment->id)],
            // **`url` con esquemas acotados y no `url` a secas.** La validación por defecto
            // de Laravel acepta cualquier esquema, y esto se pinta como un enlace en el
            // panel: con `javascript:` dentro, cualquiera con permiso de edición deja un
            // clic que ejecuta código en la sesión de quien lo pulse.
            'loginUrl' => 'nullable|url:http,https|max:500',
            'description' => 'nullable|string',
            'version' => 'required|string|max:50',
            // La lista vive en el modelo: de su valor depende el índice único del
            // dominio, que exime a los locales con un `CASE WHEN env = 'local'`
            // (MGR-010).
            'env' => Environment::reglaDeEnv(),
            'client_id' => 'nullable|exists:clients,id',
            'type_id' => 'nullable|exists:types,id',
            // `boolean` y no `accepted`: con `accepted` desmarcar la casilla daba «The
            // active field must be accepted» y el entorno se quedaba activo, o sea que
            // **no se podía desactivar ninguno**. `accepted` es para un «acepto los
            // términos», donde false es inválido; aquí false es la mitad del sentido del
            // campo.
            'active' => 'boolean',
        ];
    }

    public function update()
    {
        $this->authorize('admin.environments.edit');

        $validated = $this->validate();
        $validated['active'] = $this->active;

        // La propiedad es `loginUrl` y la columna `login_url`: sin esta línea el
        // `update()` recibiría una clave que no existe y el campo se guardaría vacío
        // sin dar ningún error. Vacío se guarda como null: '' y null significan lo
        // mismo aquí —«el de siempre»— y tener los dos son dos formas de decirlo.
        $validated['login_url'] = trim($this->loginUrl) ?: null;
        unset($validated['loginUrl']);

        // El dominio es LA LLAVE con la que la API encuentra el entorno: el plugin manda
        // su `host` y el middleware lo compara con esta columna. Cambiarlo aquí corta el
        // servicio de ese sitio en la siguiente petición —`Environment not found for this
        // host`— hasta que su Moodle responda en el dominio nuevo.
        //
        // Antes esto no dejaba **ningún rastro**. Si un cliente decía «llevamos dos días
        // sin contenido», no había forma de saber que alguien le había tocado el dominio
        // el martes.
        $anterior = $this->environment->getOriginal('domain');

        $this->environment->update($validated);

        $nuevo = $this->environment->fresh()->domain;

        if ($anterior !== $nuevo) {
            MonitoringLog::db(
                'warning',
                '16020',
                'Dominio del entorno ' . $this->environment->name . ' cambiado de '
                . ($anterior ?: 'vacío') . ' a ' . ($nuevo ?: 'vacío')
                . ' (por ' . (Auth::user()?->name ?? 'desconocido') . ')',
                'Environment',
                (string) $this->environment->id
            );

            // Se avisa en vez de pedir confirmación: quien edita un dominio suele saber
            // lo que hace, pero **no siempre sabe que el corte es inmediato** ni que el
            // sitio deja de aparecer como sincronizado hasta que el cambio llegue al
            // Moodle.
            session()->flash('warning', 'Dominio cambiado. Este sitio dejará de recibir '
                . 'licencias y contenido —la API le responderá «Environment not found for '
                . 'this host»— hasta que su Moodle llame desde ' . $nuevo . '. Si el cambio '
                . 'aún no está hecho en el Moodle, vuelve a poner el dominio anterior: '
                . ($anterior ?: '—'));

            return $this->redirectRoute('environments.index', navigate: true);
        }

        session()->flash('success', 'Entorno actualizado correctamente.');

        return $this->redirect(route('environments.index'), navigate: true);
    }

    /**
     * Clientes que coinciden con lo escrito.
     *
     * La consulta vive en `SugerenciasDeClientes` y no aquí: el mismo selector está en el
     * visor de peticiones, y lo que no puede divergir es por qué campos busca y cuántas
     * devuelve.
     *
     * @return array{filas: \Illuminate\Support\Collection, total: int}
     */
    public function sugerenciasDeClientes(): array
    {
        return SugerenciasDeClientes::para($this->clientSearch);
    }

    /**
     * El cliente elegido, para pintarlo como etiqueta quitable.
     *
     * Aquí y no en la plantilla porque es **una consulta por clave primaria**, y que la
     * plantilla no tenga ninguna es justo lo que se está arreglando.
     */
    public function clienteElegido(): ?Client
    {
        return $this->client_id ? Client::find($this->client_id) : null;
    }

    /**
     * Elegir un cliente, o quitarlo.
     *
     * **`null` es un valor válido**: un entorno puede no tener cliente —el desplegable
     * tenía su «Sin cliente»— y quitarlo tiene que seguir siendo posible.
     */
    public function elegirCliente(?int $clienteId): void
    {
        $this->client_id = $clienteId;
        $this->clientSearch = '';
    }

    public function render()
    {
        return view('livewire.environments.edit', [
            'types' => Type::orderBy('name')->get(),
            'envs' => Environment::envs(),
        ])->layout('layouts.clean');
    }
}

