<?php

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;

uses(RefreshDatabase::class);

beforeEach(function () {
    app()[PermissionRegistrar::class]->forgetCachedPermissions();

    $modules = [
        'user' => ['view', 'create', 'update', 'delete', 'toggle-active', 'reset-password'],
        'category' => ['view', 'create', 'update', 'delete'],
        'supplier' => ['view', 'create', 'update', 'delete'],
        'customer' => ['view', 'create', 'update', 'delete'],
        'cash-account' => ['view', 'deposit', 'withdrawal', 'update', 'delete'],
        'expense' => ['view', 'create', 'update', 'delete'],
        'employee-advance' => ['view', 'create', 'update', 'delete', 'approve', 'pay'],
        'payroll-period' => ['view', 'show', 'current', 'close', 'reopen'],
        'payroll' => ['pay', 'cancel'],
        'payroll-adjustment' => ['create', 'delete'],
        'leave-request' => ['view', 'create', 'update', 'delete', 'approve', 'reject'],
        'attendance' => ['view', 'check-in', 'check-out', 'by-date'],
        'settings' => ['view', 'update-system', 'update-homepage', 'update-social-media', 'update-hr'],
    ];

    foreach ($modules as $module => $actions) {
        foreach ($actions as $action) {
            Permission::create(['name' => "{$module}.{$action}"]);
        }
    }

    $allPermissions = Permission::all()->pluck('name')->toArray();

    $developer = Role::create(['name' => 'Developer']);
    $developer->syncPermissions($allPermissions);
});

function createUserWithRole(string $roleName = 'Developer'): User
{
    $user = User::factory()->create();
    $user->assignRole($roleName);

    return $user;
}

/*
|--------------------------------------------------------------------------
| AUTHENTICATION
|--------------------------------------------------------------------------
*/

test('guests are redirected to the login page', function () {
    $response = $this->get(route('admin.settings.roles.index'));
    $response->assertRedirect(route('login'));
});

test('authenticated users without permission cannot visit the role index page', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->get(route('admin.settings.roles.index'));
    $response->assertForbidden();
});

test('authenticated users with permission can visit the role index page', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->get(route('admin.settings.roles.index'));
    $response->assertOk();
});

/*
|--------------------------------------------------------------------------
| INDEX PAGE
|--------------------------------------------------------------------------
*/

test('role index page displays roles', function () {
    $this->actingAs(createUserWithRole());

    Role::create(['name' => 'Manager']);
    Role::create(['name' => 'Staff']);

    $response = $this->get(route('admin.settings.roles.index'));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/roles/index')
        ->has('roles.data', 3)
        ->where('roles.total', 3)
        ->where('roles.current_page', 1)
        ->where('roles.per_page', 25)
    );
});

test('index page works with zero additional roles', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->get(route('admin.settings.roles.index'));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/roles/index')
        ->has('roles.data', 1)
        ->where('roles.total', 1)
        ->where('roles.current_page', 1)
        ->where('roles.per_page', 25)
    );
});

test('index page displays correct role count after delete', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'To Delete']);

    $this->delete(route('admin.settings.roles.destroy', $role));

    $response = $this->get(route('admin.settings.roles.index'));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/roles/index')
        ->has('roles.data', 1)
        ->where('roles.total', 1)
        ->where('roles.current_page', 1)
        ->where('roles.per_page', 25)
    );
});

/*
|--------------------------------------------------------------------------
| CREATE / STORE
|--------------------------------------------------------------------------
*/

test('authenticated users without permission cannot visit the create page', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->get(route('admin.settings.roles.create'));
    $response->assertForbidden();
});

test('authenticated users with permission can visit the create page', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->get(route('admin.settings.roles.create'));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/roles/create')
        ->has('permissions')
    );
});

test('role can be created', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'Manager',
        'permissions' => ['user.view', 'user.create'],
    ]);

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.settings.roles.index'));

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

test('role name is required', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => '',
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasErrors('name');
});

test('role name must be string', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 12345,
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasErrors('name');
});

test('role name must not exceed 100 characters', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => str_repeat('a', 101),
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasErrors('name');
});

test('role name exactly 100 characters passes validation', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => str_repeat('a', 100),
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasNoErrors();
});

test('role name must be unique', function () {
    $this->actingAs(createUserWithRole());

    Role::create(['name' => 'Existing Role']);

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'Existing Role',
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasErrors('name');
});

