<?php

declare(strict_types=1);

use App\Models\User;
use Database\Seeders\RoleAndPermissionSeeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

beforeEach(function (): void {
    $this->seed(RoleAndPermissionSeeder::class);
    $this->adminUrl = 'http://'.config('app.domains.admin');
});

// ---------------------------------------------------------------------------
// Roles & Permissions Seeder
// ---------------------------------------------------------------------------
describe('Roles & Permissions Setup', function (): void {
    it('creates all expected granular permissions', function (): void {
        $expected = [
            'customers.view', 'customers.edit', 'customers.create', 'customers.delete',
            'customers.suspend', 'customers.impersonate',
            'services.view', 'services.suspend', 'services.terminate', 'services.provision', 'services.extend',
            'invoices.view', 'invoices.create', 'invoices.edit', 'invoices.void', 'invoices.refund',
            'tickets.view', 'tickets.reply', 'tickets.assign', 'tickets.close', 'tickets.delete',
            'reports.view', 'reports.export',
            'settings.view', 'settings.edit',
            'audit_logs.view', 'audit_logs.export',
            'plans.view', 'plans.create', 'plans.edit', 'plans.delete',
            'coupons.view', 'coupons.create', 'coupons.edit', 'coupons.delete',
            'credit_notes.view', 'credit_notes.create', 'credit_notes.void',
            'debit_notes.view', 'debit_notes.create', 'debit_notes.void',
            'affiliates.view', 'affiliates.approve', 'affiliates.payout',
            'staff.view', 'staff.manage',
        ];

        foreach ($expected as $permName) {
            expect(Permission::findByName($permName, 'web'))->not->toBeNull();
        }
    });

    it('creates all expected roles', function (): void {
        $expectedRoles = ['admin', 'customer', 'super_admin', 'billing_admin', 'support_agent', 'support_lead', 'readonly_admin'];

        foreach ($expectedRoles as $roleName) {
            expect(Role::findByName($roleName, 'web'))->not->toBeNull();
        }
    });

    it('gives admin role all permissions', function (): void {
        $admin = Role::findByName('admin', 'web');
        $allPerms = Permission::where('guard_name', 'web')->count();

        expect($admin->permissions->count())->toBe($allPerms);
    });

    it('gives super_admin role all permissions', function (): void {
        $superAdmin = Role::findByName('super_admin', 'web');
        $allPerms = Permission::where('guard_name', 'web')->count();

        expect($superAdmin->permissions->count())->toBe($allPerms);
    });

    it('gives billing_admin the correct permissions', function (): void {
        $role = Role::findByName('billing_admin', 'web');
        $permissions = $role->permissions->pluck('name')->toArray();

        expect($permissions)
            ->toContain('customers.view', 'customers.edit', 'invoices.view', 'invoices.create',
                'invoices.edit', 'invoices.void', 'invoices.refund', 'services.view',
                'services.suspend', 'reports.view', 'reports.export', 'plans.view')
            ->not->toContain('staff.manage', 'customers.delete', 'customers.impersonate');
    });

    it('gives support_agent only limited permissions', function (): void {
        $role = Role::findByName('support_agent', 'web');
        $permissions = $role->permissions->pluck('name')->toArray();

        expect($permissions)
            ->toContain('customers.view', 'tickets.view', 'tickets.reply', 'services.view')
            ->not->toContain('tickets.assign', 'tickets.close', 'tickets.delete', 'customers.edit');
    });

    it('gives readonly_admin only view permissions', function (): void {
        $role = Role::findByName('readonly_admin', 'web');
        $permissions = $role->permissions->pluck('name')->toArray();

        foreach ($permissions as $perm) {
            expect($perm)->toEndWith('.view');
        }
    });
});

