<?php

namespace Tests\Feature\ApiLogs;

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

/**
 * El filtro de antigüedad del visor: 24 h / 7 días / 30 días / a medida.
 *
 * Se escribe porque **no había ni un test del rango de fechas**, que es el filtro que se
 * usa siempre y del que dependen todos los demás —los indicadores, el periodo anterior y
 * la clave de caché salen de él—.
 */
class RangoDeFechasTest extends TestCase
{
    use RefreshDatabase;

    private function usuario(): User
    {
        $rol = Role::firstOrCreate(['name' => 'RolDeFechas', 'guard_name' => 'web']);
        $rol->givePermissionTo(Permission::firstOrCreate(
            ['name' => 'admin.api-logs.index', 'guard_name' => 'web'],
            ['model' => 'api-logs', 'description' => 'admin.api-logs.index']
        ));

        $user = User::factory()->create();
        $user->assignRole($rol);
        $this->actingAs($user);
        Cache::flush();

        return $user;
    }

    private function peticion(\Carbon\Carbon $cuando): void
    {
        ApiRequestLog::create([
            'request_uuid' => (string) Str::uuid(),
            'method' => 'POST',
            'path' => '/api/v1',
            'auth_type' => 'token',
            'action' => 'licence',
            'http_status' => 200,
            'error_code' => 0,
            'duration_ms' => 40,
            'started_at' => $cuando,
            'ended_at' => $cuando,
        ]);
    }

    public function test_cada_preset_acota_lo_que_se_ve(): void
    {
        $this->usuario();

        // Una de hoy, una de hace tres días y una de hace veinte: cada preset debería
        // dejar dentro un número distinto.
        $this->peticion(now()->subHours(2));
        $this->peticion(now()->subDays(3));
        $this->peticion(now()->subDays(20));

        // **Viéndolo todo**: estas peticiones son correctas y la pantalla abre por
        // «errores», así que sin esto el listado saldría vacío y el test estaría midiendo el
        // filtro de severidad en lugar del rango de fechas. Los indicadores sí describen el
        // rango entero, y por eso `total` no necesita esto.
        $componente = Livewire::test(Index::class)->set('severity', '');

        // El preset por defecto son 30 días: entran las tres.
        $this->assertSame(3, $componente->viewData('total'), '30d por defecto');

        $componente->set('rangePreset', '24h');
        $this->assertSame(1, $componente->viewData('total'), '24h deja solo la de hoy');
        $this->assertCount(1, $componente->viewData('logs'));

        $componente->set('rangePreset', '7d');
        $this->assertSame(2, $componente->viewData('total'), '7d deja la de hoy y la de hace tres');
        $this->assertCount(2, $componente->viewData('logs'));

        $componente->set('rangePreset', '30d');
        $this->assertSame(3, $componente->viewData('total'), 'y 30d las tres otra vez');
    }

    public function test_la_cache_de_indicadores_aguanta_su_ttl_entero(): void
    {
        // **La caché de cinco minutos duraba uno.** La clave salía de `dateFrom`/`dateTo`,
        // que con un preset se recalculan desde `now()` con precision de minuto: al
        // cambiar el minuto cambiaba la clave y los doce agregados volvian a correr sobre
        // la tabla que mas crece del sistema.
        $this->usuario();

        // **El reloj se ancla al principio de un cubo.** La clave lleva
        // `intdiv(now(), 300)`, asi que saltar 70 segundos desde un momento cualquiera
        // cruza el borde del cubo una vez de cada cuatro y el test falla solo. Con el
        // reloj puesto en un multiplo de 300, los 70 segundos caen siempre dentro.
        $this->travelTo(\Carbon\Carbon::createFromTimestamp(1_800_000_000));

        $componente = Livewire::test(Index::class);
        $clave = $this->clave($componente->instance());

        // Un minuto y diez segundos despues: dentro del mismo TTL de 300 s.
        $this->travel(70)->seconds();

        $this->assertSame($clave, $this->clave($componente->instance()),
            "la clave no puede cambiar dentro del TTL: si cambia, no hay cache");
    }

    /** `statsCacheKey()` es protegido: se lee por reflexion para no abrirlo solo por el test. */
    private function clave(object $componente): string
    {
        $metodo = new \ReflectionMethod($componente, "statsCacheKey");
        $metodo->setAccessible(true);

        return $metodo->invoke($componente);
    }

    public function test_el_rango_a_medida_manda_sobre_el_preset(): void
    {
        $this->usuario();

        $this->peticion(now()->subDays(1));
        $this->peticion(now()->subDays(10));

        $componente = Livewire::test(Index::class)
            ->set('rangePreset', 'custom')
            ->set('dateFrom', now()->subDays(12)->format('Y-m-d\TH:i'))
            ->set('dateTo', now()->subDays(5)->format('Y-m-d\TH:i'));

        // Solo la de hace diez días cae en la ventana de 12 a 5 días atrás.
        $this->assertSame(1, $componente->viewData('total'));
    }

    public function test_volver_de_a_medida_a_un_preset_recalcula_las_fechas(): void
    {
        // Es la trampa: `dateFrom` y `dateTo` viven en la URL, así que al salir de «a
        // medida» tienen valores puestos. Si no se recalcularan, el preset no haría nada.
        $this->usuario();

        $this->peticion(now()->subHours(1));

        $componente = Livewire::test(Index::class)
            ->set('rangePreset', 'custom')
            ->set('dateFrom', now()->subDays(30)->format('Y-m-d\TH:i'))
            ->set('dateTo', now()->subDays(20)->format('Y-m-d\TH:i'));

        $this->assertSame(0, $componente->viewData('total'), 'la ventana antigua no coge nada');

        $componente->set('rangePreset', '24h');

        $this->assertSame(1, $componente->viewData('total'),
            'al volver a un preset, las fechas se recalculan');
    }
}
