<?php

namespace App\Livewire\ApiLogs;

use App\Models\ApiRequestLog;
use App\Models\Environments\Environment;
use App\Services\Api\Severidad;
use App\Services\System\ApiLogPurger;
use Illuminate\Database\Eloquent\Builder;
use LivewireUI\Modal\ModalComponent;

/**
 * Borrar **todo el histórico de peticiones** de un sitio: un entorno, o un host que no es
 * de nadie.
 *
 * **Por qué esto necesita su propia modal y no el «¿seguro?» de siempre.** Las otras
 * limpiezas del visor borran por antigüedad o por rango: se llevan lo viejo de todo el
 * parque, que es una decisión de espacio. Esto se lleva **el rastro completo de un sitio
 * concreto, incluido el de hoy**, y eso no es espacio: es la respuesta a «¿qué le pasó a
 * este cliente?», que es la pregunta para la que existe esta tabla.
 *
 * Es la misma decisión que la del apagado de un entorno y la del borrado de una versión de
 * contenido: **lo que hace que alguien lea un aviso es que le diga qué va a pasar y a
 * cuánto**, no un «¿estás seguro?». Así que la modal cuenta cuántas filas y de qué
 * severidades, desde cuándo, si hay errores que nadie ha revisado, y qué **no** se toca.
 *
 * ## Los dos casos, y por qué son dos consultas
 *
 * - **Un entorno**: `environment_id = X`.
 * - **Un host sin dar de alta**: `environment_id IS NULL AND host = X`.
 *
 * El `whereNull` no es un adorno. Un mismo dominio puede tener filas de las dos clases —el
 * alta se terminó a media mañana, así que las de antes quedaron sin entorno— y borrar «el
 * host» llevándose las que sí tienen entorno sería borrar más de lo que dice el aviso.
 *
 * **Y el caso del host es la mitad del sentido de esta pantalla.** Un host que no resuelve
 * a ningún entorno acumula peticiones que no son de nadie: sin ficha a la que ir, sin
 * responsable que las mire y sin forma de llegar a ellas hasta que hubo buscador de host.
 * Son las que más ensucian y las últimas que alguien limpia.
 */
class BorrarLogsDeSitioModal extends ModalComponent
{
    public ?Environment $environment = null;

    public ?string $host = null;

    public function mount(?int $environmentId = null, ?string $host = null): 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');

        if ($environmentId !== null) {
            // 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->environment = Environment::with('client')->findOrFail($environmentId);

            return;
        }

        $host = trim((string) $host);

        // Sin ninguna de las dos cosas no hay nada que borrar, y seguir adelante dejaría
        // una consulta sin `where` — o sea, la tabla entera.
        abort_if($host === '', 404, 'No se ha indicado de qué sitio borrar el histórico.');

        $this->host = $host;
    }

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

    /** ¿Es un host que no está dado de alta como entorno? */
    public function esHostHuerfano(): bool
    {
        return $this->environment === null;
    }

    /** Cómo se llama esto en la pantalla. */
    public function comoSeLlama(): string
    {
        return $this->environment?->name ?? (string) $this->host;
    }

    /**
     * Las filas que se van a borrar. **La única definición del alcance**, para que el
     * número del aviso y el del borrado no puedan discrepar.
     */
    private function filas(): Builder
    {
        if ($this->environment !== null) {
            return ApiRequestLog::where('environment_id', $this->environment->id);
        }

        return ApiRequestLog::whereNull('environment_id')->where('host', $this->host);
    }

    /**
     * Qué se va a borrar, con su desglose.
     *
     * **Todo el histórico del sitio, sin rango de fechas.** Es lo que se pide desde la
     * limpieza, y los otros dos criterios de ese bloque tampoco miran los filtros de
     * arriba. La modal dice las fechas para que el alcance esté a la vista.
     *
     * @return array<string, mixed>
     */
    public function impacto(): array
    {
        $porSeveridad = $this->filas()
            ->selectRaw('severity, count(*) as c')
            ->groupBy('severity')
            ->pluck('c', 'severity');

        return [
            'total' => (int) $porSeveridad->sum(),
            'ok' => (int) ($porSeveridad[Severidad::OK] ?? 0),
            'error' => (int) ($porSeveridad[Severidad::ERROR] ?? 0),
            'sinContenido' => (int) ($porSeveridad[Severidad::SIN_CONTENIDO] ?? 0),
            'negocio' => (int) ($porSeveridad[Severidad::NEGOCIO] ?? 0),
            // Los que nadie ha mirado: borrarlos los saca de las alertas sin resolverlos.
            'erroresSinRevisar' => (int) $this->filas()
                ->where('severity', Severidad::ERROR)
                ->whereNull('reviewed_at')
                ->count(),
            'desde' => $this->filas()->min('started_at'),
            'hasta' => $this->filas()->max('started_at'),
        ];
    }

    public function borrar(ApiLogPurger $purgador): void
    {
        $this->authorize('admin.api-logs.destroy');

        $criterio = $this->environment !== null
            ? 'histórico completo del entorno ' . $this->environment->name
                . ' (' . $this->environment->domain . ')'
            // Entre comillas y diciendo que no tiene entorno: es un dato que viene de
            // fuera, y quien audite el borrado tiene que poder distinguir los dos casos.
            : 'histórico completo del host «' . $this->host . '» sin entorno reconocido';

        $borrados = $purgador->borrar(
            fn () => $this->filas(),
            $criterio,
            'la limpieza del visor'
        );

        session()->flash('warning', number_format($borrados, 0, ',', '.')
            . ' petición(es) de ' . $this->comoSeLlama() . ' borradas para siempre.'
            . ($this->environment !== null
                ? ' Su ficha aparecerá sin actividad hasta que su Moodle vuelva a llamar.'
                : ' Si ese Moodle sigue llamando, volverán a aparecer: lo que hay que arreglar es el alta de ese dominio.'));

        // 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(['apiLogsPurgados']);
    }

    public function render()
    {
        return view('livewire.api-logs.borrar-logs-de-sitio-modal', [
            'impacto' => $this->impacto(),
        ]);
    }
}
