<?php

namespace Tests\Feature\Monitoring;

use App\Livewire\Monitoring\SystemLog;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Monitoring\Log as MonitoringLog;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

/**
 * La pantalla del registro de acciones del panel.
 *
 * **El hueco que tapaba.** La tabla `logs` se escribe desde media aplicación —bajas de
 * cliente, cambios de dominio, renovaciones, borrados de logs, cambios de umbrales,
 * bloqueos manuales— y **no tenía pantalla**. Se veía solo en el bloque «Sistema» del
 * Dashboard: los 5 más recientes, de 7 días, y solo de nivel `danger` o `warning`. Todo
 * lo de nivel `info` no aparecía en ningún sitio.
 *
 * El permiso `admin.logs.index` lo creaba una migración vieja **sin ruta ni pantalla**
 * (MGR-011). Esta pantalla lo recupera.
 */
class SystemLogTest extends TestCase
{
    use RefreshDatabase;

    private function usuario(bool $conPermiso = true): User
    {
        foreach (['admin.logs.index', 'admin.dashboard.index'] as $nombre) {
            Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'logs', 'description' => $nombre]
            );
        }

        $user = User::factory()->create();
        $user->givePermissionTo($conPermiso ? 'admin.logs.index' : 'admin.dashboard.index');
        $this->actingAs($user);
        Cache::flush();

        return $user;
    }

    private function entrada(string $nivel, string $codigo, string $mensaje, ?string $cuando = null): MonitoringLog
    {
        $log = MonitoringLog::create([
            'level' => $nivel,
            'code' => $codigo,
            'msg' => $mensaje,
        ]);

        if ($cuando !== null) {
            // `update` sobre timestamps hay que forzarlo: `created_at` no es fillable.
            MonitoringLog::whereKey($log->id)->update(['created_at' => $cuando]);
        }

        return $log->fresh();
    }

    /* ===================== Acceso ===================== */

    public function test_sin_permiso_no_se_entra(): void
    {
        $this->usuario(conPermiso: false);

        $this->get(route('monitoring.system-log'))->assertForbidden();
    }

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

        $this->get(route('monitoring.system-log'))
            ->assertOk()
            ->assertSee('Acciones del panel');
    }

    /* ===================== Lo que se ve ===================== */

    public function test_muestra_tambien_los_de_nivel_info(): void
    {
        // El Dashboard solo pinta `danger` y `warning`, así que una renovación de
        // licencia (info) no aparecía en ningún sitio.
        $this->usuario();
        $this->entrada('info', '16018', 'Licencia renovada 1 año: Cliente X');

        Livewire::test(SystemLog::class)->assertSee('Licencia renovada');
    }

    public function test_traduce_el_codigo_a_algo_legible(): void
    {
        // Nadie recuerda que el 16012 es el corte de los límites.
        $this->usuario();
        $this->entrada('warning', '16012', 'Los límites de la API pasan a CORTAR peticiones');

        Livewire::test(SystemLog::class)
            ->assertSee('Cambio del modo de los límites de la API');
    }

    public function test_un_codigo_desconocido_no_rompe_la_pantalla(): void
    {
        // Un `Log::db()` con un código que no esté en el catálogo tiene que seguir
        // funcionando: mejor «Código NNNNN» que un error.
        $this->usuario();
        $this->entrada('warning', '99999', 'Algo que no está catalogado');

        Livewire::test(SystemLog::class)
            ->assertSee('Código 99999')
            ->assertSee('Algo que no está catalogado');
    }

    /* ===================== Los filtros ===================== */

    public function test_filtra_por_nivel(): void
    {
        $this->usuario();
        $this->entrada('warning', '16019', 'Cliente dado de BAJA');
        $this->entrada('info', '16015', 'Una entrada informativa cualquiera');

        Livewire::test(SystemLog::class)
            ->set('nivel', 'warning')
            ->assertSee('Cliente dado de BAJA')
            ->assertDontSee('Una entrada informativa cualquiera');
    }

    public function test_filtra_por_codigo(): void
    {
        $this->usuario();
        $this->entrada('warning', '16020', 'Dominio del entorno A cambiado');
        $this->entrada('warning', '16019', 'Cliente dado de BAJA');

        Livewire::test(SystemLog::class)
            ->set('codigo', '16020')
            ->assertSee('Dominio del entorno A cambiado')
            ->assertDontSee('Cliente dado de BAJA');
    }

    public function test_busca_en_el_mensaje(): void
    {
        // Es la forma real de encontrar algo: «¿qué se hizo con el cliente Tal?».
        $this->usuario();
        $this->entrada('warning', '16019', 'Cliente dado de BAJA: Ayuntamiento de Alcorcón');
        $this->entrada('warning', '16019', 'Cliente dado de BAJA: Otra cosa');

        Livewire::test(SystemLog::class)
            ->set('busqueda', 'Alcorcón')
            ->assertSee('Ayuntamiento de Alcorcón')
            ->assertDontSee('Otra cosa');
    }

    public function test_el_rango_de_fechas_incluye_los_dos_dias(): void
    {
        // Comparando contra las 00:00 del día «hasta» se quedaría fuera ese día entero:
        // es la trampa de `whereDate` que ya mordió en `DataAction`.
        $this->usuario();
        $this->entrada('warning', '16019', 'Del primer día', now()->subDays(5)->setTime(23, 50)->toDateTimeString());
        $this->entrada('warning', '16019', 'Del último día', now()->subDays(3)->setTime(23, 50)->toDateTimeString());
        $this->entrada('warning', '16019', 'De fuera del rango', now()->subDays(6)->toDateTimeString());

        Livewire::test(SystemLog::class)
            ->set('desde', now()->subDays(5)->format('Y-m-d'))
            ->set('hasta', now()->subDays(3)->format('Y-m-d'))
            ->assertSee('Del primer día')
            ->assertSee('Del último día')
            ->assertDontSee('De fuera del rango');
    }

    public function test_el_desplegable_solo_ofrece_los_codigos_que_existen(): void
    {
        // Un desplegable con los 22 del catálogo, de los que la mayoría no se ha usado
        // nunca, hace buscar entre opciones vacías.
        $this->usuario();
        $this->entrada('warning', '16019', 'Una baja');

        $codigos = Livewire::test(SystemLog::class)->viewData('codigosUsados');

        $this->assertSame(['16019'], $codigos);
    }

    /* ===================== El enlace a la ficha ===================== */

    public function test_enlaza_con_la_ficha_del_cliente(): void
    {
        // `entity` y `entity_id` se rellenaban desde el principio y no se usaban para
        // nada; el accessor `url` componía una ruta que no existe en este proyecto.
        $this->usuario();

        $cliente = Client::create([
            'name' => 'Cliente con ficha',
            'shortname' => 'ficha' . uniqid(),
            'actived' => true,
        ]);

        $log = MonitoringLog::create([
            'level' => 'warning',
            'code' => '16019',
            'msg' => 'Cliente dado de BAJA',
            'entity' => 'Client',
            'entity_id' => (string) $cliente->id,
        ]);

        $this->assertSame(route('clients.show', $cliente->id), $log->enlace());

        Livewire::test(SystemLog::class)->assertSee('Ver ficha');
    }

    public function test_sin_entidad_no_se_ofrece_enlace(): void
    {
        $this->usuario();
        $log = $this->entrada('warning', '16012', 'Cambio de modo');

        $this->assertNull($log->enlace());
    }
    /* ===================== El usuario responsable ===================== */

    public function test_el_log_guarda_quien_lo_hizo(): void
    {
        // Antes el nombre iba DENTRO del texto del mensaje, concatenado a mano en cada
        // sitio: no se podía filtrar por persona ni distinguir a alguien de una tarea.
        $usuario = $this->usuario();

        MonitoringLog::db('warning', '16019', 'Cliente dado de BAJA');

        $log = MonitoringLog::latest('id')->first();

        $this->assertSame($usuario->id, $log->user_id);
        $this->assertSame($usuario->name, $log->responsable);
    }

    public function test_una_tarea_programada_se_distingue_de_una_persona(): void
    {
        // Sin sesión no hay usuario, y eso también es información: lo hizo el cron.
        MonitoringLog::db('warning', '16010', 'Licencia desactivada por caducidad');

        $log = MonitoringLog::latest('id')->first();

        $this->assertNull($log->user_id);
        $this->assertSame('Tarea programada o API', $log->responsable);
    }

    public function test_filtra_por_usuario(): void
    {
        $usuario = $this->usuario();
        MonitoringLog::db('warning', '16019', 'Lo hice yo');

        $otro = User::factory()->create(['name' => 'Otra persona']);
        $this->actingAs($otro);
        MonitoringLog::db('warning', '16019', 'Lo hizo el otro');
        $this->actingAs($usuario);

        Livewire::test(SystemLog::class)
            ->set('usuario', (string) $usuario->id)
            ->assertSee('Lo hice yo')
            ->assertDontSee('Lo hizo el otro');
    }

    public function test_se_pueden_ver_solo_las_de_las_tareas(): void
    {
        // «¿Qué ha hecho el sistema solo?» es una pregunta distinta de «¿qué ha tocado
        // esta cuenta?», y el 0 del filtro es esa opción, no la ausencia de filtro.
        $usuario = $this->usuario();
        MonitoringLog::db('warning', '16019', 'Lo hizo una persona');

        auth()->logout();
        MonitoringLog::db('warning', '16010', 'Lo hizo el cron');
        $this->actingAs($usuario);

        Livewire::test(SystemLog::class)
            ->set('usuario', '0')
            ->assertSee('Lo hizo el cron')
            ->assertDontSee('Lo hizo una persona');
    }
}
