<?php

namespace Tests\Feature\Configurations;

use App\Livewire\Configurations\Roles\Edit as RolesEdit;
use App\Livewire\Configurations\Access;
use App\Models\Auth\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Los roles del sistema (los que crea RoleSeeder) no se pueden borrar ni editar,
 * y borrar cualquier rol exige el permiso admin.roles.destroy.
 *
 * La protección anterior comparaba `$role->name === 'admin'` mientras el rol real
 * se llama `Admin`, así que no protegía nada. Ver known-issues MGR-018.
 */
class RolesTest extends TestCase
{
    use RefreshDatabase;

    private function permission(string $name): Permission
    {
        // La tabla `permissions` de este proyecto tiene `model` NOT NULL.
        return Permission::firstOrCreate(
            ['name' => $name, 'guard_name' => 'web'],
            ['model' => 'roles', 'description' => $name]
        );
    }

    private function actingAsUserWith(array $permissions): User
    {
        $user = User::factory()->create();

        foreach ($permissions as $name) {
            $this->permission($name);
        }

        $user->givePermissionTo($permissions);
        $this->actingAs($user);

        return $user;
    }

    private function makeRole(string $name, array $permissions = []): Role
    {
        $role = Role::create(['name' => $name, 'fullname' => $name, 'guard_name' => 'web']);

        foreach ($permissions as $permission) {
            $role->givePermissionTo($this->permission($permission));
        }

        return $role;
    }

    public static function systemRoleProvider(): array
    {
        return [
            'Admin con mayúscula' => ['Admin'],
            'admin en minúscula' => ['admin'],
            'Manager' => ['Manager'],
            'Developer' => ['Developer'],
            'Support' => ['Support'],
            'System' => ['System'],
            'WebService' => ['WebService'],
        ];
    }

    /**
     * @dataProvider systemRoleProvider
     */
    public function test_no_se_puede_borrar_un_rol_que_crea_el_despliegue(string $roleName): void
    {
        // **Borrar no es editar, y por eso sigue bloqueado en los seis** aunque sus permisos
        // ya se cambien desde el panel: borrarlos solo haría que volvieran a aparecer vacíos
        // en la siguiente migración de permisos, con su gente por el camino.
        $this->actingAsUserWith(['admin.roles.destroy']);

        // Sin usuarios asignados: el único freno real tiene que ser la protección
        // de roles del sistema, no el contador de usuarios.
        $role = $this->makeRole($roleName, ['admin.dashboard.index']);

        Livewire::test(Access::class)->call('borrarRol', ['itemId' => $role->id]);

        $this->assertNotNull(Role::find($role->id), "El rol del sistema {$roleName} se ha podido borrar.");
        $this->assertSame(1, Role::find($role->id)->permissions()->count());
    }

    public function test_si_se_puede_borrar_un_rol_propio_sin_usuarios(): void
    {
        $this->actingAsUserWith(['admin.roles.destroy']);

        $role = $this->makeRole('Coordinador');

        Livewire::test(Access::class)->call('borrarRol', ['itemId' => $role->id]);

        $this->assertNull(Role::find($role->id));
    }

    public function test_sin_permiso_no_se_puede_borrar_un_rol(): void
    {
        $this->actingAs(User::factory()->create());

        $role = $this->makeRole('Coordinador');

        Livewire::test(Access::class)
            ->call('borrarRol', ['itemId' => $role->id])
            ->assertForbidden();

        $this->assertNotNull(Role::find($role->id));
    }

    /**
     * @dataProvider adminProvider
     */
    public function test_los_permisos_de_admin_no_se_tocan_desde_el_panel(string $nombre): void
    {
        // **El único rol blindado, y por un motivo concreto**: es el que da acceso a esta
        // misma pantalla. Quitarle `admin.roles.edit` deja el panel sin ninguna forma de
        // volver a editar ningún rol, y la salida sería entrar a la base a mano.
        //
        // En minúscula también: la comprobación vieja era `=== 'admin'` y el rol real se
        // llama `Admin`, así que no se activaba nunca (MGR-018).
        $this->actingAsUserWith(['admin.roles.edit']);

        $role = $this->makeRole($nombre, ['admin.dashboard.index', 'admin.roles.index']);

        Livewire::test(RolesEdit::class, ['role' => $role])
            ->assertRedirect(route('configurations.access'));

        // Lo importante no es la redirección, es que no le hayan tocado los permisos.
        $this->assertSame(2, $role->fresh()->permissions()->count());
    }

