<?php

namespace Tests\Feature\Identity;

use App\Domain\Identity\Enums\PermissionScope;
use App\Domain\Identity\Models\User;
use App\Domain\Identity\Services\PermissionRegistry;
use App\Domain\Identity\Services\PermissionScopeResolver;
use App\Domain\Identity\Services\RoleRegistry;
use Database\Seeders\PermissionSeeder;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Spatie\Permission\Models\Permission;
use Tests\TestCase;

class RolePermissionSeedingTest extends TestCase
{
    use RefreshDatabase;

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

    private function resolver(): PermissionScopeResolver
    {
        return app(PermissionScopeResolver::class);
    }

    public function test_every_registered_permission_is_seeded(): void
    {
        $this->assertSame(count(PermissionRegistry::names()), Permission::count());
        $this->assertDatabaseHas('permissions', ['name' => 'payment.confirm', 'module' => 'payment']);
    }

    public function test_seeding_is_idempotent(): void
    {
        $this->seed([PermissionSeeder::class, RoleSeeder::class]);

        $this->assertSame(count(PermissionRegistry::names()), Permission::count());
        $this->assertSame(count(RoleRegistry::roles()), DB::table('roles')->count());
    }

    public function test_a_super_admin_resolves_every_permission_at_global_scope(): void
    {
        $user = User::factory()->create();
        $user->assignRole('super-admin');

        $resolver = $this->resolver();

        $this->assertTrue($resolver->allows($user, 'accounting.journal_post', PermissionScope::Global));
        $this->assertSame(PermissionScope::Global, $resolver->scopeFor($user, 'applicant.view'));
    }

    public function test_an_agent_only_sees_their_own_applicants(): void
    {
        $user = User::factory()->create();
        $user->assignRole('agent');

        $resolver = $this->resolver();

        $this->assertSame(PermissionScope::Own, $resolver->scopeFor($user, 'applicant.view'));
        $this->assertFalse($resolver->allows($user, 'applicant.view', PermissionScope::Branch));
        $this->assertNull($resolver->scopeFor($user, 'document.verify'));
        $this->assertNull($resolver->scopeFor($user, 'payment.confirm'));
    }

    public function test_multiple_roles_union_to_the_widest_scope(): void
    {
        $user = User::factory()->create();
        $user->assignRole(['agent', 'branch-manager']);

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

    public function test_an_inactive_role_grants_nothing(): void
    {
        $user = User::factory()->create();
        $user->assignRole('branch-manager');
        DB::table('roles')->where('name', 'branch-manager')->update(['is_active' => false]);

        $this->assertNull($this->resolver()->scopeFor($user, 'applicant.view'));
    }

    public function test_a_user_override_can_widen_or_deny_a_permission(): void
    {
        $user = User::factory()->create();
        $user->assignRole('agent');

        DB::table('user_permission_overrides')->insert([
            [
                'user_id' => $user->id,
                'permission_id' => Permission::where('name', 'applicant.view')->value('id'),
                'effect' => 'grant',
                'scope' => PermissionScope::Branch->value,
                'created_at' => now(), 'updated_at' => now(),
            ],
            [
                'user_id' => $user->id,
                'permission_id' => Permission::where('name', 'applicant.create')->value('id'),
                'effect' => 'deny',
                'scope' => null,
                'created_at' => now(), 'updated_at' => now(),
            ],
        ]);

        $resolver = $this->resolver();

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

    public function test_an_expired_override_is_ignored(): void
    {
        $user = User::factory()->create();
        $user->assignRole('agent');

        DB::table('user_permission_overrides')->insert([
            'user_id' => $user->id,
            'permission_id' => Permission::where('name', 'applicant.create')->value('id'),
            'effect' => 'deny',
            'scope' => null,
            'expires_at' => now()->subDay(),
            'created_at' => now(), 'updated_at' => now(),
        ]);

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

    public function test_a_cashier_cannot_reverse_or_refund_payments(): void
    {
        $user = User::factory()->create();
        $user->assignRole('cashier');

        $resolver = $this->resolver();

        $this->assertSame(PermissionScope::Branch, $resolver->scopeFor($user, 'payment.create'));
        $this->assertNull($resolver->scopeFor($user, 'payment.reverse'));
        $this->assertNull($resolver->scopeFor($user, 'payment.refund'));
    }

    public function test_an_auditor_has_no_write_permissions(): void
    {
        $user = User::factory()->create();
        $user->assignRole('auditor');

        $scopes = $this->resolver()->allScopes($user);
        $writes = array_filter(array_keys($scopes), fn (string $p) => (bool) preg_match(
            '/\.(create|update|delete|post|pay|approve|reverse|refund|confirm|verify|close|calculate)$/', $p
        ));

        $this->assertSame([], array_values($writes));
    }
}
