<?php

namespace Tests\Feature;

use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use App\Services\Authorization\AuthorizationAssignmentService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Spatie\Permission\Models\Permission as SpatiePermission;
use Spatie\Permission\Models\Role as SpatieRole;
use Spatie\Permission\Traits\HasRoles;
use Tests\TestCase;

class SpatiePermissionIntegrationTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(DatabaseSeeder::class);
    }

    public function test_spatie_is_the_only_runtime_authorization_assignment_model(): void
    {
        $this->assertTrue(config('permission.register_permission_check_method'));
        $this->assertSame(Role::class, config('permission.models.role'));
        $this->assertSame(Permission::class, config('permission.models.permission'));
        $this->assertSame(User::class, config('permission.models.default_model'));
        $this->assertFalse(config('permission.teams'));
        $this->assertFalse(config('permission.enable_wildcard_permission'));
        $this->assertContains(HasRoles::class, class_uses_recursive(User::class));
        $this->assertTrue(is_subclass_of(Role::class, SpatieRole::class));
        $this->assertTrue(is_subclass_of(Permission::class, SpatiePermission::class));
        $this->assertFalse(Schema::hasTable('role_user'));
        $this->assertFalse(Schema::hasTable('permission_role'));
        $this->assertSame(0, DB::table('model_has_permissions')->count());
    }

    public function test_gate_authorization_matches_canonical_role_permissions_for_system_roles(): void
    {
        $permissions = Permission::query()->orderBy('id')->get(['id', 'name']);
        $roles = Role::query()->where('is_system', true)->orderBy('id')->get();
        $assignments = app(AuthorizationAssignmentService::class);

        foreach ($roles as $role) {
            $user = User::factory()->create();
            $assignments->syncUserRoleIds($user, [$role->id]);
            $user = $user->fresh();

            $canonicalPermissionIds = DB::table('role_has_permissions')
                ->where('role_id', $role->id)
                ->pluck('permission_id')
                ->map(fn ($id) => (int) $id)
                ->all();

            foreach ($permissions as $permission) {
                $expected = $role->name === 'super-administrator'
                    || in_array((int) $permission->id, $canonicalPermissionIds, true);

                $this->assertSame(
                    $expected,
                    $user->can($permission->name),
                    "Gate mismatch for role {$role->name} and permission {$permission->name}",
                );
            }
        }
    }

    public function test_super_administrator_break_glass_gate_remains_intact(): void
    {
        $admin = User::query()->where('email', 'admin@microfinance.local')->firstOrFail();

        $this->assertTrue($admin->hasRole('super-administrator'));
        $this->assertTrue($admin->can('post-retirement.super-admin-gate-bypass'));
        $this->assertFalse(method_exists(User::class, 'hasPermission'));
    }

    public function test_assignment_service_writes_only_canonical_spatie_pivots(): void
    {
        $user = User::factory()->create();
        $role = Role::query()->create([
            'name' => 'post-retirement-test-role',
            'guard_name' => 'web',
            'display_name' => 'Post Retirement Test Role',
            'is_system' => false,
        ]);
        $permission = Permission::query()->where('name', 'customers.view')->firstOrFail();
        $assignments = app(AuthorizationAssignmentService::class);

        $assignments->syncRolePermissionIds($role, [$permission->id]);
        $assignments->syncUserRoleIds($user, [$role->id]);

        $this->assertDatabaseHas('role_has_permissions', [
            'role_id' => $role->id,
            'permission_id' => $permission->id,
        ]);
        $this->assertDatabaseHas('model_has_roles', [
            'role_id' => $role->id,
            'model_type' => User::class,
            'model_id' => $user->id,
        ]);
        $this->assertTrue($user->fresh()->can('customers.view'));
        $this->assertFalse(Schema::hasTable('role_user'));
        $this->assertFalse(Schema::hasTable('permission_role'));
    }
}