    public static function adminProvider(): array
    {
        return ['Admin con mayúscula' => ['Admin'], 'admin en minúscula' => ['admin']];
    }

    /**
     * @dataProvider rolesEditablesProvider
     */
    public function test_un_admin_si_cambia_los_permisos_de_los_demas_roles(string $nombre): void
    {
        // **Lo que estaba mal hasta el 2026-09-15.** Los seis roles del seeder estaban
        // bloqueados, así que un Admin no podía cambiar el alcance de Soporte sin tocar el
        // seeder y desplegar: una decisión de organización convertida en tarea de
        // desarrollo.
        //
        // `System` y `WebService` entran también, y conviene saber por qué no da miedo: las
        // rutas de la API no llevan un solo middleware `permission:` —la autenticación es
        // por token— así que sus permisos no abren ni cierran nada del contrato.
        $this->actingAsUserWith(['admin.roles.edit']);

        $role = $this->makeRole($nombre, ['admin.dashboard.index']);
        $this->permission('admin.roles.index');

        Livewire::test(RolesEdit::class, ['role' => $role])
            ->set('permissions', ['admin.dashboard.index', 'admin.roles.index'])
            ->call('update');

        $this->assertSame(2, $role->fresh()->permissions()->count(), "No se han podido cambiar los permisos de $nombre.");
    }

    public static function rolesEditablesProvider(): array
    {
        return [
            'Manager' => ['Manager'],
            'Developer' => ['Developer'],
            'Support' => ['Support'],
            'System' => ['System'],
            'WebService' => ['WebService'],
        ];
    }

    /**
     * @dataProvider rolesEditablesProvider
     */
    public function test_pero_el_nombre_de_los_del_seeder_no_se_cambia(string $nombre): void
    {
        // **Es otra cosa y más pequeña que los permisos.** El seeder y las migraciones
        // buscan los roles por nombre: renombrar «Manager» no daría ningún error — haría que
        // la siguiente migración creara un Manager nuevo y vacío mientras los usuarios se
        // quedan en el renombrado, con los permisos viejos y sin que nadie se entere.
        //
        // Para la etiqueta visible está `fullname`, que sí se cambia.
        $this->actingAsUserWith(['admin.roles.edit']);

        $role = $this->makeRole($nombre, ['admin.dashboard.index']);

        Livewire::test(RolesEdit::class, ['role' => $role])
            ->set('name', 'OtroNombre')
            ->set('fullname', 'Una etiqueta nueva')
            ->call('update')
            ->assertHasErrors('name');

        $this->assertSame($nombre, $role->fresh()->name, 'el nombre interno se ha quedado igual');
    }

    public function test_el_alias_visible_si_se_puede_cambiar(): void
    {
        // La otra mitad: bloquear el nombre no puede dejar el rol sin forma de renombrarse
        // de cara a quien lo lee.
        $this->actingAsUserWith(['admin.roles.edit']);

        $role = $this->makeRole('Support', ['admin.dashboard.index']);

        Livewire::test(RolesEdit::class, ['role' => $role])
            ->set('fullname', 'Atención al cliente')
            ->call('update');

        $this->assertSame('Atención al cliente', $role->fresh()->fullname);
        $this->assertSame('Support', $role->fresh()->name);
    }

    public function test_un_rol_propio_si_se_puede_editar(): void
    {
        $this->actingAsUserWith(['admin.roles.edit']);

        $role = $this->makeRole('Coordinador', ['admin.dashboard.index']);
        $this->permission('admin.roles.index');

        Livewire::test(RolesEdit::class, ['role' => $role])
            ->set('fullname', 'Coordinador de proyecto')
            ->set('permissions', ['admin.dashboard.index', 'admin.roles.index'])
            ->call('update');

        $this->assertSame('Coordinador de proyecto', $role->fresh()->fullname);
        $this->assertSame(2, $role->fresh()->permissions()->count());
    }

    public function test_no_se_aceptan_permisos_inexistentes(): void
    {
        $this->actingAsUserWith(['admin.roles.edit']);

        $role = $this->makeRole('Coordinador', ['admin.dashboard.index']);

        Livewire::test(RolesEdit::class, ['role' => $role])
            ->set('permissions', ['admin.dashboard.index', 'permiso.que.no.existe'])
            ->call('update')
            ->assertHasErrors('permissions.1');

        $this->assertSame(1, $role->fresh()->permissions()->count());
    }
}