test('role name uniqueness is case sensitive', function () {
    $this->actingAs(createUserWithRole());

    Role::create(['name' => 'Manager']);

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'Manager',
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasErrors('name');
});

test('role name with special characters is accepted', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'Admin & Staff!',
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasNoErrors();
    $this->assertDatabaseHas('roles', ['name' => 'Admin & Staff!']);
});

test('permissions are required', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'New Role',
    ]);

    $response->assertSessionHasErrors('permissions');
});

test('permissions must be an array', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'New Role',
        'permissions' => 'user.view',
    ]);

    $response->assertSessionHasErrors('permissions');
});

test('permissions must exist in database', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'New Role',
        'permissions' => ['nonexistent.permission'],
    ]);

    $response->assertSessionHasErrors('permissions.0');
});

test('role can be created with multiple permissions', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'Multi Permission Role',
        'permissions' => ['user.view', 'user.create', 'category.view'],
    ]);

    $response->assertSessionHasNoErrors();

    $role = Role::where('name', 'Multi Permission Role')->first();
    expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['category.view', 'user.create', 'user.view']);
});

test('role can be created with empty permissions array', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'No Permission Role',
        'permissions' => [],
    ]);

    $response->assertSessionHasNoErrors();

    $role = Role::where('name', 'No Permission Role')->first();
    expect($role->permissions->count())->toBe(0);
});

test('store flashes success toast via Inertia', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'Toast Role',
        'permissions' => ['user.view'],
    ]);

    $response->assertRedirect();
});

test('store creates new role in database', function () {
    $this->actingAs(createUserWithRole());

    $this->post(route('admin.settings.roles.store'), [
        'name' => 'Database Check',
        'permissions' => ['user.view'],
    ]);

    $this->assertDatabaseCount('roles', 2);
    $this->assertDatabaseHas('roles', ['name' => 'Database Check']);
});

test('multiple roles can be created sequentially', function () {
    $this->actingAs(createUserWithRole());

    $this->post(route('admin.settings.roles.store'), ['name' => 'Role 1', 'permissions' => ['user.view']]);
    $this->post(route('admin.settings.roles.store'), ['name' => 'Role 2', 'permissions' => ['user.view']]);
    $this->post(route('admin.settings.roles.store'), ['name' => 'Role 3', 'permissions' => ['user.view']]);

    $this->assertDatabaseCount('roles', 4);
    $this->assertDatabaseHas('roles', ['name' => 'Role 1']);
    $this->assertDatabaseHas('roles', ['name' => 'Role 2']);
    $this->assertDatabaseHas('roles', ['name' => 'Role 3']);
});

/*
|--------------------------------------------------------------------------
| EDIT / UPDATE
|--------------------------------------------------------------------------
*/

test('authenticated users without permission cannot visit the edit page', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $role = Role::create(['name' => 'To Edit']);

    $response = $this->get(route('admin.settings.roles.edit', $role));
    $response->assertForbidden();
});

test('authenticated users with permission can visit the edit page', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'To Edit']);

    $response = $this->get(route('admin.settings.roles.edit', $role));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/roles/edit')
        ->has('role')
        ->has('permissions')
    );
});

test('role can be updated', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Old Name']);
    $role->syncPermissions(['user.view']);

    $response = $this->put(route('admin.settings.roles.update', $role), [
        'name' => 'New Name',
        'permissions' => ['user.view', 'user.create'],
    ]);

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.settings.roles.index'));

    $role->refresh();
    expect($role->name)->toBe('New Name');
    expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['user.create', 'user.view']);
});

test('role name can be updated to itself', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Same Name']);

    $response = $this->put(route('admin.settings.roles.update', $role), [
        'name' => 'Same Name',
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasNoErrors();
});

test('role update name must be unique excluding itself', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'First']);
    Role::create(['name' => 'Second']);

    $response = $this->put(route('admin.settings.roles.update', $role), [
        'name' => 'Second',
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasErrors('name');
});

test('role update name is required', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Existing']);

    $response = $this->put(route('admin.settings.roles.update', $role), [
        'name' => '',
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasErrors('name');
});

test('role update name must not exceed 100 characters', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Existing']);

    $response = $this->put(route('admin.settings.roles.update', $role), [
        'name' => str_repeat('a', 101),
        'permissions' => ['user.view'],
    ]);

    $response->assertSessionHasErrors('name');
});

