<?php

namespace App\Services\System;

use App\Models\ApiRequestLog;
use App\Models\Monitoring\Log as MonitoringLog;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;

/**
 * Borrado de `api_request_logs`: por lotes y dejando rastro.
 *
 * Existe porque el mismo borrado se hace desde tres sitios —el visor, la configuración y
 * la consola— y las tres copias tenían que estar de acuerdo en dos cosas que no son
 * negociables:
 *
 * 1. **Por lotes.** Un `DELETE` de golpe bloquea la tabla mientras la API sigue
 *    escribiendo en ella, y esta es la tabla que recibe una fila por petición de cada
 *    entorno.
 * 2. **Con rastro.** Queda quién, cuántas filas y con qué criterio. Un borrado de logs
 *    que no deja rastro es exactamente lo que haría alguien tapando huellas.
 *
 * La consulta llega como **callable**, no como builder: hay que reconstruirla en cada
 * lote. Reutilizar el mismo builder con `limit()` dentro de un bucle arrastra estado y
 * acaba borrando lo que no toca.
 */
class ApiLogPurger
{
    /** Filas por lote. */
    public const LOTE = 2000;

    /**
     * La consulta de un criterio de limpieza.
     *
     * **Está aquí y no en la pantalla porque hay dos pantallas.** La modal de limpieza
     * cuenta lo que se va a borrar y el visor lo borra después, en dos componentes
     * distintos; si cada uno armara su propia consulta, el día que una cambiara el aviso
     * diría un número y el borrado se llevaría otro — y eso, en algo que no tiene vuelta
     * atrás, es lo peor que puede pasar.
     *
     * Devuelve `null` para los criterios que **no se pueden definir aquí**: «lo que se está
     * viendo» son los filtros del visor, y solo el visor los conoce.
     *
     * @param  array{criterio?: string, dias?: int|string|null, desde?: string|null, hasta?: string|null}  $orden
     */
    public static function consultaDe(array $orden): ?Builder
    {
        $criterio = $orden['criterio'] ?? '';

        if ($criterio === 'antiguedad' && ($orden['dias'] ?? null) !== null) {
            return ApiRequestLog::query()
                ->where('started_at', '<', now()->subDays((int) $orden['dias']));
        }

        if ($criterio === 'rango' && ! empty($orden['desde']) && ! empty($orden['hasta'])) {
            // **Los dos días van incluidos.** Sin el `endOfDay`, poner el mismo día en
            // «desde» y en «hasta» no borra nada: la comparación sería contra las 00:00.
            return ApiRequestLog::query()
                ->whereBetween('started_at', [
                    Carbon::parse($orden['desde'])->startOfDay(),
                    Carbon::parse($orden['hasta'])->endOfDay(),
                ]);
        }

        return null;
    }

    /**
     * @param  callable(): \Illuminate\Database\Eloquent\Builder  $consulta
     * @param  string  $criterio  Qué se ha borrado, para el log ("anteriores a 90 días")
     * @param  string  $origen    Desde dónde ("el visor", "la configuración", "consola")
     * @param  callable(int): void|null  $avance  Se llama con las filas de cada lote
     */
    public function borrar(
        callable $consulta,
        string $criterio,
        string $origen,
        ?int $porLote = null,
        ?callable $avance = null
    ): int {
        $lote = max(100, $porLote ?? self::LOTE);
        $borrados = 0;

        do {
            $cuantos = $consulta()->limit($lote)->delete();
            $borrados += $cuantos;

            if ($avance !== null) {
                $avance($cuantos);
            }
        } while ($cuantos > 0);

        MonitoringLog::db(
            'warning',
            '16002',
            'Borrado de ' . $borrados . ' registro(s) de api_request_logs (' . $criterio . ') desde '
                . $origen . ' por ' . (Auth::user()?->email ?? 'una tarea programada'),
            'ApiRequestLog',
            null
        );

        return $borrados;
    }
}
