<?php

namespace Tests\Feature\ApiLogs;

use App\Livewire\ApiLogs\Index as ApiLogsIndex;
use App\Models\ApiRequestLog;
use App\Models\Auth\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * El visor de logs tiene que seguir siendo usable cuando la tabla crece.
 *
 * Antes hacía doce consultas por render y dos de ellas cargaban en memoria la
 * columna `duration_ms` de TODAS las filas del rango: en producción la pantalla no
 * se podía abrir. Ver known-issues MGR-027.
 */
class ViewerPerformanceTest extends TestCase
{
    use RefreshDatabase;

    private function actingAsConAcceso(): User
    {
        $user = User::factory()->create();

        Permission::firstOrCreate(
            ['name' => 'admin.api-logs.index', 'guard_name' => 'web'],
            ['model' => 'api_logs', 'description' => 'API Logs: Ver listado']
        );

        $user->givePermissionTo('admin.api-logs.index');
        $this->actingAs($user);

        return $user;
    }

    private function sembrarPeticiones(int $cuantas): void
    {
        $filas = [];

        for ($i = 0; $i < $cuantas; $i++) {
            $filas[] = [
                'request_uuid' => (string) \Illuminate\Support\Str::uuid(),
                'started_at' => now()->subHours($i % 48),
                'ended_at' => now()->subHours($i % 48),
                'method' => 'POST',
                'path' => 'api/v1',
                'action' => ['licence', 'products', 'setup', 'features'][$i % 4],
                'http_status' => [200, 200, 401, 500][$i % 4],
                'success' => $i % 4 < 2,
                'duration_ms' => 10 + ($i % 300),
                'ip' => '127.0.0.1',
                'auth_type' => 'bearer',


            ];
        }

        foreach (array_chunk($filas, 200) as $lote) {
            ApiRequestLog::insert($lote);
        }
    }

    public function test_los_percentiles_no_cargan_la_columna_entera(): void
    {
        $this->actingAsConAcceso();
        $this->sembrarPeticiones(600);

        Cache::flush();

        $consultas = 0;
        $filasLeidas = 0;

        DB::listen(function ($consulta) use (&$consultas, &$filasLeidas) {
            $consultas++;

            // Antes había un `select duration_ms ... order by duration_ms` sin límite:
            // eso es lo que traía la columna entera a PHP.
            if (str_contains($consulta->sql, 'duration_ms') && !str_contains($consulta->sql, 'limit')) {
                if (str_contains($consulta->sql, 'select `duration_ms`') || str_contains($consulta->sql, 'select "duration_ms"')) {
                    $filasLeidas++;
                }
            }
        });

        Livewire::test(ApiLogsIndex::class)->assertOk();

        DB::listen(fn () => null);

        $this->assertSame(
            0,
            $filasLeidas,
            'Se sigue trayendo la columna duration_ms completa a PHP.'
        );
    }

    public function test_los_indicadores_se_cachean_entre_renders(): void
    {
        $this->actingAsConAcceso();
        $this->sembrarPeticiones(300);

        Cache::flush();
        config(['api.logs_viewer.stats_cache_seconds' => 300]);

        $componente = Livewire::test(ApiLogsIndex::class);

        $consultasPrimero = 0;
        DB::listen(function () use (&$consultasPrimero) {
            $consultasPrimero++;
        });

        // Una interacción cualquiera vuelve a ejecutar render(): con los indicadores
        // cacheados debe quedar solo la consulta paginada y poco más.
        $componente->call('$refresh');

        DB::listen(fn () => null);

        // **El umbral es 20 y no 10, y la diferencia está medida**: con los indicadores
        // cacheados el refresco cuesta **10** consultas —el paginador, su recuento, los
        // vistos que esconde y los tres selectores buscables— y sin caché, **52**. Con 10
        // justo, el test fallaba al cambiar el defecto de severidad sin que la caché tuviera
        // nada que ver; con 20 sigue cazando lo que existe para cazar, que es que los
        // indicadores dejen de cachearse.
        $this->assertLessThan(
            20,
            $consultasPrimero,
            "Un cambio de filtro ha lanzado {$consultasPrimero} consultas: los indicadores no se están cacheando."
        );
    }

    public function test_los_percentiles_siguen_dando_un_valor_razonable(): void
    {
        $this->actingAsConAcceso();

        // Duraciones conocidas: 1..100 ms.
        $filas = [];
        for ($i = 1; $i <= 100; $i++) {
            $filas[] = [
                'request_uuid' => (string) \Illuminate\Support\Str::uuid(),
                'started_at' => now()->subMinutes(10),
                'ended_at' => now()->subMinutes(10),
                'method' => 'POST',
                'path' => 'api/v1',
                'action' => 'licence',
                'http_status' => 200,
                'success' => true,
                'duration_ms' => $i,
                'ip' => '127.0.0.1',
                'auth_type' => 'bearer',


            ];
        }
        ApiRequestLog::insert($filas);

        Cache::flush();

        Livewire::test(ApiLogsIndex::class)
            ->assertViewHas('p50', fn ($valor) => $valor >= 48 && $valor <= 52)
            ->assertViewHas('p95', fn ($valor) => $valor >= 93 && $valor <= 97);
    }

    public function test_la_pantalla_abre_con_volumen(): void
    {
        $this->actingAsConAcceso();
        $this->sembrarPeticiones(2000);

        Cache::flush();

        $this->get('/api-logs')->assertOk();
    }
}
