<?php

use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use Illuminate\Foundation\Testing\WithFaker;

uses(WithFaker::class);

function grantManageAccessRights(User $user): void
{
    $perm = Permission::firstOrCreate(['name' => 'manage access rights', 'guard_name' => 'web']);
    Permission::firstOrCreate(['name' => 'view access rights', 'guard_name' => 'web']);
    $user->givePermissionTo($perm);
}

test('guest cannot access roles index', function () {
    $this->get('/roles')->assertRedirect('/login');
});

test('authenticated user without permission gets 403 on roles index', function () {
    $user = User::factory()->create();
    $this->actingAs($user)->get('/roles')->assertForbidden();
});

test('user with manage access rights can store a new role', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);
    $p = Permission::firstOrCreate(['name' => 'view dashboard', 'guard_name' => 'web']);

    $response = $this->actingAs($user)
        ->postJson('/roles', [
            'name' => 'editor-role',
            'guard_name' => 'web',
            'permissions' => [$p->id],
        ]);

    $response->assertOk()->assertJson(['success' => true]);
    $this->assertDatabaseHas('roles', ['name' => 'editor-role']);

    $role = Role::where('name', 'editor-role')->first();
    expect($role->hasPermissionTo('view dashboard'))->toBeTrue();
});

test('store rejects duplicate role name', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);
    $p = Permission::firstOrCreate(['name' => 'view dashboard', 'guard_name' => 'web']);
    Role::create(['name' => 'dup-role', 'guard_name' => 'web']);

    $response = $this->actingAs($user)
        ->postJson('/roles', [
            'name' => 'dup-role',
            'guard_name' => 'web',
            'permissions' => [$p->id],
        ]);

    $response->assertStatus(422);
});

test('store rejects empty permissions array', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);

    $response = $this->actingAs($user)
        ->postJson('/roles', [
            'name' => 'no-perm-role',
            'guard_name' => 'web',
            'permissions' => [],
        ]);

    $response->assertStatus(422);
});

test('store rejects invalid characters in role name', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);
    $p = Permission::firstOrCreate(['name' => 'view dashboard', 'guard_name' => 'web']);

    $response = $this->actingAs($user)
        ->postJson('/roles', [
            'name' => 'role with space!',
            'guard_name' => 'web',
            'permissions' => [$p->id],
        ]);

    $response->assertStatus(422);
});

test('update replaces role permissions', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);
    $p1 = Permission::firstOrCreate(['name' => 'view dashboard', 'guard_name' => 'web']);
    $p2 = Permission::firstOrCreate(['name' => 'view user directory', 'guard_name' => 'web']);
    $role = Role::create(['name' => 'original-role', 'guard_name' => 'web']);
    $role->givePermissionTo('view dashboard');

    $response = $this->actingAs($user)
        ->putJson("/roles/{$role->id}", [
            'name' => 'renamed-role',
            'guard_name' => 'web',
            'permissions' => [$p2->id],
        ]);

    $response->assertOk();
    $role->refresh();
    expect($role->name)->toBe('renamed-role');
    expect($role->hasPermissionTo('view user directory'))->toBeTrue();
    expect($role->hasPermissionTo('view dashboard'))->toBeFalse();
});

test('toggleStatus activates and deactivates role', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);
    $role = Role::create(['name' => 'toggleable', 'guard_name' => 'web', 'is_active' => true]);

    $this->actingAs($user)
        ->postJson('/roles/toggle-status', ['id' => $role->id, 'status' => 'deactivate'])
        ->assertOk();
    expect((bool) $role->fresh()->is_active)->toBeFalse();

    $this->actingAs($user)
        ->postJson('/roles/toggle-status', ['id' => $role->id, 'status' => 'activate'])
        ->assertOk();
    expect((bool) $role->fresh()->is_active)->toBeTrue();
});

test('destroy soft deletes role when no users assigned', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);
    $role = Role::create(['name' => 'doomed', 'guard_name' => 'web']);

    $this->actingAs($user)
        ->deleteJson("/roles/{$role->id}")
        ->assertOk();

    expect(Role::withTrashed()->find($role->id)->trashed())->toBeTrue();
});

test('destroy blocks deletion when role is still assigned to a user', function () {
    $admin = User::factory()->create();
    grantManageAccessRights($admin);
    $role = Role::create(['name' => 'in-use', 'guard_name' => 'web']);
    $victim = User::factory()->create();
    $victim->assignRole($role);

    $this->actingAs($admin)
        ->deleteJson("/roles/{$role->id}")
        ->assertStatus(422)
        ->assertJson(['success' => false]);

    expect(Role::find($role->id))->not->toBeNull();
});

test('restore brings back a soft-deleted role', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);
    $role = Role::create(['name' => 'restorable', 'guard_name' => 'web']);
    $role->delete();

    $this->actingAs($user)
        ->postJson("/roles/{$role->id}/restore")
        ->assertOk();

    expect(Role::find($role->id)->trashed())->toBeFalse();
});

test('forceDelete permanently removes role', function () {
    $user = User::factory()->create();
    grantManageAccessRights($user);
    $role = Role::create(['name' => 'gone-forever', 'guard_name' => 'web']);
    $role->delete();

    $this->actingAs($user)
        ->deleteJson("/roles/{$role->id}/force")
        ->assertOk();

    expect(Role::withTrashed()->find($role->id))->toBeNull();
});

test('forceDelete blocks when role has users assigned', function () {
    $admin = User::factory()->create();
    grantManageAccessRights($admin);
    $role = Role::create(['name' => 'sticky', 'guard_name' => 'web']);
    $victim = User::factory()->create();
    $victim->assignRole($role);

    $this->actingAs($admin)
        ->deleteJson("/roles/{$role->id}/force")
        ->assertStatus(422);

    expect(Role::find($role->id))->not->toBeNull();
});
