<?php

use App\Enums\Permission as PermissionEnum;
use App\Enums\Role as RoleEnum;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Role;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->seed(RolePermissionSeeder::class);
});

// ─── Helper ───────────────────────────────────────────────

function createRoleUserWithPermission(PermissionEnum ...$permissions): User
{
    $user = User::factory()->create();

    $user->givePermissionTo(
        array_merge(
            [PermissionEnum::DASHBOARD_VIEW->value],
            array_map(fn (PermissionEnum $p) => $p->value, $permissions)
        )
    );

    $user->forgetCachedPermissions();

    return $user;
}

// ─── Index ────────────────────────────────────────────────

describe('Role Index', function () {
    test('authenticated user with permission can view role index', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);

        $this->actingAs($user)
            ->get(route('admin.system.roles.index'))
            ->assertOk();
    });

    test('guest is redirected to login', function () {
        $this->get(route('admin.system.roles.index'))
            ->assertRedirect(route('login'));
    });

    test('user without permission is forbidden', function () {
        $user = User::factory()->create();

        $this->actingAs($user)
            ->get(route('admin.system.roles.index'))
            ->assertForbidden();
    });

    test('index displays all seeded roles', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);

        $this->actingAs($user)
            ->get(route('admin.system.roles.index'))
            ->assertOk();
    });

    test('index can search roles by name', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);

        $this->actingAs($user)
            ->get(route('admin.system.roles.index', ['search' => 'developer']))
            ->assertOk();
    });
});

// ─── Create ───────────────────────────────────────────────

describe('Role Create', function () {
    test('authenticated user with permission can view create form', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);

        $this->actingAs($user)
            ->get(route('admin.system.roles.create'))
            ->assertOk();
    });

    test('guest is redirected to login', function () {
        $this->get(route('admin.system.roles.create'))
            ->assertRedirect(route('login'));
    });

    test('user without create permission is forbidden', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);

        $this->actingAs($user)
            ->get(route('admin.system.roles.create'))
            ->assertForbidden();
    });
});

// ─── Store ────────────────────────────────────────────────

describe('Role Store', function () {
    test('authenticated user with permission can create a role', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.system.roles.store'), [
                'name' => 'Custom Role',
                'permissions' => [PermissionEnum::DASHBOARD_VIEW->value, PermissionEnum::CATEGORIES_VIEW->value],
            ])
            ->assertRedirect(route('admin.system.roles.index'));

        $this->assertDatabaseHas('roles', [
            'name' => 'custom-role',
        ]);

        $role = Role::findByName('custom-role');
        expect($role->permissions->pluck('name')->toArray())
            ->toContain(PermissionEnum::DASHBOARD_VIEW->value, PermissionEnum::CATEGORIES_VIEW->value);
    });

    test('guest cannot create a role', function () {
        $this->post(route('admin.system.roles.store'), [
            'name' => 'New Role',
        ])->assertRedirect(route('login'));
    });

    test('user without create permission is forbidden', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);

        $this->actingAs($user)
            ->post(route('admin.system.roles.store'), [
                'name' => 'New Role',
            ])
            ->assertForbidden();
    });

    test('name is required', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.system.roles.store'), [
                'name' => '',
            ])
            ->assertSessionHasErrors('name');
    });

    test('name must not exceed 50 characters', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.system.roles.store'), [
                'name' => str_repeat('a', 51),
            ])
            ->assertSessionHasErrors('name');
    });

    test('name must be unique', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.system.roles.store'), [
                'name' => RoleEnum::DEVELOPER->value,
            ])
            ->assertSessionHasErrors('name');
    });

    test('name is slugified before saving', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.system.roles.store'), [
                'name' => 'My Custom Role',
            ])
            ->assertRedirect(route('admin.system.roles.index'));

        $this->assertDatabaseHas('roles', [
            'name' => 'my-custom-role',
        ]);
    });

    test('permissions must exist in database', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.system.roles.store'), [
                'name' => 'new-role',
                'permissions' => ['nonexistent.permission'],
            ])
            ->assertSessionHasErrors('permissions.0');
    });

    test('role can be created without permissions', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_CREATE);

        $this->actingAs($user)
            ->post(route('admin.system.roles.store'), [
                'name' => 'empty-role',
                'permissions' => [],
            ])
            ->assertRedirect(route('admin.system.roles.index'));

        $this->assertDatabaseHas('roles', [
            'name' => 'empty-role',
        ]);

        $role = Role::findByName('empty-role');
        expect($role->permissions)->toHaveCount(0);
    });
});

// ─── Edit ─────────────────────────────────────────────────

