<?php

use App\Filament\Resources\ManagedEnvironmentResource\Pages\ViewManagedEnvironment;
use App\Http\Controllers\RbacDelegatedAuthController;
use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Models\ManagedEnvironment;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Support\Providers\ProviderConnectionType;
use App\Support\Providers\ProviderConsentStatus;
use App\Support\Providers\ProviderVerificationStatus;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function () {
    Cache::flush();
    config()->set('tenantpilot.features.conditional_access', false);
});

function tenantWithApp(): ManagedEnvironment
{
    $tenant = ManagedEnvironment::create([
        'managed_environment_id' => 'tenant-guid',
        'name' => 'ManagedEnvironment One',
        'app_client_id' => 'client-123',
        'app_client_secret' => 'secret',
        'status' => 'active',
    ]);

    $connection = ProviderConnection::factory()->dedicated()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'is_default' => true,
        'is_enabled' => true,
        'connection_type' => ProviderConnectionType::Dedicated->value,
        'consent_status' => ProviderConsentStatus::Granted->value,
        'consent_granted_at' => now(),
        'consent_last_checked_at' => now(),
        'verification_status' => ProviderVerificationStatus::Healthy->value,
        'last_health_check_at' => now(),
    ]);

    ProviderCredential::factory()->create([
        'provider_connection_id' => $connection->getKey(),
        'payload' => [
            'client_id' => 'client-123',
            'client_secret' => 'secret',
        ],
    ]);

    return $tenant;
}

