<?php

namespace Tests\Feature\Identity;

use App\Domain\Identity\Enums\PermissionScope;
use App\Domain\Identity\Models\Role;
use App\Domain\Identity\Models\User;
use App\Domain\Identity\Services\PermissionScopeResolver;
use Database\Seeders\PermissionSeeder;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class RoleManagementTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed([PermissionSeeder::class, RoleSeeder::class]);
    }

    private function admin(): User
    {
        return tap(User::factory()->create())->assignRole('super-admin');
    }

    private function agent(): User
    {
        return tap(User::factory()->create())->assignRole('agent');
    }

    public function test_an_administrator_can_list_roles(): void
    {
        $response = $this->actingAs($this->admin())->getJson('/api/v1/roles');

        $response->assertOk()
            ->assertJsonPath('success', true)
            ->assertJsonFragment(['name' => 'cashier'])
            ->assertJsonStructure(['data' => [['name', 'label', 'level', 'is_system', 'users_count']]]);
    }

    public function test_a_user_without_role_view_cannot_list_roles(): void
    {
        $this->actingAs($this->agent())->getJson('/api/v1/roles')
            ->assertStatus(403)
            ->assertJsonPath('success', false);
    }

    public function test_role_detail_exposes_the_scoped_permission_matrix(): void
    {
        $response = $this->actingAs($this->admin())->getJson('/api/v1/roles/cashier');

        $response->assertOk();
        $this->assertSame(PermissionScope::Branch->value, $response->json('data.permissions')['payment.create']);

        $this->assertArrayNotHasKey('payment.reverse', $response->json('data.permissions'));
    }

    public function test_an_administrator_can_create_a_role_with_scoped_grants(): void
    {
        $response = $this->actingAs($this->admin())->postJson('/api/v1/roles', [
            'label' => 'Regional Auditor',
            'description' => 'Reads everything in one branch.',
            'level' => 55,
            'permissions' => [
                'applicant.view' => 'branch',
                'payment.view' => 'branch',
            ],
        ]);

        $response->assertCreated()->assertJsonPath('data.name', 'regional-auditor');

        $role = Role::where('name', 'regional-auditor')->firstOrFail();
        $this->assertFalse($role->is_system);
        $this->assertSame(2, $role->permissionScopes()->count());

        $user = tap(User::factory()->create())->assignRole($role);
        $this->assertSame(
            PermissionScope::Branch,
            app(PermissionScopeResolver::class)->scopeFor($user, 'applicant.view')
        );
    }

    public function test_creating_a_role_rejects_unknown_permissions_and_scopes(): void
    {
        $this->actingAs($this->admin())->postJson('/api/v1/roles', [
            'label' => 'Broken Role',
            'permissions' => ['applicant.teleport' => 'branch'],
        ])->assertStatus(422)->assertJsonPath('success', false);

        $this->actingAs($this->admin())->postJson('/api/v1/roles', [
            'label' => 'Broken Role',
            'permissions' => ['applicant.view' => 'planet'],
        ])->assertStatus(422);

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

    public function test_updating_permissions_replaces_the_previous_grants(): void
    {
        $admin = $this->admin();
        $this->actingAs($admin)->postJson('/api/v1/roles', [
            'label' => 'Desk Officer',
            'permissions' => ['applicant.view' => 'branch', 'applicant.create' => 'branch'],
        ])->assertCreated();

        $response = $this->actingAs($admin)->patchJson('/api/v1/roles/desk-officer', [
            'permissions' => ['applicant.view' => 'own'],
        ])->assertOk();
        $this->assertSame('own', $response->json('data.permissions')['applicant.view']);

        $user = tap(User::factory()->create())->assignRole('desk-officer');
        $resolver = app(PermissionScopeResolver::class);

        $this->assertSame(PermissionScope::Own, $resolver->scopeFor($user, 'applicant.view'));
        $this->assertNull($resolver->scopeFor($user, 'applicant.create'));
    }

    public function test_a_role_can_be_deactivated_and_stops_granting_access(): void
    {
        $admin = $this->admin();
        $user = $this->agent();

        $this->assertTrue($user->can('applicant.view'));

        $this->actingAs($admin)->patchJson('/api/v1/roles/agent', ['is_active' => false])->assertOk();

        $this->assertNull(app(PermissionScopeResolver::class)->scopeFor($user->fresh(), 'applicant.view'));
    }

    public function test_a_role_can_be_cloned_with_its_grants(): void
    {
        $response = $this->actingAs($this->admin())
            ->postJson('/api/v1/roles/cashier/clone', ['label' => 'Senior Cashier']);

        $response->assertCreated()->assertJsonPath('data.name', 'senior-cashier');

        $clone = Role::where('name', 'senior-cashier')->firstOrFail();
        $original = Role::where('name', 'cashier')->firstOrFail();

        $this->assertSame(
            $original->permissionScopes()->count(),
            $clone->permissionScopes()->count(),
        );
        $this->assertFalse($clone->is_system);
    }

    public function test_a_system_role_cannot_be_deleted(): void
    {
        $this->actingAs($this->admin())->deleteJson('/api/v1/roles/cashier')->assertStatus(403);

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

    public function test_a_role_in_use_cannot_be_deleted(): void
    {
        $admin = $this->admin();
        $this->actingAs($admin)->postJson('/api/v1/roles', ['label' => 'Temp Role'])->assertCreated();

        tap(User::factory()->create())->assignRole('temp-role');

        $this->actingAs($admin)->deleteJson('/api/v1/roles/temp-role')->assertStatus(403);
    }

    public function test_an_unused_custom_role_can_be_deleted(): void
    {
        $admin = $this->admin();
        $this->actingAs($admin)->postJson('/api/v1/roles', [
            'label' => 'Temp Role',
            'permissions' => ['applicant.view' => 'own'],
        ])->assertCreated();

        $this->actingAs($admin)->deleteJson('/api/v1/roles/temp-role')->assertOk();

        $this->assertDatabaseMissing('roles', ['name' => 'temp-role']);
        $this->assertSame(0, \DB::table('role_permission_scopes')->whereNotIn(
            'role_id', \DB::table('roles')->pluck('id')
        )->count());
    }

    public function test_users_can_be_attached_to_and_detached_from_a_role(): void
    {
        $admin = $this->admin();
        $user = User::factory()->create();

        $this->actingAs($admin)->postJson('/api/v1/roles/cashier/users', [
            'action' => 'attach',
            'users' => [$user->uuid],
        ])->assertOk()->assertJsonPath('data.affected', 1);

        $this->assertTrue($user->fresh()->hasRole('cashier'));
        $this->actingAs($admin)->getJson('/api/v1/roles/cashier/users')
            ->assertOk()
            ->assertJsonPath('data.0.email', $user->email);

        $this->actingAs($admin)->postJson('/api/v1/roles/cashier/users', [
            'action' => 'detach',
            'users' => [$user->uuid],
        ])->assertOk();

        $this->assertFalse($user->fresh()->hasRole('cashier'));
    }

    public function test_the_permission_matrix_lists_modules_and_scopes(): void
    {
        $response = $this->actingAs($this->admin())->getJson('/api/v1/permissions');

        $response->assertOk()
            ->assertJsonStructure(['data' => [
                'modules' => [['module', 'label', 'permissions' => [['name', 'action', 'label']]]],
                'scopes' => [['value', 'label', 'rank']],
            ]]);

        $this->assertCount(6, $response->json('data.scopes'));
    }

    public function test_role_endpoints_require_authentication(): void
    {
        $this->getJson('/api/v1/roles')->assertStatus(401);
        $this->getJson('/api/v1/permissions')->assertStatus(401);
    }
}
