<?php

namespace App\Livewire\Products;

use App\Models\Monitoring\Log as MonitoringLog;
use App\Models\Products\Product;
use App\Support\ImpactoDeBorrarVersion;
use App\Support\VersionBorrable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use LivewireUI\Modal\ModalComponent;

/**
 * Borrar una versión de contenido, con el impacto delante (PRODSECU-198).
 *
 * **Una modal para los cinco tipos** —Funcionalidades, Tutoriales, Recursos, SCSS y JS—,
 * porque los cinco tienen la misma forma: versiones numeradas `YYYYMMDDXX` que contienen
 * elementos, y la API le sirve a cada Moodle la más alta menor o igual a la que pide su
 * plugin. Lo que cambia son el modelo y las palabras, y eso vive en `VersionBorrable`.
 *
 * Cinco modales habrían sido cinco copias del cálculo de impacto, que es MGR-024 otra vez.
 *
 * **Por qué no pregunta «¿seguro?».** Una versión de contenido **se está sirviendo a los
 * Moodles de los clientes**: borrarla cambia qué recibe un cliente en su siguiente
 * petición, sin que él haya tocado nada. Así que la modal **enseña a qué versión cae cada
 * entorno afectado**, y cuáles se quedan sin ninguna. Ese es el dato con el que se decide, y
 * no lo tiene nadie en la cabeza.
 *
 * **El caso silencioso.** Si se borra la única versión, el producto deja de servir ese
 * contenido y la API responde «sin contenido» —que `norma-cero-errores.md` clasifica como
 * **respuesta correcta**—. Ni el Dashboard ni el visor ni los avisos dicen nada: el sitio se
 * queda vacío y nadie se entera. La modal lo dice en rojo.
 *
 * **Pide escribir el número de versión.** Es el patrón del panel para lo que no se hace por
 * error, y aquí pesa más: **ninguno de los cinco modelos usa `SoftDeletes`**, así que lo que
 * se va, se va.
 */
class BorrarVersionModal extends ModalComponent
{
    /** La clave del tipo: `features`, `tutorials`, `resources`, `scss` o `js`. */
    public string $tipo;

    public Product $product;

    public int $versionId;


    public function mount(string $tipo, int $productId, int $versionId): void
    {
        // Revienta si el tipo no existe, a propósito: ver `VersionBorrable::de()`.
        $ficha = VersionBorrable::de($tipo);

        // El permiso de la ruta no se reaplica en /livewire/update (MGR-005), y esto borra
        // contenido que se está sirviendo.
        $this->authorize($ficha['permisoVersion']);

        $this->tipo = $tipo;
        $this->product = Product::findOrFail($productId);

        // Acotado al producto en la consulta y no comparando después: si no es de este
        // producto, es un 404 (MGR-006).
        $this->versionId = $ficha['modelo']::where('product_id', $this->product->id)
            ->findOrFail($versionId)
            ->id;
    }

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

    /** @return array<string, mixed> */
    public function ficha(): array
    {
        return VersionBorrable::de($this->tipo);
    }

    public function version(): Model
    {
        return $this->ficha()['modelo']::findOrFail($this->versionId);
    }

    /**
     * Qué pasa si se borra.
     *
     * @return array<string, mixed>
     */
    public function impacto(): array
    {
        $ficha = $this->ficha();
        $version = $this->version();
        $elementos = $version->{$ficha['relacion']}();

        return ImpactoDeBorrarVersion::de(
            $this->product,
            $version,
            $ficha['modelo'],
            elementos: (clone $elementos)->count(),
            // Los tipos sin estado sirven todo lo que tienen: no hay borradores que
            // descontar.
            publicados: $ficha['conEstado']
                ? (clone $elementos)->where('status', 'published')->count()
                : (clone $elementos)->count(),
        );
    }

    /**
     * ¿Se puede borrar esta versión?
     *
     * **La regla es del usuario y es la que hace esto seguro**: solo si no le está llegando
     * a ningún entorno y está vacía. Antes esta modal enseñaba el impacto y dejaba decidir;
     * con la regla, el impacto es la **condición**, y lo que la modal tiene que hacer es
     * decir **por qué no se puede** —porque las dos cosas se arreglan, cada una de una
     * forma— o confirmar cuando sí.
     *
     * Y por eso **ya no pide escribir el número de versión**: ese campo estaba para lo que
     * no se hace por error, y ahora no hay nada que perder. Una versión vacía que nadie
     * recibe no le cambia el servicio a ningún cliente.
     */
    public function puedeBorrar(): bool
    {
        return $this->impacto()['sePuedeBorrar'];
    }

    public function borrar(): void
    {
        $ficha = $this->ficha();

        $this->authorize($ficha['permisoVersion']);

        // **Se vuelve a comprobar aquí, y no es redundante**: entre abrir la modal y pulsar
        // pueden pasar minutos, y en medio un Moodle puede haber sincronizado y empezar a
        // recibir esta versión. Esconder el botón no es protegerlo (MGR-005).
        if (! $this->puedeBorrar()) {
            $motivos = $this->impacto()['motivos'];

            session()->flash('error',
                'No se puede borrar la versión: ' . implode(' y ', $motivos) . '.');

            $this->closeModal();

            return;
        }

        $version = $this->version();
        $numero = (string) $version->version;
        $impacto = $this->impacto();

        // **El rastro se escribe antes de borrar**, porque después no se puede reconstruir:
        // sin `SoftDeletes` no queda fila de la que sacar cuántos elementos tenía ni a quién
        // le llegaba.
        MonitoringLog::db(
            'warning',
            '16034',
            'Versión de ' . $ficha['nombre'] . ' borrada: ' . $numero . ' de ' . $this->product->name
            // Estaba vacía y no la recibía nadie —es la condición para poder borrarla—, así
            // que el rastro dice eso: sirve para saber que no se cortó ningún servicio.
            . ' (estaba vacía y ningún entorno la recibía)'
            . ' (por ' . (Auth::user()?->name ?? 'desconocido') . ')',
            class_basename($ficha['modelo']),
            (string) $version->id
        );

        // **Solo la versión.** No hay elementos que borrar —es la condición— y por eso no
        // hace falta transacción: es una sola fila. Si algún día se relajara la regla,
        // habría que volver a envolverlo.
        $version->delete();

        session()->flash('success', 'Versión ' . $numero . ' borrada. No la recibía ningún entorno.');

        // **`skipRender` no es una optimización, es obligatorio.** Después de borrar,
        // Livewire vuelve a renderizar el componente, y `render()` busca el modelo con
        // `findOrFail`: sin esto, borrar lanza un `ModelNotFoundException` justo después de
        // haber borrado bien. La modal se está cerrando, así que no hay nada que pintar.
        $this->skipRender();
        $this->closeModal();

        // Al listado: la pantalla de la versión que se acaba de borrar ya no existe.
        $this->redirectRoute($ficha['rutaListado'], $this->product, navigate: true);
    }

    public function render()
    {
        return view('livewire.products.borrar-version-modal', [
            'ficha' => $this->ficha(),
            'version' => $this->version(),
            'impacto' => $this->impacto(),
        ]);
    }
}
