<?php

declare(strict_types=1);

use Builder\Core\Extensions\Resources\Roles\RoleResource;
use Builder\Core\Foundation\Registry;
use Builder\Core\Tests\BuilderTestUser;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Inertia\Testing\AssertableInertia;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Spatie\Permission\PermissionRegistrar;

beforeEach(function (): void {
    Schema::create('users', function ($table): void {
        $table->id();
        $table->string('name')->nullable();
        $table->string('email')->nullable()->unique();
        $table->string('password')->nullable();
        $table->json('user_widget_preferences')->nullable();
        $table->rememberToken();
        $table->timestamps();
    });

    Schema::create('roles', function ($table): void {
        $table->id();
        $table->string('name');
        $table->string('guard_name')->default('web');
        $table->boolean('locked')->default(false);
        $table->timestamps();
        $table->unique(['name', 'guard_name']);
    });

    Schema::create('permissions', function ($table): void {
        $table->id();
        $table->string('name');
        $table->string('guard_name')->default('web');
        $table->timestamps();
        $table->unique(['name', 'guard_name']);
    });

    Schema::create('role_has_permissions', function ($table): void {
        $table->foreignId('permission_id');
        $table->foreignId('role_id');
        $table->primary(['permission_id', 'role_id']);
    });

    Schema::create('model_has_roles', function ($table): void {
        $table->foreignId('role_id');
        $table->string('model_type');
        $table->unsignedBigInteger('model_id');
        $table->index(['model_id', 'model_type']);
        $table->primary(['role_id', 'model_id', 'model_type']);
    });

    Schema::create('model_has_permissions', function ($table): void {
        $table->foreignId('permission_id');
        $table->string('model_type');
        $table->unsignedBigInteger('model_id');
        $table->index(['model_id', 'model_type']);
        $table->primary(['permission_id', 'model_id', 'model_type']);
    });

    config([
        'auth.providers.users.model' => BuilderTestUser::class,
        'permission.table_names' => [
            'roles' => 'roles',
            'permissions' => 'permissions',
            'model_has_permissions' => 'model_has_permissions',
            'model_has_roles' => 'model_has_roles',
            'role_has_permissions' => 'role_has_permissions',
        ],
        'permission.column_names' => [
            'role_pivot_key' => 'role_id',
            'permission_pivot_key' => 'permission_id',
            'model_morph_key' => 'model_id',
            'team_foreign_key' => 'team_id',
        ],
        'permission.teams' => false,
        'permission.cache' => [
            'expiration_time' => DateInterval::createFromDateString('24 hours'),
            'key' => 'spatie.permission.cache',
            'store' => 'array',
        ],
    ]);

    Schema::create('revisions', function ($table): void {
        $table->id();
        $table->string('revisable_type');
        $table->unsignedBigInteger('revisable_id');
        $table->unsignedBigInteger('user_id')->nullable();
        $table->json('revision_data');
        $table->timestamps();
    });

    app(PermissionRegistrar::class)->forgetCachedPermissions();

    app(Registry::class)->resource(RoleResource::class);
});

it('RoleResource is registered via BuilderServiceProvider', function (): void {
    $registry = app(Registry::class);

    expect($registry->resolveBySlug('roles'))->not->toBeNull();
});

it('renders role index page', function (): void {
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->get(route('builder.resource.index', 'roles'))
        ->assertInertia(fn (AssertableInertia $page): AssertableInertia => $page->component('Resource/Index'));
});

it('renders role create page', function (): void {
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->get(route('builder.resource.create', 'roles'))
        ->assertInertia(fn (AssertableInertia $page): AssertableInertia => $page->component('Resource/Form'));
});

it('stores a new role', function (): void {
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->post(route('builder.resource.store', 'roles'), [
            'name' => 'editor',
        ])
        ->assertRedirect(route('builder.resource.index', 'roles'));

    expect(Role::where('name', 'editor')->exists())->toBeTrue();
});

it('store validates required fields', function (): void {
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

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

it('store enforces unique name per guard', function (): void {
    Role::create(['name' => 'editor', 'guard_name' => 'web']);
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

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

it('renders role edit page', function (): void {
    $role = Role::create(['name' => 'editor', 'guard_name' => 'web']);
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->get(route('builder.resource.edit', ['resource' => 'roles', 'id' => $role->id]))
        ->assertInertia(fn (AssertableInertia $page): AssertableInertia => $page->component('Resource/Form'));
});

it('updates a role name', function (): void {
    $role = Role::create(['name' => 'old-name', 'guard_name' => 'web']);
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->put(route('builder.resource.update', ['resource' => 'roles', 'id' => $role->id]), [
            'name' => 'new-name',
        ])
        ->assertRedirect(route('builder.resource.edit', ['resource' => 'roles', 'id' => $role->id]));

    $role->refresh();
    expect($role->name)->toBe('new-name');
});

it('deletes a role', function (): void {
    $role = Role::create(['name' => 'to-delete', 'guard_name' => 'web']);
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->delete(route('builder.resource.destroy', ['resource' => 'roles', 'id' => $role->id]))
        ->assertRedirect(route('builder.resource.index', 'roles'));

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

it('syncs permissions on role create', function (): void {
    $perm = Permission::create(['name' => 'view-users', 'guard_name' => 'web']);
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->post(route('builder.resource.store', 'roles'), [
            'name' => 'editor',
            'permissions' => ['view-users'],
        ])
        ->assertRedirect(route('builder.resource.index', 'roles'));

    $role = Role::where('name', 'editor')->firstOrFail();
    expect($role->permissions->pluck('name')->toArray())->toContain('view-users');
});

it('syncs permissions on role update', function (): void {
    $perm = Permission::create(['name' => 'edit-users', 'guard_name' => 'web']);
    $role = Role::create(['name' => 'editor', 'guard_name' => 'web']);
    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'admin@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->put(route('builder.resource.update', ['resource' => 'roles', 'id' => $role->id]), [
            'name' => 'editor',
            'permissions' => ['edit-users'],
        ])
        ->assertRedirect(route('builder.resource.edit', ['resource' => 'roles', 'id' => $role->id]));

    $role->refresh();
    expect($role->permissions->pluck('name')->toArray())->toContain('edit-users');
});

it('delete of a locked role redirects with error toast and does not delete', function (): void {
    $role = Role::create(['name' => 'administrator', 'guard_name' => 'web']);
    DB::table('roles')->where('id', $role->id)->update(['locked' => true]);

    $admin = BuilderTestUser::create(['name' => 'Admin', 'email' => 'locked-del@test.com', 'password' => 'x']);

    $this->actingAs($admin)
        ->delete(route('builder.resource.destroy', ['resource' => 'roles', 'id' => $role->id]))
        ->assertRedirect();

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