<?php

namespace Tests\Feature\Auth;

use App\Models\Auth\User;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * El rol Support.
 *
 * **Tenía cero de los 126 permisos**: entraba al panel y no veía una sola pantalla, y a
 * la vez cinco pantallas del panel están construidas diciendo «esto lo mira Soporte».
 *
 * Lo que se fija aquí es **el criterio**, no la lista: Soporte lee todo lo que hace falta
 * para diagnosticar, puede sincronizar, y no puede tocar nada que rompa. Si mañana
 * alguien añade `admin.clients.destroy` a la lista, estos tests lo paran.
 */
class SupportRoleTest extends TestCase
{
    use RefreshDatabase;

    private function soporte(): User
    {
        $rol = Role::firstOrCreate(['name' => 'Support', 'guard_name' => 'web']);

        foreach (RoleSeeder::PERMISOS_DE_SOPORTE as $nombre) {
            Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'test', 'description' => $nombre]
            );
        }

        $rol->syncPermissions(RoleSeeder::PERMISOS_DE_SOPORTE);

        $user = User::factory()->create();
        $user->assignRole($rol);

        return $user;
    }

    private function permiso(string $nombre): void
    {
        Permission::firstOrCreate(
            ['name' => $nombre, 'guard_name' => 'web'],
            ['model' => 'test', 'description' => $nombre]
        );
    }

    /* ===================== Lo que tiene que poder ===================== */

    public function test_soporte_puede_abrir_las_pantallas_de_diagnostico(): void
    {
        // Son las que responden «este cliente dice que no le llega el contenido».
        $soporte = $this->soporte();

        $imprescindibles = [
            'admin.dashboard.index',
            'admin.clients.index',
            'admin.environments.index',
            'admin.license-tokens.index',
            'admin.license-tokens.show',
            'admin.products.index',
            'admin.api-logs.index',
            'admin.logs.index',
            'admin.notices.index',
        ];

        foreach ($imprescindibles as $permiso) {
            $this->assertTrue($soporte->can($permiso), 'Soporte debería poder ' . $permiso);
        }
    }

    public function test_soporte_puede_sincronizar_un_entorno(): void
    {
        // Es **la** acción de diagnóstico: dale a sincronizar y mira si falla. No cambia
        // datos comerciales, le pide los suyos al Moodle del cliente.
        $this->assertTrue($this->soporte()->can('admin.environments.refresh'));
    }

    public function test_soporte_ve_el_inventario_y_las_versiones_publicadas(): void
    {
        // «¿Qué versión hay publicada de esto?» es la otra mitad del diagnóstico.
        $soporte = $this->soporte();

        foreach (['admin.plugins.environment', 'admin.setups.index', 'admin.features.index',
                  'admin.scss.index', 'admin.js.index', 'admin.tutorials.index',
                  'admin.resources.index'] as $permiso) {
            $this->assertTrue($soporte->can($permiso), 'Soporte debería poder ' . $permiso);
        }
    }

    /* ===================== Lo que no puede ===================== */

    public function test_soporte_no_puede_tocar_datos_comerciales(): void
    {
        $soporte = $this->soporte();

        $prohibidos = [
            'admin.clients.create', 'admin.clients.edit', 'admin.clients.destroy',
            'admin.environments.create', 'admin.environments.edit', 'admin.environments.destroy',
            'admin.license-tokens.create', 'admin.license-tokens.edit', 'admin.license-tokens.destroy',
            'admin.products.create', 'admin.products.edit', 'admin.products.destroy',
        ];

        foreach ($prohibidos as $permiso) {
            $this->permiso($permiso);
            $this->assertFalse($soporte->fresh()->can($permiso), 'Soporte NO debería poder ' . $permiso);
        }
    }

    public function test_soporte_no_puede_bloquear_ips_ni_liberar_bloqueos(): void
    {
        // Decidido al diseñar Monitorización: no es lo mismo mirar que tocar. Cortar una
        // IP es cortar a un cliente.
        $this->permiso('admin.configs.edit');

        $this->assertFalse($this->soporte()->can('admin.configs.edit'));
    }

    public function test_soporte_no_ve_el_token_de_servicios_web_del_cliente(): void
    {
        // **Es una credencial de un tercero**, cifrada en reposo a propósito (MGR-038).
        // No se reparte por comodidad.
        $this->permiso('admin.tokens.index');

        $this->assertFalse($this->soporte()->can('admin.tokens.index'));
        $this->assertNotContains('admin.tokens.index', RoleSeeder::PERMISOS_DE_SOPORTE);
    }

    public function test_soporte_no_puede_borrar_el_log_de_peticiones(): void
    {
        $this->permiso('admin.api-logs.destroy');

        $this->assertFalse($this->soporte()->can('admin.api-logs.destroy'));
    }

    public function test_soporte_no_administra_usuarios_ni_roles(): void
    {
        $soporte = $this->soporte();

        foreach (['admin.users.index', 'admin.roles.index', 'admin.permissions.index'] as $permiso) {
            $this->permiso($permiso);
            $this->assertFalse($soporte->fresh()->can($permiso), 'Soporte NO debería poder ' . $permiso);
        }
    }

    /* ===================== La lista, como definición ===================== */

    public function test_la_lista_no_contiene_nada_destructivo(): void
    {
        // Red de seguridad sobre la lista entera: si alguien añade un `create`, `edit`,
        // `destroy` o `store` a los permisos de Soporte, esto lo para.
        foreach (RoleSeeder::PERMISOS_DE_SOPORTE as $permiso) {
            foreach (['.create', '.edit', '.destroy', '.store', '.disactivate'] as $verbo) {
                $this->assertStringNotContainsString(
                    $verbo,
                    $permiso,
                    'El permiso ' . $permiso . ' escribe, y Soporte solo diagnostica'
                );
            }
        }
    }

    public function test_la_lista_no_esta_vacia_ni_es_todo(): void
    {
        // El estado del que venimos era cero. Y darle los 126 sería «Admin con otro
        // nombre», que tampoco sirve.
        $cuantos = count(RoleSeeder::PERMISOS_DE_SOPORTE);

        $this->assertGreaterThan(20, $cuantos);
        $this->assertLessThan(60, $cuantos);
    }

    /* ===================== La prueba de verdad: abrir las pantallas ===================== */

    public function test_soporte_abre_las_pantallas_del_panel_de_verdad(): void
    {
        // Los permisos son la mitad de la historia: lo que importa es que las rutas
        // respondan 200. El `permission:` de la ruta es lo que decide de verdad.
        $this->actingAs($this->soporte());

        foreach (['dashboard', 'clients.index', 'environments.index',
                  'license_tokens.index', 'products.index', 'monitoring.index',
                  'monitoring.system-log', 'monitoring.notices', 'monitoring.expirations',
                  'api-logs.index'] as $ruta) {
            $this->get(route($ruta))
                ->assertOk('Soporte debería poder abrir ' . $ruta);
        }
    }

    public function test_soporte_no_abre_lo_que_no_le_toca(): void
    {
        $this->actingAs($this->soporte());

        foreach (['users.index', 'clients.create', 'environments.create',
                  'license_tokens.create', 'products.create'] as $ruta) {
            $this->get(route($ruta))
                ->assertForbidden('Soporte NO debería poder abrir ' . $ruta);
        }
    }
}