test('rbac action prompts login when no delegated token', function () {
    $tenant = tenantWithApp();
    [$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
        ->mountAction('setup_rbac')
        ->setActionData([
            'role_definition_id' => 'role-1',
            'role_display_name' => 'Policy and Profile Manager',
            'scope' => 'all_devices',
            'group_mode' => 'create',
        ])
        ->callMountedAction()
        ->assertHasNoActionErrors();

    expect(Cache::get(RbacDelegatedAuthController::cacheKey($tenant, $user->id, session()->getId())))->toBeNull();
});

test('rbac action succeeds and clears token cache', function () {
    $tenant = tenantWithApp();
    [$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    $cacheKey = RbacDelegatedAuthController::cacheKey($tenant, $user->id, null);
    Cache::put($cacheKey, 'delegated-token', now()->addMinutes(5));

    app()->bind(GraphClientInterface::class, function () {
        return new class implements GraphClientInterface
        {
            public function listPolicies(string $policyType, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getOrganization(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getServicePrincipalPermissions(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function request(string $method, string $path, array $options = []): GraphResponse
            {
                $path = ltrim($path, '/');

                $filter = $options['query']['$filter'] ?? '';

                if ($method === 'GET' && $path === 'servicePrincipals') {
                    return new GraphResponse(true, ['value' => [['id' => 'sp-1']]]);
                }

                if ($method === 'GET' && $path === 'groups' && str_contains($filter, 'displayName eq')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'POST' && $path === 'groups') {
                    return new GraphResponse(true, ['id' => 'group-1']);
                }

                if ($method === 'POST' && str_contains($path, '/members/$ref')) {
                    return new GraphResponse(true, []);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleDefinitions') {
                    return new GraphResponse(true, ['value' => [['id' => 'role-1', 'displayName' => 'Policy and Profile Manager']]]);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'POST' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['id' => 'assign-1']);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceConfigurations')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceCompliancePolicies')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                return new GraphResponse(true, ['value' => []]);
            }
        };
    });

    Livewire::test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
        ->mountAction('setup_rbac')
        ->setActionData([
            'role_definition_id' => 'role-1',
            'role_display_name' => 'Policy and Profile Manager',
            'scope' => 'all_devices',
            'group_mode' => 'create',
        ])
        ->callMountedAction()
        ->assertHasNoActionErrors();

    $tenant->refresh();

    expect($tenant->rbac_group_id)->toBe('group-1');
    expect($tenant->rbac_role_assignment_id)->toBe('assign-1');
    expect($tenant->rbac_canary_results)->toMatchArray([
        'deviceConfigurations' => 'ok',
        'deviceCompliancePolicies' => 'ok',
    ]);
    expect($tenant->rbac_last_warnings)->toContain('ca_canary_disabled');
    expect(Cache::has($cacheKey))->toBeFalse();
});

test('rbac action is idempotent on rerun', function () {
    $tenant = tenantWithApp();
    [$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    $cacheKey = RbacDelegatedAuthController::cacheKey($tenant, $user->id, null);
    Cache::put($cacheKey, 'delegated-token', now()->addMinutes(5));

    app()->bind(GraphClientInterface::class, function () {
        return new class implements GraphClientInterface
        {
            public function listPolicies(string $policyType, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getOrganization(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getServicePrincipalPermissions(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function request(string $method, string $path, array $options = []): GraphResponse
            {
                $path = ltrim($path, '/');

                if ($method === 'GET' && $path === 'servicePrincipals') {
                    return new GraphResponse(true, ['value' => [['id' => 'sp-1']]]);
                }

                if ($method === 'POST' && str_contains($path, '/members/$ref')) {
                    return new GraphResponse(false, [], 400, [
                        [
                            'error' => [
                                'code' => 'Request_BadRequest',
                                'message' => 'One or more added object references already exist for the following modified objects',
                            ],
                        ],
                    ]);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleDefinitions') {
                    return new GraphResponse(true, ['value' => [['id' => 'role-1', 'displayName' => 'Policy and Profile Manager']]]);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['value' => [[
                        'id' => 'assign-1',
                        'members' => ['group-1'],
                        'resourceScopes' => ['/'],
                        'roleDefinition' => ['id' => 'role-1'],
                    ]]]);
                }

                if ($method === 'PATCH' && str_contains($path, 'deviceManagement/roleAssignments/assign-1')) {
                    return new GraphResponse(true, ['id' => 'assign-1']);
                }

                if ($method === 'GET' && str_starts_with($path, 'groups/')) {
                    $id = str_replace('groups/', '', $path);

                    return new GraphResponse(true, ['id' => $id, 'displayName' => "Group {$id}"]);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceConfigurations')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceCompliancePolicies')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                return new GraphResponse(true, ['value' => []]);
            }
        };
    });

    Livewire::test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
        ->mountAction('setup_rbac')
        ->setActionData([
            'role_definition_id' => 'role-1',
            'role_display_name' => 'Policy and Profile Manager',
            'scope' => 'scope_group',
            'scope_group_id' => 'group-scope',
            'group_mode' => 'existing',
            'existing_group_id' => 'group-1',
        ])
        ->callMountedAction()
        ->assertHasNoActionErrors();

    $tenant->refresh();
    expect($tenant->rbac_group_id)->toBe('group-1');
    expect($tenant->rbac_role_assignment_id)->toBe('assign-1');
    expect($tenant->rbac_scope_mode)->toBe('scope_group');
    expect($tenant->rbac_last_warnings)->toContain('scope_limited');
    expect($tenant->rbac_status)->toBe('ok');
});

test('existing group membership error from Graph json payload is treated idempotently', function () {
    $tenant = tenantWithApp();
    [$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    $cacheKey = RbacDelegatedAuthController::cacheKey($tenant, $user->id, null);
    Cache::put($cacheKey, 'delegated-token', now()->addMinutes(5));

    app()->bind(GraphClientInterface::class, function () {
        return new class implements GraphClientInterface
        {
            public function listPolicies(string $policyType, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getOrganization(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getServicePrincipalPermissions(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function request(string $method, string $path, array $options = []): GraphResponse
            {
                $path = ltrim($path, '/');

                if ($method === 'GET' && $path === 'servicePrincipals') {
                    return new GraphResponse(true, ['value' => [['id' => 'sp-1']]]);
                }

                if ($method === 'GET' && $path === 'groups' && str_contains($options['query']['$filter'] ?? '', 'displayName')) {
                    return new GraphResponse(true, ['value' => [['id' => 'group-1', 'displayName' => 'Existing Group']]]);
                }

                if ($method === 'POST' && str_contains($path, '/members/$ref')) {
                    return new GraphResponse(false, [], 400, [
                        [
                            'error' => [
                                'code' => 'Request_BadRequest',
                                'message' => 'One or more added object references already exist for the following modified objects',
                            ],
                        ],
                    ]);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleDefinitions') {
                    return new GraphResponse(true, ['value' => [['id' => 'role-1', 'displayName' => 'Policy and Profile Manager']]]);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'POST' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['id' => 'assign-1']);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceConfigurations')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceCompliancePolicies')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                return new GraphResponse(true, ['value' => []]);
            }
        };
    });

    Livewire::test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
        ->mountAction('setup_rbac')
        ->setActionData([
            'role_definition_id' => 'role-1',
            'role_display_name' => 'Policy and Profile Manager',
            'scope' => 'all_devices',
            'group_mode' => 'existing',
            'existing_group_id' => 'group-1',
        ])
        ->callMountedAction()
        ->assertHasNoActionErrors();

    $tenant->refresh();
    expect($tenant->rbac_group_id)->toBe('group-1');
    expect($tenant->rbac_role_assignment_id)->toBe('assign-1');
    expect($tenant->rbac_status)->toBe('ok');
});

test('group picker is disabled without delegated token', function () {
    $tenant = tenantWithApp();
    [$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
        ->mountAction('setup_rbac')
        ->setActionData([
            'group_mode' => 'existing',
        ])
        ->assertFormFieldDisabled('existing_group_id');

    expect(\App\Filament\Resources\ManagedEnvironmentResource::groupSearchHelper($tenant))->toBe('Login to search groups');
});

test('group picker toggles when switching modes', function () {
    $tenant = tenantWithApp();
    [$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    Livewire::test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
        ->mountAction('setup_rbac')
        ->setActionData([
            'group_mode' => 'existing',
        ])
        ->assertFormFieldVisible('existing_group_id')
        ->assertFormFieldHidden('group_name');
});

test('delegated group search returns options and persists selection', function () {
    $tenant = tenantWithApp();
    [$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    $cacheKey = RbacDelegatedAuthController::cacheKey($tenant, $user->id, null);
    Cache::put($cacheKey, 'delegated-token', now()->addMinutes(5));

    app()->bind(GraphClientInterface::class, function () {
        return new class implements GraphClientInterface
        {
            public function listPolicies(string $policyType, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getOrganization(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getServicePrincipalPermissions(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function request(string $method, string $path, array $options = []): GraphResponse
            {
                $path = ltrim($path, '/');

                $filter = $options['query']['$filter'] ?? '';

                if ($method === 'GET' && $path === 'servicePrincipals') {
                    return new GraphResponse(true, ['value' => [['id' => 'sp-1']]]);
                }

                if ($method === 'GET' && $path === 'groups' && str_contains($filter, 'securityEnabled eq true')) {
                    return new GraphResponse(true, ['value' => [
                        ['id' => 'group-123', 'displayName' => 'Ops Team'],
                        ['id' => 'group-456', 'displayName' => 'Helpdesk'],
                    ]]);
                }

                if ($method === 'GET' && str_starts_with($path, 'groups/')) {
                    $id = str_replace('groups/', '', $path);

                    return new GraphResponse(true, ['id' => $id, 'displayName' => 'Ops Team']);
                }

                if ($method === 'POST' && str_contains($path, '/members/$ref')) {
                    return new GraphResponse(true, []);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleDefinitions') {
                    return new GraphResponse(true, ['value' => [['id' => 'role-1', 'displayName' => 'Policy and Profile Manager']]]);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'POST' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['id' => 'assign-1']);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceConfigurations')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceCompliancePolicies')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                return new GraphResponse(true, ['value' => []]);
            }
        };
    });

    $options = \App\Filament\Resources\ManagedEnvironmentResource::groupSearchOptions($tenant, 'Ops');

    expect($options)->toHaveKey('group-123');
    expect($options['group-123'])->toContain('Ops Team');

    Livewire::test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
        ->mountAction('setup_rbac')
        ->setActionData([
            'group_mode' => 'existing',
            'role_definition_id' => 'role-1',
            'role_display_name' => 'Policy and Profile Manager',
            'scope' => 'all_devices',
            'existing_group_id' => 'group-123',
        ])
        ->assertFormFieldEnabled('existing_group_id')
        ->callMountedAction()
        ->assertHasNoActionErrors();

    $tenant->refresh();

    expect($tenant->rbac_group_id)->toBe('group-123');
    expect($tenant->rbac_role_assignment_id)->toBe('assign-1');
    expect(Cache::has($cacheKey))->toBeFalse();
});

test('delegated role search returns options and persists role definition id', function () {
    $tenant = tenantWithApp();
    [$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
    $this->actingAs($user);
    Filament::setTenant($tenant, true);

    $cacheKey = RbacDelegatedAuthController::cacheKey($tenant, $user->id, null);
    Cache::put($cacheKey, 'delegated-token', now()->addMinutes(5));

    app()->bind(GraphClientInterface::class, function () {
        return new class implements GraphClientInterface
        {
            public function listPolicies(string $policyType, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getOrganization(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function getServicePrincipalPermissions(array $options = []): GraphResponse
            {
                return new GraphResponse(true, []);
            }

            public function request(string $method, string $path, array $options = []): GraphResponse
            {
                $filter = $options['query']['$filter'] ?? '';

                if ($method === 'GET' && $path === 'deviceManagement/roleDefinitions') {
                    return new GraphResponse(true, ['value' => [
                        ['id' => 'role-1', 'displayName' => 'Policy and Profile Manager'],
                        ['id' => 'role-2', 'displayName' => 'Helpdesk Operator'],
                    ]]);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleDefinitions/role-1') {
                    return new GraphResponse(true, ['id' => 'role-1', 'displayName' => 'Policy and Profile Manager']);
                }

                if ($method === 'GET' && $path === 'servicePrincipals') {
                    return new GraphResponse(true, ['value' => [['id' => 'sp-1']]]);
                }

                if ($method === 'GET' && $path === 'groups' && str_contains($filter, 'displayName eq')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'POST' && $path === 'groups') {
                    return new GraphResponse(true, ['id' => 'group-1']);
                }

                if ($method === 'POST' && str_contains($path, '/members/$ref')) {
                    return new GraphResponse(true, []);
                }

                if ($method === 'GET' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'POST' && $path === 'deviceManagement/roleAssignments') {
                    return new GraphResponse(true, ['id' => 'assign-1']);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceConfigurations')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                if ($method === 'GET' && str_starts_with($path, 'deviceManagement/deviceCompliancePolicies')) {
                    return new GraphResponse(true, ['value' => []]);
                }

                return new GraphResponse(true, ['value' => []]);
            }
        };
    });

    $roles = \App\Filament\Resources\ManagedEnvironmentResource::roleSearchOptions($tenant, 'Policy');

    expect($roles)->toHaveKey('role-1');
    expect($roles['role-1'])->toContain('Policy and Profile Manager');

    Livewire::test(ViewManagedEnvironment::class, ['record' => $tenant->getRouteKey()])
        ->mountAction('setup_rbac')
        ->setActionData([
            'role_definition_id' => 'role-1',
            'role_display_name' => 'Policy and Profile Manager',
            'scope' => 'all_devices',
            'group_mode' => 'create',
        ])
        ->callMountedAction()
        ->assertHasNoActionErrors();

    $tenant->refresh();

    expect($tenant->rbac_role_definition_id)->toBe('role-1');
    expect($tenant->rbac_role_display_name)->toBe('Policy and Profile Manager');
    expect(Cache::has($cacheKey))->toBeFalse();
});