// ---------------------------------------------------------------------------
// Permission-based Route Access
// ---------------------------------------------------------------------------
describe('Permission-based Access Control', function (): void {
    it('allows support_agent to view tickets', function (): void {
        $agent = User::factory()->create();
        $agent->assignRole('support_agent');

        $this->actingAs($agent)
            ->get($this->adminUrl.'/tickets')
            ->assertOk();
    });

    it('blocks support_agent from viewing settings', function (): void {
        $agent = User::factory()->create();
        $agent->assignRole('support_agent');

        $this->actingAs($agent)
            ->get($this->adminUrl.'/settings')
            ->assertForbidden();
    });

    it('blocks support_agent from accessing customers edit', function (): void {
        $agent = User::factory()->create();
        $agent->assignRole('support_agent');
        $customer = User::factory()->customer()->create();

        $this->actingAs($agent)
            ->get($this->adminUrl.'/customers/'.$customer->id.'/edit')
            ->assertForbidden();
    });

    it('allows support_lead to view and edit customers', function (): void {
        $lead = User::factory()->create();
        $lead->assignRole('support_lead');
        $customer = User::factory()->customer()->create();

        $this->actingAs($lead)
            ->get($this->adminUrl.'/customers/'.$customer->id)
            ->assertOk();
    });

    it('allows billing_admin to access invoices', function (): void {
        $billingAdmin = User::factory()->create();
        $billingAdmin->assignRole('billing_admin');

        $this->actingAs($billingAdmin)
            ->get($this->adminUrl.'/invoices')
            ->assertOk();
    });

    it('blocks billing_admin from managing staff', function (): void {
        $billingAdmin = User::factory()->create();
        $billingAdmin->assignRole('billing_admin');

        $this->actingAs($billingAdmin)
            ->get($this->adminUrl.'/staff/roles')
            ->assertForbidden();
    });

    it('allows readonly_admin to view but not edit', function (): void {
        $readonly = User::factory()->create();
        $readonly->assignRole('readonly_admin');

        $this->actingAs($readonly)
            ->get($this->adminUrl.'/customers')
            ->assertOk();

        $this->actingAs($readonly)
            ->get($this->adminUrl.'/settings')
            ->assertOk();
    });

    it('blocks readonly_admin from editing settings', function (): void {
        $readonly = User::factory()->create();
        $readonly->assignRole('readonly_admin');

        $this->actingAs($readonly)
            ->put($this->adminUrl.'/settings', ['group' => 'general', 'company_name' => 'Test'])
            ->assertForbidden();
    });
});

// ---------------------------------------------------------------------------
// Staff Index Page
// ---------------------------------------------------------------------------
describe('Staff Management', function (): void {
    it('lists staff members on index page', function (): void {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->get($this->adminUrl.'/staff')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Staff/Index')
                ->has('staff')
                ->has('roles')
                ->has('filters')
            );
    });

    it('shows staff member details', function (): void {
        $admin = User::factory()->admin()->create();
        $staffMember = User::factory()->create();
        $staffMember->assignRole('support_agent');

        $this->actingAs($admin)
            ->get($this->adminUrl.'/staff/'.$staffMember->id)
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Staff/Show')
                ->has('staffMember')
                ->has('allPermissions')
                ->has('directPermissions')
                ->has('loginHistories')
            );
    });

    it('filters staff by role', function (): void {
        $admin = User::factory()->admin()->create();
        $agent = User::factory()->create();
        $agent->assignRole('support_agent');

        $this->actingAs($admin)
            ->get($this->adminUrl.'/staff?role=support_agent')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Staff/Index')
                ->where('filters.role', 'support_agent')
            );
    });
});

