<?php

namespace App\Livewire\Monitoring;

use App\Models\Products\LicenseToken;
use App\Models\Products\LicenseTokenProduct;
use Livewire\Attributes\Url;
use Livewire\Component;

/**
 * Lo próximo que va a cortar servicio.
 *
 * **Qué entra y qué no.** Solo lo que, al caducar, **deja a un cliente sin contenido**:
 *
 * - **Licencias** (`license_tokens.end_at`): al pasar la fecha el middleware responde
 *   «Token expirado» y ese cliente pierde todo.
 * - **Productos dentro de una licencia** (`license_token_product.end_at`): al pasar la
 *   fecha ese plugin concreto deja de funcionar en todos los entornos de la licencia, con
 *   el mensaje «Licence does not include this plugin», que apunta a otro sitio.
 *
 * **Los soportes no entran**: un soporte es un acuerdo comercial y caducar no corta nada
 * —eso lo decide la licencia—. Tienen su propio aviso y su pantalla.
 *
 * **Los tokens de servicios web tampoco**, y no por decisión: el Manager **no guarda su
 * fecha de caducidad**. Moodle permite crear tokens con `validuntil`, pero el plugin no
 * nos lo envía en el `sync`, así que aquí no se puede saber. Por eso la pantalla de
 * conexión recomienda crearlos **sin caducidad**: si caducan, esto deja de funcionar el
 * día que caduquen y sin avisar.
 *
 * **Se excluyen los clientes de baja**: sus licencias ya están desactivadas y sus Moodles
 * ya reciben «Cliente inactivo», así que no hay nada que se vaya a cortar.
 */
class Expirations extends Component
{
    /** Cuántas filas se muestran. Lo pedido fueron 15. */
    #[Url(as: 'n', except: 15)]
    public int $cuantas = 15;

    /**
     * todo | licencias | productos.
     *
     * Las dos fuentes cortan cosas distintas: una licencia deja al cliente sin nada, un
     * producto deja de servir un plugin concreto. Poder mirar solo una de las dos es la
     * diferencia entre «qué renuevo» y «qué producto se está quedando desalineado».
     */
    #[Url(as: 'tipo', except: 'todo')]
    public string $tipo = 'todo';

    /** Solo lo que caduca en 30 días o menos: lo que hay que hacer este mes. */
    #[Url(as: 'pronto', except: false)]
    public bool $soloPronto = false;

    /** Filtro por cliente, para preparar una conversación concreta. */
    #[Url(as: 'cliente', except: '')]
    public string $cliente = '';

    public function limpiarFiltros(): void
    {
        $this->reset(['tipo', 'soloPronto', 'cliente']);
    }

    public function hayFiltros(): bool
    {
        return $this->tipo !== 'todo' || $this->soloPronto || $this->cliente !== '';
    }

    public function mount(): void
    {
        $this->authorize('admin.api-logs.index');
    }

    /**
     * Las dos fuentes, unificadas y ordenadas por lo que caduca antes.
     *
     * Se juntan **en PHP y no con un `UNION`** a propósito: son dos consultas pequeñas
     * —acotadas a lo activo y a lo futuro— y un `UNION` entre tablas con columnas
     * distintas obliga a inventar columnas de relleno y a repetir la lógica de fechas en
     * SQL, que es donde luego se cuela una diferencia entre MariaDB y sqlite.
     *
     * @return array<int, array<string, mixed>>
     */
    private function filas(): array
    {
        $deAlta = fn ($consulta) => $consulta->whereHas(
            'client',
            fn ($q) => $q->where('actived', true)
        );

        $licencias = $deAlta(
            LicenseToken::with(['client', 'environments'])
                ->where('active', true)
                ->whereNotNull('end_at')
                ->where('end_at', '>=', now())
        )
            ->orderBy('end_at')
            ->limit($this->cuantas)
            ->get()
            ->map(fn (LicenseToken $licencia) => [
                'tipo' => 'Licencia',
                'que' => $licencia->name ?? 'Licencia sin nombre',
                'detalle' => null,
                'cliente' => $licencia->client?->name,
                'entornos' => $licencia->environments->count(),
                'fecha' => $licencia->end_at,
                'url' => route('license_tokens.show', $licencia->id),
                // Una licencia caducada corta TODO lo del cliente.
                'alcance' => 'Todo el contenido de ese cliente',
            ]);

        $productos = LicenseTokenProduct::with(['token.client', 'token.environments', 'product'])
            ->whereNotNull('end_at')
            ->where('end_at', '>=', now())
            ->where('status', 'active')
            ->whereHas('token', fn ($q) => $q->where('active', true)
                ->whereHas('client', fn ($c) => $c->where('actived', true)))
            ->orderBy('end_at')
            ->limit($this->cuantas)
            ->get()
            ->map(fn (LicenseTokenProduct $fila) => [
                'tipo' => 'Producto',
                'que' => $fila->product?->name ?? 'Producto ' . $fila->product_id,
                'detalle' => 'en ' . ($fila->token?->name ?? 'una licencia'),
                'cliente' => $fila->token?->client?->name,
                'entornos' => $fila->token?->environments->count() ?? 0,
                'fecha' => $fila->end_at,
                'url' => $fila->token ? route('license_tokens.show', $fila->token->id) : null,
                'alcance' => 'Solo ese plugin',
            ]);

        return $licencias->concat($productos)
            ->when($this->tipo === 'licencias', fn ($c) => $c->where('tipo', 'Licencia'))
            ->when($this->tipo === 'productos', fn ($c) => $c->where('tipo', 'Producto'))
            ->when($this->cliente !== '', fn ($c) => $c->where('cliente', $this->cliente))
            // Rango **cerrado**: de hoy a 30 días. Las ya caducadas no entran en la
            // consulta, así que aquí no hay riesgo de contarlas dos veces (MGR-025).
            ->when($this->soloPronto, fn ($c) => $c->filter(
                fn ($fila) => $fila['fecha'] !== null
                    && $fila['fecha']->copy()->startOfDay()->lte(now()->addDays(30)->startOfDay())
            ))
            ->sortBy('fecha')
            ->take($this->cuantas)
            ->values()
            ->all();
    }

    public function render()
    {
        $filas = $this->filas();

        return view('livewire.monitoring.expirations', [
            'filas' => $filas,
            'hayFiltros' => $this->hayFiltros(),
            // Los clientes que aparecen de verdad: ofrecer un filtro que no devuelve
            // nada es peor que no ofrecerlo.
            'clientes' => collect($filas)->pluck('cliente')->filter()->unique()->sort()->values()->all(),
            // Cuántas hay en total sin el recorte, para no leer «15» como «15 en total».
            // Es el mismo problema que el truncado del Dashboard (MGR-030).
            'total' => $this->cuantasHay(),
        ])->layout('layouts.app');
    }

    private function cuantasHay(): int
    {
        $licencias = LicenseToken::where('active', true)
            ->whereNotNull('end_at')
            ->where('end_at', '>=', now())
            ->whereHas('client', fn ($q) => $q->where('actived', true))
            ->count();

        $productos = LicenseTokenProduct::whereNotNull('end_at')
            ->where('end_at', '>=', now())
            ->where('status', 'active')
            ->whereHas('token', fn ($q) => $q->where('active', true)
                ->whereHas('client', fn ($c) => $c->where('actived', true)))
            ->count();

        return $licencias + $productos;
    }
}
