<?php

namespace App\Livewire\Monitoring;

use App\Models\Monitoring\Log as MonitoringLog;
use App\Services\System\LimpiezaDeCortes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use LivewireUI\Modal\ModalComponent;

/**
 * Limpieza del registro de cortes, por rango de fechas y opcionalmente por IP.
 *
 * **Es la puerta que no existía.** De `api_blocks` solo se podía «revisar» un episodio y de
 * `api_rate_events` no se podía hacer nada: las dos solo crecían. Y crecen más rápido de lo
 * que parece, porque un episodio revisado que vuelve a pasar **abre fila nueva**.
 *
 * Va en su propia modal —y no con el `ConfirmModal` genérico— porque **lleva formulario**:
 * las fechas y la IP acotan lo que se borra, y el número de filas cambia con cada cambio.
 * Es el mismo patrón que la «Limpieza del histórico» del visor de peticiones.
 *
 * ## Las dos reglas que la modal tiene que dejar claras
 *
 * 1. **Un corte pendiente no se borra nunca.** Se cuentan y se dicen, para que quien
 *    confirma sepa que se quedan: borrar la fila no levantaría el corte —el contador vive en
 *    la caché del limitador— y solo se perdería de vista algo que sigue cortando.
 * 2. **No hay papelera.** Ninguna de las dos tablas lleva borrado lógico, así que lo que se
 *    va no vuelve. Queda el rastro en el registro de acciones del panel (código 16040).
 */
class LimpiezaDeCortesModal extends ModalComponent
{
    public string $desde = '';

    public string $hasta = '';

    /** Opcional: acota a una sola IP. */
    public string $ip = '';

    /**
     * Opcional: acota a un dominio.
     *
     * **Es como se busca a un cliente de verdad.** Nadie se acuerda de la IP de nadie, y el
     * host es lo que el plugin declara en cada petición: es el dato con el que llega una
     * incidencia —«el sitio de tal está raro»—.
     */
    public string $host = '';

    /**
     * Qué tabla se limpia: `episodios`, `ventanas` o `ambas`.
     *
     * **Elegir importa porque no son lo mismo.** Las ventanas del limitador
     * (`api_rate_events`) son telemetría de un minuto: vaciarlas no pierde nada que alguien
     * vaya a consultar. Los episodios (`api_blocks`) son el histórico de incidentes, y
     * borrarlos sí es perder algo. Antes solo se podía «episodios» o «las dos», nunca las
     * ventanas solas, que es justo lo más inofensivo y lo que más se va a querer.
     */
    public string $que = self::AMBAS;

    public const EPISODIOS = 'episodios';

    public const VENTANAS = 'ventanas';

    public const AMBAS = 'ambas';

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

        // **El defecto no borra nada de lo reciente**: de hace un año a hace treinta días.
        // Abrir la modal con un rango que incluya hoy invita a un clic que se lleva lo que
        // se está investigando ahora mismo.
        $this->desde = now()->subYear()->format('Y-m-d');
        $this->hasta = now()->subDays(30)->format('Y-m-d');
    }

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

    protected function rules(): array
    {
        return [
            'desde' => ['required', 'date'],
            'hasta' => ['required', 'date', 'after_or_equal:desde'],
            // Sin `ip` como regla de validación de formato: `api_blocks.ip` admite IPv4 e
            // IPv6, y lo que se escribe aquí se compara tal cual con lo guardado. Una IP mal
            // escrita no borra nada, que es el fallo inofensivo.
            'ip' => ['nullable', 'string', 'max:45'],
            // El host se compara tal cual con lo que declaró el plugin: escribirlo mal no
            // borra nada, que es el fallo inofensivo.
            'host' => ['nullable', 'string', 'max:255'],
            'que' => ['required', 'in:' . self::EPISODIOS . ',' . self::VENTANAS . ',' . self::AMBAS],
        ];
    }

    protected function messages(): array
    {
        return [
            'hasta.after_or_equal' => 'La fecha final no puede ser anterior a la inicial.',
        ];
    }

    /**
     * Qué se llevaría con los criterios puestos.
     *
     * **Se recalcula en cada cambio del formulario** y por eso está en un método y no en el
     * `mount()`: un impacto que no se actualiza al mover una fecha es peor que no darlo.
     *
     * @return array{episodios: int, pendientes: int, ventanas: int}
     */
    public function impacto(): array
    {
        [$desde, $hasta] = $this->rango();

        if ($desde === null) {
            return ['episodios' => 0, 'pendientes' => 0, 'ventanas' => 0];
        }

        return LimpiezaDeCortes::contarPorCriterios($this->criterios($desde, $hasta));
    }

    /**
     * Los criterios tal y como están en el formulario.
     *
     * @return array<string, mixed>
     */
    private function criterios(\DateTimeInterface $desde, \DateTimeInterface $hasta): array
    {
        return [
            'desde' => $desde,
            'hasta' => $hasta,
            'ip' => $this->ipONull(),
            'host' => $this->hostONull(),
            'episodios' => $this->que !== self::VENTANAS,
            'ventanas' => $this->que !== self::EPISODIOS,
        ];
    }

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

        $this->validate();

        [$desde, $hasta] = $this->rango();

        if ($desde === null) {
            return;
        }

        $resultado = LimpiezaDeCortes::porCriterios($this->criterios($desde, $hasta));

        MonitoringLog::db(
            'warning',
            '16040',
            // **El rastro dice los criterios y no solo el resultado.** Sin ellos, el día que
            // falte una fila no hay forma de saber si entraba en aquel borrado.
            'Limpieza del registro de cortes: ' . $resultado['episodios'] . ' episodios y '
            . $resultado['ventanas'] . ' ventanas borradas del '
            . $desde->format('d/m/Y') . ' al ' . $hasta->format('d/m/Y')
            . ($this->ipONull() !== null ? ' · IP ' . $this->ip : '')
            . ($this->hostONull() !== null ? ' · host ' . $this->host : '')
            . ' (por ' . (Auth::user()?->name ?? 'desconocido') . ')',
            'ApiBlock',
            null
        );

        $partes = [];

        if ($this->que !== self::VENTANAS) {
            $partes[] = $resultado['episodios'] . ' episodios';
        }

        if ($this->que !== self::EPISODIOS) {
            $partes[] = $resultado['ventanas'] . ' ventanas del limitador';
        }

        session()->flash('success', 'Borrados ' . implode(' y ', $partes)
            . '. El borrado es definitivo; queda el rastro en Acciones del panel.');

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

    /**
     * El rango, con el día final entero.
     *
     * **El `endOfDay` no es un detalle**: sin él, poner el mismo día en las dos fechas no
     * borraría nada, porque la comparación sería contra las 00:00 de ese día. Es el mismo
     * cuidado que en la limpieza del histórico del visor.
     *
     * @return array{?Carbon, ?Carbon}
     */
    private function rango(): array
    {
        if ($this->desde === '' || $this->hasta === '') {
            return [null, null];
        }

        try {
            return [
                Carbon::parse($this->desde)->startOfDay(),
                Carbon::parse($this->hasta)->endOfDay(),
            ];
        } catch (\Throwable) {
            // Una fecha a medio escribir no puede tumbar la modal mientras se teclea.
            return [null, null];
        }
    }

    private function ipONull(): ?string
    {
        $ip = trim($this->ip);

        return $ip === '' ? null : $ip;
    }

    private function hostONull(): ?string
    {
        $host = trim($this->host);

        return $host === '' ? null : $host;
    }

    public function render()
    {
        return view('livewire.monitoring.limpieza-de-cortes-modal', [
            'impacto' => $this->impacto(),
        ]);
    }
}
