<?php

namespace App\Livewire\ApiLogs;

use App\Models\ApiRequestLog;
use App\Services\Api\Severidad;
use App\Services\System\ApiLogPurger;
use App\Support\SugerenciasDeEntornos;
use Illuminate\Support\Carbon;
use LivewireUI\Modal\ModalComponent;

/**
 * La limpieza del histórico de peticiones, en una modal.
 *
 * **Por qué se sacó de la pantalla.** Era una tira desplegable al final del visor con
 * cuatro bloques dentro: cuatro atajos por antigüedad, un rango de fechas, un buscador de
 * sitio y un botón que borraba «lo que se está viendo». Cerrada no decía nada y abierta
 * ocupaba más que el listado — en una pantalla que existe para *mirar* peticiones, no para
 * borrarlas. Y no es una acción que se haga a menudo: es la que menos, y la única sin
 * vuelta atrás.
 *
 * **Y la modal no es solo un sitio donde meterlo.** Aquí el criterio se elige primero y el
 * borrado es el último paso, con el número delante: se pulsa «Más antiguos de 3 meses», la
 * modal dice **cuántas filas son, de qué fechas y cuántos errores sin revisar se llevan**, y
 * solo entonces aparece el botón rojo. En la tira, cada botón abría directamente el
 * «¿seguro?» genérico, así que el criterio y la decisión iban en el mismo clic.
 *
 * ## Quién borra, y por qué no es esta modal
 *
 * Los criterios propios —antigüedad y rango— se cuentan aquí con
 * `ApiLogPurger::consultaDe()`, que es **la misma consulta que ejecuta el borrado**: en algo
 * irreversible, el número del aviso no puede salir de una consulta distinta de la que
 * borra.
 *
 * «Lo que se está viendo» no se puede contar aquí: son los filtros del visor y solo el
 * visor los conoce. Así que llega ya contado como argumento, y **el borrado lo hace el
 * visor** —esta modal solo le manda la orden—. Duplicar aquí `baseQuery()` con sus veinte
 * filtros sería la forma segura de que algún día el aviso y el borrado dejaran de coincidir.
 *
 * El caso «un sitio concreto» tiene su propia modal desde antes
 * (`BorrarLogsDeSitioModal`), porque se lleva el histórico completo de un entorno incluido
 * el de hoy y hace falta contar bastante más. Desde aquí se abre esa.
 */
class LimpiezaDelHistoricoModal extends ModalComponent
{
    /** Qué se va a borrar: `''` (nada elegido aún), `antiguedad`, `rango` o `filtro`. */
    public string $criterio = '';

    public ?int $dias = null;

    public ?string $desde = null;

    public ?string $hasta = null;

    /** El buscador de sitio: entornos dados de alta y hosts que no son de nadie. */
    public string $busqueda = '';

    /**
     * Lo que coincide con los filtros del visor, **contado por el visor**.
     *
     * Llega como argumento porque aquí no se puede recalcular sin duplicar los filtros. Es
     * una foto del momento en que se abrió la modal, y eso está bien: es exactamente lo que
     * la persona tenía delante al pulsar.
     */
    public int $filtroTotal = 0;

    /** Cuántos filtros hay puestos y qué rango de fechas, para poder decirlo. */
    public int $filtrosPuestos = 0;

    public string $rangoTexto = '';

    public function mount(int $filtroTotal = 0, int $filtrosPuestos = 0, string $rangoTexto = ''): void
    {
        // El permiso de la ruta no se reaplica en /livewire/update (MGR-005), y esto borra
        // un rastro de auditoría.
        $this->authorize('admin.api-logs.destroy');

        $this->filtroTotal = $filtroTotal;
        $this->filtrosPuestos = $filtrosPuestos;
        $this->rangoTexto = $rangoTexto;
    }

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