test('role permissions can be updated', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Update Perm']);
    $role->syncPermissions(['user.view']);

    $this->put(route('admin.settings.roles.update', $role), [
        'name' => 'Update Perm',
        'permissions' => ['category.view', 'category.create'],
    ]);

    $role->refresh();
    expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['category.create', 'category.view']);
});

test('role can be updated multiple times', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Version 1']);

    $this->put(route('admin.settings.roles.update', $role), ['name' => 'Version 2', 'permissions' => ['user.view']]);
    $role->refresh();
    expect($role->name)->toBe('Version 2');

    $this->put(route('admin.settings.roles.update', $role), ['name' => 'Version 3', 'permissions' => ['user.view']]);
    $role->refresh();
    expect($role->name)->toBe('Version 3');
});

test('update flashes success toast via Inertia', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Toast']);

    $response = $this->put(route('admin.settings.roles.update', $role), [
        'name' => 'Updated Toast',
        'permissions' => ['user.view'],
    ]);

    $response->assertRedirect();
});

test('updating non-existent role returns 404', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->put(route('admin.settings.roles.update', 999999), [
        'name' => 'Ghost Role',
        'permissions' => ['user.view'],
    ]);

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

/*
|--------------------------------------------------------------------------
| DELETE / DESTROY
|--------------------------------------------------------------------------
*/

test('authenticated users without permission cannot delete roles', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $role = Role::create(['name' => 'To Delete']);

    $response = $this->delete(route('admin.settings.roles.destroy', $role));
    $response->assertForbidden();

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

test('role can be deleted', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'To Delete']);

    $response = $this->delete(route('admin.settings.roles.destroy', $role));

    $response
        ->assertSessionHasNoErrors()
        ->assertRedirect(route('admin.settings.roles.index'));

    $this->assertDatabaseMissing('roles', ['id' => $role->id]);
});

test('role permissions are removed when role is deleted', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'With Perms']);
    $role->syncPermissions(['user.view', 'user.create']);

    $this->delete(route('admin.settings.roles.destroy', $role));

    $this->assertDatabaseMissing('roles', ['id' => $role->id]);
    $this->assertDatabaseMissing('role_has_permissions', ['role_id' => $role->id]);
});

test('delete flashes success toast via Inertia', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Toast Delete']);

    $response = $this->delete(route('admin.settings.roles.destroy', $role));

    $response->assertRedirect();
});

test('deleting non-existent role returns 404', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->delete(route('admin.settings.roles.destroy', 999999));

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

/*
|--------------------------------------------------------------------------
| AUTHORIZATION - GUEST CANNOT PERFORM ACTIONS
|--------------------------------------------------------------------------
*/

test('guest cannot create role', function () {
    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'Unauthorized',
        'permissions' => ['user.view'],
    ]);

    $response->assertRedirect(route('login'));
    $this->assertDatabaseMissing('roles', ['name' => 'Unauthorized']);
});

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

    $response = $this->put(route('admin.settings.roles.update', $role), [
        'name' => 'Unauthorized Update',
        'permissions' => ['user.view'],
    ]);

    $response->assertRedirect(route('login'));
});

test('guest cannot delete role', function () {
    $role = Role::create(['name' => 'Existing']);

    $response = $this->delete(route('admin.settings.roles.destroy', $role));

    $response->assertRedirect(route('login'));
    $this->assertDatabaseHas('roles', ['id' => $role->id]);
});

/*
|--------------------------------------------------------------------------
| AUTHORIZATION - USER WITHOUT ROLE CANNOT PERFORM ACTIONS
|--------------------------------------------------------------------------
*/

test('user without developer role cannot create role', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $response = $this->post(route('admin.settings.roles.store'), [
        'name' => 'Unauthorized',
        'permissions' => ['user.view'],
    ]);

    $response->assertForbidden();
    $this->assertDatabaseMissing('roles', ['name' => 'Unauthorized']);
});

test('user without developer role cannot update role', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $role = Role::create(['name' => 'Existing']);

    $response = $this->put(route('admin.settings.roles.update', $role), [
        'name' => 'Unauthorized Update',
        'permissions' => ['user.view'],
    ]);

    $response->assertForbidden();
});