// ---------------------------------------------------------------------------
// Role Management
// ---------------------------------------------------------------------------
describe('Role Management', function (): void {
    it('lists all roles on index page', function (): void {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->get($this->adminUrl.'/staff/roles')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Staff/Roles/Index')
                ->has('roles')
            );
    });

    it('shows create role form with permission groups', function (): void {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->get($this->adminUrl.'/staff/roles/create')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Staff/Roles/Create')
                ->has('permissionGroups')
            );
    });

    it('creates a new role with permissions', function (): void {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->post($this->adminUrl.'/staff/roles', [
                'name' => 'custom_role',
                'permissions' => ['customers.view', 'tickets.view', 'tickets.reply'],
            ])
            ->assertRedirect();

        $role = Role::findByName('custom_role', 'web');
        expect($role)->not->toBeNull();
        expect($role->permissions->pluck('name')->toArray())
            ->toContain('customers.view', 'tickets.view', 'tickets.reply')
            ->toHaveCount(3);
    });

    it('validates role name format', function (): void {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->post($this->adminUrl.'/staff/roles', [
                'name' => 'Invalid Role Name!',
                'permissions' => ['customers.view'],
            ])
            ->assertSessionHasErrors('name');
    });

    it('shows edit role form with current permissions', function (): void {
        $admin = User::factory()->admin()->create();
        $role = Role::findByName('support_agent', 'web');

        $this->actingAs($admin)
            ->get($this->adminUrl.'/staff/roles/'.$role->id.'/edit')
            ->assertOk()
            ->assertInertia(fn ($page) => $page
                ->component('Admin/Staff/Roles/Edit')
                ->has('role')
                ->has('permissionGroups')
                ->has('usersCount')
            );
    });

    it('updates role permissions', function (): void {
        $admin = User::factory()->admin()->create();
        $role = Role::create(['name' => 'test_role', 'guard_name' => 'web']);
        $role->syncPermissions(['customers.view']);

        $this->actingAs($admin)
            ->put($this->adminUrl.'/staff/roles/'.$role->id, [
                'name' => 'test_role',
                'permissions' => ['customers.view', 'customers.edit', 'services.view'],
            ])
            ->assertRedirect();

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

    it('prevents deletion of built-in roles', function (): void {
        $admin = User::factory()->admin()->create();
        $role = Role::findByName('admin', 'web');

        $this->actingAs($admin)
            ->delete($this->adminUrl.'/staff/roles/'.$role->id)
            ->assertRedirect()
            ->assertSessionHas('error');
    });

    it('prevents deletion of roles with users', function (): void {
        $admin = User::factory()->admin()->create();
        $role = Role::create(['name' => 'temp_role', 'guard_name' => 'web']);
        $role->syncPermissions(['customers.view']);

        $user = User::factory()->create();
        $user->assignRole('temp_role');

        $this->actingAs($admin)
            ->delete($this->adminUrl.'/staff/roles/'.$role->id)
            ->assertRedirect()
            ->assertSessionHas('error');

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

    it('deletes a custom role with no users', function (): void {
        $admin = User::factory()->admin()->create();
        $role = Role::create(['name' => 'deletable_role', 'guard_name' => 'web']);
        $role->syncPermissions(['customers.view']);

        $this->actingAs($admin)
            ->delete($this->adminUrl.'/staff/roles/'.$role->id)
            ->assertRedirect()
            ->assertSessionHas('success');

        expect(Role::where('name', 'deletable_role')->exists())->toBeFalse();
    });

    it('prevents protected role name from being changed', function (): void {
        $admin = User::factory()->admin()->create();
        $role = Role::findByName('super_admin', 'web');

        $this->actingAs($admin)
            ->put($this->adminUrl.'/staff/roles/'.$role->id, [
                'name' => 'renamed_super',
                'permissions' => ['customers.view'],
            ])
            ->assertRedirect();

        // Name should NOT have changed
        expect(Role::find($role->id)->name)->toBe('super_admin');
    });

    it('logs role creation in audit log', function (): void {
        $admin = User::factory()->admin()->create();

        $this->actingAs($admin)
            ->post($this->adminUrl.'/staff/roles', [
                'name' => 'audited_role',
                'permissions' => ['customers.view'],
            ])
            ->assertRedirect();

        $this->assertDatabaseHas('audit_logs', [
            'admin_id' => $admin->id,
            'action' => 'role_created',
            'resource_type' => 'role',
        ]);
    });
});

// ---------------------------------------------------------------------------
// User Model
// ---------------------------------------------------------------------------
describe('User Admin Check', function (): void {
    it('recognizes all admin-like roles via isAdmin()', function (): void {
        $roles = ['admin', 'super_admin', 'billing_admin', 'support_agent', 'support_lead', 'readonly_admin'];

        foreach ($roles as $roleName) {
            $user = User::factory()->create();
            $user->assignRole($roleName);

            expect($user->isAdmin())->toBeTrue("User with role {$roleName} should be admin");
        }
    });

    it('recognizes only admin and super_admin via isSuperAdmin()', function (): void {
        $superRoles = ['admin', 'super_admin'];
        $nonSuperRoles = ['billing_admin', 'support_agent', 'support_lead', 'readonly_admin'];

        foreach ($superRoles as $roleName) {
            $user = User::factory()->create();
            $user->assignRole($roleName);
            expect($user->isSuperAdmin())->toBeTrue("User with role {$roleName} should be super admin");
        }

        foreach ($nonSuperRoles as $roleName) {
            $user = User::factory()->create();
            $user->assignRole($roleName);
            expect($user->isSuperAdmin())->toBeFalse("User with role {$roleName} should NOT be super admin");
        }
    });
});
