<?php

namespace App\Console\Commands;

use App\Models\Monitoring\ApiBlock;
use App\Models\Monitoring\ApiRateEvent;
use App\Models\Monitoring\Log as MonitoringLog;
use App\Services\System\LimpiezaDeCortes;
use App\Services\System\Settings;
use Illuminate\Console\Command;

/**
 * Borrado del registro de cortes: `api_blocks` y `api_rate_events`.
 *
 * **Estas dos tablas crecen con el tráfico y nadie las miraba.** `api_rate_events` guarda
 * una fila por ventana de un minuto y por sujeto, así que en un parque con cientos de
 * Moodles llamando cada minuto es la segunda tabla que más crece del sistema después del
 * log de peticiones — y era la única sin ninguna forma de limpiarse, ni a mano ni sola.
 *
 * Los días los decide el panel (`--retention`), no el crontab: si estuvieran en la línea de
 * cron, cambiar la retención obligaría a redesplegar. Mientras nadie configure una
 * retención, la tarea se ejecuta y no borra nada.
 *
 * **Un corte activo no se borra nunca**, diga lo que diga la retención: mientras esté
 * cortando, esa fila es la que explica por qué un cliente no recibe servicio, y borrarla
 * dejaría el corte en pie sin nada que lo justifique. Eso lo garantiza
 * {@see LimpiezaDeCortes::porRetencion()}, que solo mira los ya cerrados.
 *
 * Ejemplos:
 *   php artisan api-blocks:purge --retention          los días que diga el panel
 *   php artisan api-blocks:purge --episodios=90       lo cerrado hace más de 90 días
 *   php artisan api-blocks:purge --ventanas=30
 *   php artisan api-blocks:purge --retention --dry-run
 */
class PurgeApiBlocks extends Command
{
    protected $signature = 'api-blocks:purge
        {--retention : Usa los días configurados en el panel; si no hay retención, no hace nada}
        {--episodios= : Borra los episodios cerrados anteriores a N días}
        {--ventanas= : Borra las ventanas del limitador anteriores a N días}
        {--all : Vacía el registro entero. Los cortes ACTIVOS no se tocan}
        {--dry-run : No borra nada; dice cuántas filas serían}';

    protected $description = 'Borra episodios de corte cerrados y ventanas del limitador (api_blocks, api_rate_events)';

    public function handle(Settings $ajustes): int
    {
        $episodios = $this->option('episodios');
        $ventanas = $this->option('ventanas');

        // **Vaciar del todo.** Va antes que lo demás porque no se combina con nada: o se
        // borra por antigüedad o se borra entero.
        if ($this->option('all')) {
            if ($this->option('retention') || $episodios !== null || $ventanas !== null) {
                $this->error('--all no se combina con --retention, --episodios ni --ventanas.');

                return self::FAILURE;
            }

            return $this->vaciar();
        }

        if ($this->option('retention')) {
            if ($episodios !== null || $ventanas !== null) {
                $this->error('--retention no se combina con --episodios ni --ventanas.');

                return self::FAILURE;
            }

            $episodios = (string) $ajustes->diasDeRetencionDeCortes();
            $ventanas = (string) $ajustes->diasDeRetencionDeVentanas();

            if ((int) $episodios === 0 && (int) $ventanas === 0) {
                $this->info('No hay retención configurada: no se borra nada.');

                return self::SUCCESS;
            }

            $this->line('Retención del panel: episodios <fg=yellow>' . $episodios
                . '</> días · ventanas <fg=yellow>' . $ventanas . '</> días.');
        }

        if ($episodios === null && $ventanas === null) {
            $this->error('Indica --retention, --episodios=N o --ventanas=N. Sin nada de eso no se borra nada.');

            return self::FAILURE;
        }

        $dias = ['episodios' => (int) $episodios, 'ventanas' => (int) $ventanas];

        if ($this->option('dry-run')) {
            return $this->contar($dias);
        }

        $borrados = LimpiezaDeCortes::porRetencion($dias['episodios'], $dias['ventanas']);

        $this->info('Borrados ' . $borrados['episodios'] . ' episodio(s) y '
            . $borrados['ventanas'] . ' ventana(s).');

        // **Con rastro, como cualquier otro borrado del registro.** Aquí no hay nadie
        // delante, así que si no queda escrito, el día que falte un episodio no habrá forma
        // de saber si lo borró la retención o alguien a mano.
        if ($borrados['episodios'] > 0 || $borrados['ventanas'] > 0) {
            MonitoringLog::db(
                'info',
                '16040',
                'Retención del registro de cortes: ' . $borrados['episodios'] . ' episodios ('
                . $dias['episodios'] . ' días) y ' . $borrados['ventanas'] . ' ventanas ('
                . $dias['ventanas'] . ' días) borradas por la tarea programada',
                'ApiBlock',
                null
            );
        }

        return self::SUCCESS;
    }

    /**
     * Vacía el registro entero, o dice cuánto se llevaría.
     *
     * Los cortes activos se cuentan aparte y se dicen: quien vacía el registro tiene que
     * saber que eso no levanta ningún corte.
     */
    private function vaciar(): int
    {
        if ($this->option('dry-run')) {
            $cuenta = LimpiezaDeCortes::contarTodo();

            $this->line('Se borraría <fg=yellow>TODO</> el registro: '
                . '<fg=yellow>' . $cuenta['episodios'] . '</> episodio(s) cerrado(s) y '
                . '<fg=yellow>' . $cuenta['ventanas'] . '</> ventana(s). Nada se ha borrado.');

            if ($cuenta['activos'] > 0) {
                $this->line('Los ' . $cuenta['activos'] . ' corte(s) activo(s) se quedan: '
                    . 'siguen cortando, así que su fila es el estado y no el histórico.');
            }

            return self::SUCCESS;
        }

        $borrados = LimpiezaDeCortes::todo();

        $this->info('Borrados ' . $borrados['episodios'] . ' episodio(s) y '
            . $borrados['ventanas'] . ' ventana(s).');

        if ($borrados['activos'] > 0) {
            $this->line('Los ' . $borrados['activos'] . ' corte(s) activo(s) se han quedado.');
        }

        if ($borrados['episodios'] > 0 || $borrados['ventanas'] > 0) {
            MonitoringLog::db(
                'warning',
                '16040',
                'Registro de cortes vaciado a mano: ' . $borrados['episodios'] . ' episodios y '
                . $borrados['ventanas'] . ' ventanas borradas con --all',
                'ApiBlock',
                null
            );
        }

        return self::SUCCESS;
    }

    /** Qué se llevaría, sin llevárselo. */
    private function contar(array $dias): int
    {
        $episodios = $dias['episodios'] > 0
            ? ApiBlock::query()
                ->borrable()
                ->where('last_blocked_at', '<', now()->subDays($dias['episodios']))
                ->count()
            : 0;

        $ventanas = $dias['ventanas'] > 0
            ? ApiRateEvent::query()
                ->where('window_started_at', '<', now()->subDays($dias['ventanas']))
                ->count()
            : 0;

        $this->line('Se borrarían <fg=yellow>' . $episodios . '</> episodio(s) cerrado(s) y '
            . '<fg=yellow>' . $ventanas . '</> ventana(s). Nada se ha borrado.');

        // Y se dice lo que la retención NO se lleva, que es la pregunta siguiente.
        $activos = ApiBlock::blocked()->count();

        if ($activos > 0) {
            $this->line('Los ' . $activos . ' corte(s) activo(s) no se tocan, tengan la antigüedad que tengan.');
        }

        return self::SUCCESS;
    }
}
