<?php

namespace App\Livewire\Supports;

use App\Models\Environments\Environment;
use App\Models\Monitoring\Log as MonitoringLog;
use App\Models\Support\EnvironmentSupport;
use App\Models\Support\Support;
use Illuminate\Support\Facades\Auth;
use LivewireUI\Modal\ModalComponent;

/**
 * Borrar un tipo de soporte del catálogo.
 *
 * **Lo que había: nada.** El catálogo de `/supports` tenía listado, alta y edición, y
 * ninguna forma de quitar un tipo. El permiso `admin.supports.destroy` existía **sin
 * gatear nada** —se borró el 2026-09-08 con los otros 26 huérfanos (MGR-101)— y vuelve
 * aquí **con su pantalla**, que es la regla que se puso ese mismo día: un permiso o tiene
 * su pantalla en el mismo cambio, o se declara como intención.
 *
 * Es el mismo hueco que tenían los tipos de entorno (MGR-062): un catálogo al que se puede
 * añadir y del que no se puede quitar. La octava aparición del patrón en este release.
 *
 * ## No se borra si algún contrato lo usa
 *
 * Un tipo de soporte es lo que da de sí un contrato —«Soporte Premium», «Mantenimiento»—, y
 * los contratos de los entornos lo apuntan por `environment_support.support_id`. `Support`
 * usa `SoftDeletes`, así que borrar un tipo en uso **no da ningún error**:
 *
 * 1. La fila de `environment_support` se queda apuntando a un tipo borrado en blando.
 * 2. `$contrato->support` devuelve `null`, porque el borrado lógico lo excluye.
 * 3. La pantalla de soportes del entorno pinta «sin tipo», sin decir por qué.
 * 4. Y los avisos de caducidad siguen contando ese contrato, con el hueco en el nombre.
 *
 * O sea: deja contratos que no saben qué son. Por eso se **bloquea** en lugar de avisar,
 * igual que en los tipos de entorno y en el borrado de una versión de contenido.
 *
 * > **Ojo con el `cascadeOnDelete`.** `environment_support.support_id` está declarada con
 * > borrado en cascada, así que un `forceDelete()` —o un `DELETE` a mano en la base— se
 * > llevaría **todos los contratos** de ese tipo, incluido su histórico de facturación. El
 * > borrado de aquí es siempre lógico, y esa es la otra razón para no dejar pasar un tipo
 * > en uso: el día que alguien vacíe la papelera, el bloqueo es lo único que habrá evitado
 * > que se lleve los contratos por delante.
 */
class BorrarSoporteModal extends ModalComponent
{
    public Support $support;

    public function mount(int $supportId): void
    {
        // El permiso de la ruta no se reaplica en /livewire/update. Ver MGR-005.
        $this->authorize('admin.supports.destroy');

        // Por id y con `findOrFail`: el id llega en el snapshot de Livewire, así que la
        // consulta es el único sitio donde se puede acotar (MGR-006).
        $this->support = Support::findOrFail($supportId);
    }

    public static function modalMaxWidth(): string
    {
        return '2xl';
    }

    /**
     * A qué afectaría.
     *
     * **Se consulta en vivo y no con un `withCount` del `mount()`.** Livewire rehidrata el
     * modelo desde su id en cada petición y **los atributos agregados no sobreviven al
     * snapshot**: volverían como `null` —o sea 0— y el bloqueo se saltaría solo al
     * confirmar. Es el fallo que ya se cazó en `TypeDeleteModal`, y no hace falta
     * volver a cazarlo.
     *
     * **Los contratos se cuentan todos, vigentes o no.** Un contrato caducado sigue siendo
     * el histórico de lo que se le vendió a un cliente: si su tipo desaparece, ese
     * histórico se queda sin nombre.
     *
     * @return array{contratos:int, entornos:int, vigentes:int, clientes:int}
     */
    public function impacto(): array
    {
        // **Con el modelo y no con el constructor de consultas crudo.** El barrido de
        // MGR-005 (`LivewireAuthorizationTest`) cuenta una llamada a la fachada de base de
        // datos como escritura, porque desde ahí se puede escribir y no puede distinguir un
        // recuento de una actualización sin parsear. Ser conservador ahí es lo correcto —un
        // falso positivo cuesta un rato, un falso negativo cuesta un agujero—, así que se
        // usa `EnvironmentSupport`, que además es lo que toca: el contrato tiene modelo
        // propio desde MGR-052.
        //
        // Y ojo: el barrido busca la cadena en **todo** el cuerpo, comentarios incluidos.
        // Nombrar aquí la llamada prohibida, aunque sea para explicar por qué no se usa,
        // hace fallar el test. Es el precio de que la comprobación sea tonta a propósito.
        $contratos = fn () => EnvironmentSupport::where('support_id', $this->support->id);

        $entornos = $contratos()->pluck('environment_id')->unique();

        return [
            'contratos' => $contratos()->count(),
            'entornos' => $entornos->count(),
            // Los que aún cubren algo hoy: son los que se notarían de inmediato.
            'vigentes' => $contratos()->whereDate('end_at', '>=', now()->format('Y-m-d'))->count(),
            // Dos consultas en vez de un join, y se lee: cuántos clientes distintos hay
            // detrás de esos entornos.
            'clientes' => Environment::whereIn('id', $entornos)->distinct()->count('client_id'),
        ];
    }

    /** ¿Lo usa algún contrato? Entonces no se borra. */
    public function estaEnUso(): bool
    {
        return $this->impacto()['contratos'] > 0;
    }

    public function borrar(): void
    {
        $this->authorize('admin.supports.destroy');

        // Se vuelve a comprobar aquí: esconder el botón no es protegerlo (MGR-005), y
        // entre abrir la modal y confirmar alguien puede haber dado de alta un contrato
        // con este tipo.
        //
        // **Y la modal no se cierra al bloquear**, aunque sea lo que pide el cuerpo.
        // Cerrándola, el aviso iría por `session()->flash()` y la pantalla de detrás no se
        // vuelve a pintar, así que el mensaje no llega a ninguna parte: se vería cerrarse
        // la modal y el tipo seguir ahí, sin explicación. Dejándola abierta, `render()`
        // recalcula y la propia modal pasa a la variante de bloqueo.
        if ($this->estaEnUso()) {
            return;
        }

        $nombre = $this->support->name;
        $shortname = $this->support->shortname;
        $id = $this->support->id;

        // Lógico: `Support` usa `SoftDeletes`, así que un tipo borrado por error se puede
        // recuperar. Y **nunca `forceDelete()`**: la clave ajena de los contratos está en
        // cascada.
        $this->support->delete();

        MonitoringLog::db(
            'warning',
            '16036',
            'Tipo de soporte borrado del catálogo: ' . $nombre . ' (' . $shortname . ')'
            . ' — no lo usaba ningún contrato'
            . ' (por ' . (Auth::user()?->name ?? 'desconocido') . ')',
            'Support',
            (string) $id
        );

        session()->flash('success', '«' . $nombre . '» ya no está en el catálogo de soportes. '
            . 'El borrado es lógico: un administrador puede recuperarlo.');

        // Clave numérica: el paquete hace `[$event, $params] = $event` cuando el valor es
        // un array, así que `['evento' => []]` revienta con "Undefined array key 0".
        $this->closeModalWithEvents(['soportesCambiados']);
    }

    public function render()
    {
        return view('livewire.supports.borrar-soporte-modal', [
            'impacto' => $this->impacto(),
            'enUso' => $this->estaEnUso(),
        ]);
    }
}