test('user without developer role cannot delete role', function () {
    $user = User::factory()->create();
    $this->actingAs($user);

    $role = Role::create(['name' => 'Existing']);

    $response = $this->delete(route('admin.settings.roles.destroy', $role));

    $response->assertForbidden();
    $this->assertDatabaseHas('roles', ['id' => $role->id]);
});

/*
|--------------------------------------------------------------------------
| DATA INTEGRITY
|--------------------------------------------------------------------------
*/

test('created role has correct timestamps', function () {
    $this->actingAs(createUserWithRole());

    $this->post(route('admin.settings.roles.store'), [
        'name' => 'Timestamp Role',
        'permissions' => ['user.view'],
    ]);

    $role = Role::where('name', 'Timestamp Role')->first();
    expect($role->created_at)->not->toBeNull();
    expect($role->updated_at)->not->toBeNull();
});

/*
|--------------------------------------------------------------------------
| PERMISSION ASSIGNMENT
|--------------------------------------------------------------------------
*/

test('role permissions are correctly synced', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Sync Test']);
    $role->syncPermissions(['user.view', 'user.create', 'category.view']);

    $role->refresh();
    expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['category.view', 'user.create', 'user.view']);
});

test('role permissions can be replaced', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Replace Test']);
    $role->syncPermissions(['user.view']);

    $role->syncPermissions(['category.view', 'category.create']);
    $role->refresh();

    expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['category.create', 'category.view']);
});

test('role can have all permissions', function () {
    $this->actingAs(createUserWithRole());

    $allPermissions = Permission::pluck('name')->toArray();

    $role = Role::create(['name' => 'Super Admin']);
    $role->syncPermissions($allPermissions);

    $role->refresh();
    expect($role->permissions->count())->toBe(count($allPermissions));
});

/*
|--------------------------------------------------------------------------
| REALISTIC USER SCENARIOS
|--------------------------------------------------------------------------
*/

test('user creates role then immediately edits it', function () {
    $this->actingAs(createUserWithRole());

    $this->post(route('admin.settings.roles.store'), [
        'name' => 'Draft Role',
        'permissions' => ['user.view'],
    ]);

    $role = Role::where('name', 'Draft Role')->first();

    $this->put(route('admin.settings.roles.update', $role), [
        'name' => 'Final Role',
        'permissions' => ['user.view', 'user.create'],
    ]);

    $role->refresh();
    expect($role->name)->toBe('Final Role');
    expect($role->permissions->pluck('name')->sort()->values()->toArray())->toEqual(['user.create', 'user.view']);
});

test('user creates multiple roles and deletes one', function () {
    $this->actingAs(createUserWithRole());

    $this->post(route('admin.settings.roles.store'), ['name' => 'Keep Role', 'permissions' => ['user.view']]);
    $this->post(route('admin.settings.roles.store'), ['name' => 'Delete Role', 'permissions' => ['user.view']]);
    $this->post(route('admin.settings.roles.store'), ['name' => 'Also Keep Role', 'permissions' => ['user.view']]);

    $toDelete = Role::where('name', 'Delete Role')->first();
    $this->delete(route('admin.settings.roles.destroy', $toDelete));

    $response = $this->get(route('admin.settings.roles.index'));
    $response->assertOk();
    $response->assertInertia(fn (Assert $page) => $page
        ->component('admin/roles/index')
        ->has('roles.data', 3)
        ->where('roles.total', 3)
        ->where('roles.current_page', 1)
        ->where('roles.per_page', 25)
    );
});

test('user tries to create role without submitting any data', function () {
    $this->actingAs(createUserWithRole());

    $response = $this->post(route('admin.settings.roles.store'), []);

    $response->assertSessionHasErrors(['name', 'permissions']);
});

test('user tries to update role without submitting any data', function () {
    $this->actingAs(createUserWithRole());

    $role = Role::create(['name' => 'Existing']);

    $response = $this->put(route('admin.settings.roles.update', $role), []);

    $response->assertSessionHasErrors(['name', 'permissions']);
});

test('user rapidly submits same role creation twice', function () {
    $this->actingAs(createUserWithRole());

    $this->post(route('admin.settings.roles.store'), ['name' => 'Rapid Submit', 'permissions' => ['user.view']]);
    $response = $this->post(route('admin.settings.roles.store'), ['name' => 'Rapid Submit', 'permissions' => ['user.view']]);

    $response->assertSessionHasErrors('name');
    $this->assertDatabaseCount('roles', 2);
});