    /**
     * La fecha de la petición más vieja que queda.
     *
     * **Una sola consulta para los cuatro atajos**: de ella se deduce si hay algo anterior
     * a 30, 90, 180 o 365 días. Contar cuatro veces sobre la tabla que más crece del
     * sistema para pintar cuatro botones sería justo lo que no hay que hacer, y `min()` usa
     * el índice de `started_at`.
     *
     * Estaba en el `render()` del visor, así que se pagaba **en cada repintado del
     * listado** para una tira que casi nunca se abría. Aquí se paga cuando se abre.
     */
    public function masAntigua(): ?Carbon
    {
        $fecha = ApiRequestLog::min('started_at');

        return $fecha !== null ? Carbon::parse($fecha) : null;
    }

    /** La orden de limpieza tal y como la entienden el purgador y el visor. */
    private function orden(): array
    {
        return [
            'criterio' => $this->criterio,
            'dias' => $this->dias,
            'desde' => $this->desde,
            'hasta' => $this->hasta,
        ];
    }

    /**
     * Qué se va a borrar con el criterio elegido.
     *
     * **El desglose que puede cambiar la decisión, no un recuento de adorno.** Las fechas
     * dicen si el criterio se está llevando más de lo que se pensaba, y los errores sin
     * revisar son el aviso de verdad: borrarlos los saca de las alertas sin que nadie los
     * haya mirado.
     *
     * @return array{total: int, desde: ?string, hasta: ?string, erroresSinRevisar: int, propia: bool}
     */
    public function impacto(): array
    {
        $consulta = ApiLogPurger::consultaDe($this->orden());

        // «Lo que se está viendo» no se cuenta aquí —los filtros son del visor—, así que se
        // enseña el número que el visor pasó al abrir y se dice que es del filtro.
        if ($consulta === null) {
            return [
                'total' => $this->criterio === 'filtro' ? $this->filtroTotal : 0,
                'desde' => null,
                'hasta' => null,
                'erroresSinRevisar' => 0,
                'propia' => false,
            ];
        }

        return [
            'total' => (int) $consulta->clone()->count(),
            'desde' => $consulta->clone()->min('started_at'),
            'hasta' => $consulta->clone()->max('started_at'),
            'erroresSinRevisar' => (int) $consulta->clone()
                ->where('severity', Severidad::ERROR)
                ->whereNull('reviewed_at')
                ->count(),
            'propia' => true,
        ];
    }

    /** Cómo se dice el criterio elegido, en la misma frase que el rastro del borrado. */
    public function comoSeDice(): string
    {
        return match ($this->criterio) {
            'antiguedad' => 'todo lo anterior a ' . $this->dias . ' días',
            'rango' => 'lo registrado entre el ' . $this->fecha($this->desde)
                . ' y el ' . $this->fecha($this->hasta),
            'filtro' => 'lo que coincide con los filtros del visor',
            default => '',
        };
    }

    private function fecha(?string $valor): string
    {
        return $valor !== null && $valor !== '' ? Carbon::parse($valor)->format('d/m/Y') : '—';
    }

    public function elegirAntiguedad(int $dias): void
    {
        $this->criterio = 'antiguedad';
        $this->dias = $dias;
        // Los criterios son excluyentes: manda el último elegido, y dejar el rango puesto
        // haría que el botón rojo dijera un número y borrara otro.
        $this->desde = null;
        $this->hasta = null;
        $this->resetErrorBag();
    }

    public function elegirRango(): void
    {
        $this->validate([
            'desde' => ['required', 'date'],
            'hasta' => ['required', 'date', 'after_or_equal:desde'],
        ], [], ['desde' => 'fecha desde', 'hasta' => 'fecha hasta']);

        $this->criterio = 'rango';
        $this->dias = null;
    }

    public function elegirFiltro(): void
    {
        $this->criterio = 'filtro';
        $this->dias = null;
        $this->desde = null;
        $this->hasta = null;
        $this->resetErrorBag();
    }