describe('Role Edit', function () {
    test('authenticated user with permission can view edit form', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::findByName(RoleEnum::ADMIN_TOKO->value);

        $this->actingAs($user)
            ->get(route('admin.system.roles.edit', $role))
            ->assertOk();
    });

    test('guest is redirected to login', function () {
        $role = Role::findByName(RoleEnum::ADMIN_TOKO->value);

        $this->get(route('admin.system.roles.edit', $role))
            ->assertRedirect(route('login'));
    });

    test('user without update permission is forbidden', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);

        $role = Role::findByName(RoleEnum::ADMIN_TOKO->value);

        $this->actingAs($user)
            ->get(route('admin.system.roles.edit', $role))
            ->assertForbidden();
    });

    test('edit page shows role data with permissions', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::findByName(RoleEnum::ADMIN_TOKO->value);

        $this->actingAs($user)
            ->get(route('admin.system.roles.edit', $role))
            ->assertOk();
    });

    test('protected roles are flagged as protected', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::findByName(RoleEnum::DEVELOPER->value);

        $this->actingAs($user)
            ->get(route('admin.system.roles.edit', $role))
            ->assertOk();
    });
});

// ─── Update ───────────────────────────────────────────────

describe('Role Update', function () {
    test('authenticated user with permission can update a role', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::create(['name' => 'updatable-role', 'guard_name' => 'web']);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => 'updated-role-name',
                'permissions' => [PermissionEnum::DASHBOARD_VIEW->value],
            ])
            ->assertRedirect(route('admin.system.roles.index'));

        $this->assertDatabaseHas('roles', [
            'id' => $role->id,
            'name' => 'updated-role-name',
        ]);

        $role->refresh();
        expect($role->permissions->pluck('name')->toArray())
            ->toContain(PermissionEnum::DASHBOARD_VIEW->value);
    });

    test('guest cannot update a role', function () {
        $role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);

        $this->put(route('admin.system.roles.update', $role), [
            'name' => 'new-name',
        ])->assertRedirect(route('login'));
    });

    test('user without update permission is forbidden', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW);

        $role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => 'new-name',
            ])
            ->assertForbidden();
    });

    test('name is required on update', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => '',
            ])
            ->assertSessionHasErrors('name');
    });

    test('name must be unique on update', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => RoleEnum::OWNER->value,
            ])
            ->assertSessionHasErrors('name');
    });

    test('name can stay the same on update', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::create(['name' => 'test-role', 'guard_name' => 'web']);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => 'test-role',
                'permissions' => [],
            ])
            ->assertRedirect(route('admin.system.roles.index'));
    });

    test('protected role name cannot be changed', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::findByName(RoleEnum::DEVELOPER->value);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => 'renamed-developer',
                'permissions' => $role->permissions->pluck('name')->toArray(),
            ])
            ->assertRedirect(route('admin.system.roles.index'));

        $this->assertDatabaseHas('roles', [
            'id' => $role->id,
            'name' => RoleEnum::DEVELOPER->value,
        ]);
    });

    test('owner role name cannot be changed', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::findByName(RoleEnum::OWNER->value);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => 'renamed-owner',
                'permissions' => $role->permissions->pluck('name')->toArray(),
            ])
            ->assertRedirect(route('admin.system.roles.index'));

        $this->assertDatabaseHas('roles', [
            'id' => $role->id,
            'name' => RoleEnum::OWNER->value,
        ]);
    });

    test('role permissions can be updated', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::create(['name' => 'perm-test-role', 'guard_name' => 'web']);
        $role->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => 'perm-test-role',
                'permissions' => [PermissionEnum::CATEGORIES_VIEW->value, PermissionEnum::ORDERS_VIEW->value],
            ])
            ->assertRedirect(route('admin.system.roles.index'));

        $role->refresh();
        $permissionNames = $role->permissions->pluck('name')->toArray();

        expect($permissionNames)->toContain(PermissionEnum::CATEGORIES_VIEW->value);
        expect($permissionNames)->toContain(PermissionEnum::ORDERS_VIEW->value);
        expect($permissionNames)->not->toContain(PermissionEnum::DASHBOARD_VIEW->value);
    });

    test('all permissions can be removed from role', function () {
        $user = createRoleUserWithPermission(PermissionEnum::ROLES_VIEW, PermissionEnum::ROLES_UPDATE);

        $role = Role::create(['name' => 'clear-perm-role', 'guard_name' => 'web']);
        $role->givePermissionTo(PermissionEnum::DASHBOARD_VIEW->value);

        $this->actingAs($user)
            ->put(route('admin.system.roles.update', $role), [
                'name' => 'clear-perm-role',
                'permissions' => [],
            ])
            ->assertRedirect(route('admin.system.roles.index'));

        $role->refresh();
        expect($role->permissions)->toHaveCount(0);
    });
});