    /** Volver a la elección sin cerrar la modal. */
    public function olvidarCriterio(): void
    {
        $this->criterio = '';
        $this->dias = null;
        $this->desde = null;
        $this->hasta = null;
        $this->resetErrorBag();
    }

    /**
     * Qué se puede limpiar por sitio: **entornos y hosts que no son de nadie**.
     *
     * Los dos en el mismo buscador y no en dos cajas: se escribe un dominio y sale lo que
     * haya, esté dado de alta o no. Partirlo obligaría a saber de antemano en cuál de los
     * dos está, que es justo lo que se viene a averiguar.
     *
     * **Los hosts sin acotar por fechas**: aquí se borra todo el histórico del sitio, así
     * que ofrecer solo los del rango visible del visor esconderÍa precisamente los viejos,
     * que son los que se vienen a limpiar. Y sin acotar por cliente: en la limpieza no hay
     * cliente elegido.
     *
     * @return array{entornos: \Illuminate\Support\Collection, hosts: \Illuminate\Support\Collection, total: int}
     */
    public function sugerencias(): array
    {
        $entornos = SugerenciasDeEntornos::para($this->busqueda);

        $termino = trim($this->busqueda);

        $huerfanos = ApiRequestLog::query()
            ->whereNull('environment_id')
            ->whereNotNull('host')
            ->when($termino !== '', fn ($q) => $q->where('host', 'like', '%' . $termino . '%'))
            ->selectRaw('host, count(*) as peticiones, min(started_at) as desde')
            ->groupBy('host')
            // Por volumen: lo que se viene a limpiar es lo que más ocupa.
            ->orderByDesc('peticiones')
            ->limit(SugerenciasDeEntornos::CUANTAS)
            ->get();

        return [
            'entornos' => $entornos['filas'],
            'hosts' => $huerfanos,
            'total' => $entornos['total'],
        ];
    }

    /**
     * El histórico completo de un sitio: se pasa a su modal.
     *
     * **No se hace aquí** porque ese caso se lleva el rastro completo de un sitio, incluido
     * el de hoy, y lo que hay que contar antes —el desglose por severidad, desde cuándo, si
     * hay errores sin revisar y qué no se toca— es bastante más de lo que cabe en este
     * formulario.
     */
    public function abrirSitio(?int $environmentId = null, ?string $host = null): void
    {
        $this->authorize('admin.api-logs.destroy');

        if ($environmentId === null && trim((string) $host) === '') {
            return;
        }

        $this->dispatch('openModal',
            component: BorrarLogsDeSitioModal::class,
            arguments: ['environmentId' => $environmentId, 'host' => $host]
        );
    }

    /**
     * Manda la orden de borrar y se cierra.
     *
     * **El borrado lo hace el visor**, que es quien tiene los filtros y quien ya deja el
     * rastro con quién, cuántas filas y con qué criterio. Ver el docblock de la clase.
     */
    public function borrar(): void
    {
        $this->authorize('admin.api-logs.destroy');

        if ($this->criterio === '') {
            return;
        }

        // Se revalida antes de mandar: entre elegir el rango y pulsar el botón se pueden
        // haber cambiado las fechas.
        if ($this->criterio === 'rango') {
            $this->validate([
                'desde' => ['required', 'date'],
                'hasta' => ['required', 'date', 'after_or_equal:desde'],
            ], [], ['desde' => 'fecha desde', 'hasta' => 'fecha hasta']);
        }

        // Se despacha y luego se cierra, en vez de con `closeModalWithEvents()`: ese método
        // desestructura `[$evento, $params]` cuando el valor es un array, así que pasar un
        // parámetro que **es** un array por esa vía es una trampa para quien lo lea después.
        $this->dispatch('apiLogsLimpiar', orden: $this->orden());
        $this->closeModal();
    }

    public function render()
    {
        return view('livewire.api-logs.limpieza-del-historico-modal', [
            'impacto' => $this->criterio !== '' ? $this->impacto() : null,
            'masAntigua' => $this->masAntigua(),
        ]);
    }
}
