<?php

use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Models\ManagedEnvironmentPermission;
use App\Models\ManagedEnvironment;
use App\Services\Providers\ProviderOperationStartGate;
use App\Support\Auth\Capabilities;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Providers\ProviderReasonCodes;
use App\Support\Verification\ManagedEnvironmentPermissionCheckClusters;
use App\Support\Verification\VerificationReportSchema;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

if (! function_exists('providerOperationStartGateSeedRequirementRows')) {
    function providerOperationStartGateSeedRequirementRows(ManagedEnvironment $tenant, array $requirementKeys): void
    {
        $permissions = array_merge(
            config('intune_permissions.permissions', []),
            config('entra_permissions.permissions', []),
        );

        foreach ($permissions as $permission) {
            if (! is_array($permission)) {
                continue;
            }

            if (array_intersect($requirementKeys, ManagedEnvironmentPermissionCheckClusters::requirementKeysForPermissionRow($permission)) === []) {
                continue;
            }

            ManagedEnvironmentPermission::query()->updateOrCreate([
                'managed_environment_id' => (int) $tenant->getKey(),
                'permission_key' => (string) ($permission['key'] ?? ''),
                'workspace_id' => (int) $tenant->workspace_id,
            ], [
                'status' => 'granted',
                'details' => ['source' => 'provider-operation-start-gate-test'],
                'last_checked_at' => now(),
            ]);
        }
    }
}

it('starts a provider operation and dispatches the job once', function (): void {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'entra-tenant-id',
    ]);
    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => 'entra-tenant-id',
        'consent_status' => 'granted',
    ]);
    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
    ]);

    $dispatched = 0;

    $gate = app(ProviderOperationStartGate::class);

    $result = $gate->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'provider.connection.check',
        dispatcher: function (OperationRun $run) use (&$dispatched): void {
            $dispatched++;
            expect($run->type)->toBe('provider.connection.check');
        },
    );

    expect($dispatched)->toBe(1);
    expect($result->status)->toBe('started');
    expect($result->dispatched)->toBeTrue();

    $run = $result->run->fresh();
    expect($run)->not->toBeNull();
    expect($run->type)->toBe('provider.connection.check');
    expect($run->status)->toBe('queued');

    expect($run->context)->toMatchArray([
        'provider' => 'microsoft',
        'module' => 'health_check',
        'provider_connection_id' => (int) $connection->getKey(),
    ]);
    expect($run->context['provider_context'] ?? [])->toMatchArray([
        'provider' => 'microsoft',
    ]);
    expect($run->context['target_scope'] ?? [])->toMatchArray([
        'provider' => 'microsoft',
        'scope_kind' => 'tenant',
        'scope_identifier' => 'entra-tenant-id',
    ])->not->toHaveKey('entra_tenant_id');
    expect($run->context['provider_binding']['provider'] ?? null)->toBe('microsoft')
        ->and($run->context['provider_binding']['binding_status'] ?? null)->toBe('active');
});

it('dedupes when the same operation is already active for the scope', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'consent_status' => 'granted',
    ]);
    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
    ]);

    $existing = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'type' => 'provider.connection.check',
        'status' => 'running',
        'context' => [
            'provider_connection_id' => (int) $connection->getKey(),
        ],
    ]);

    $dispatched = 0;
    $gate = app(ProviderOperationStartGate::class);

    $result = $gate->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'provider.connection.check',
        dispatcher: function () use (&$dispatched): void {
            $dispatched++;
        },
    );

    expect($dispatched)->toBe(0);
    expect($result->status)->toBe('deduped');
    expect($result->run->getKey())->toBe($existing->getKey());
    expect(OperationRun::query()->where('managed_environment_id', $tenant->getKey())->count())->toBe(1);
});

it('blocks when a different operation is already active for the scope', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'consent_status' => 'granted',
    ]);
    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
    ]);

    $blocking = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'type' => 'inventory.sync',
        'status' => 'queued',
        'context' => [
            'provider_connection_id' => (int) $connection->getKey(),
        ],
    ]);

    $dispatched = 0;
    $gate = app(ProviderOperationStartGate::class);

    $result = $gate->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'provider.connection.check',
        dispatcher: function () use (&$dispatched): void {
            $dispatched++;
        },
    );

    expect($dispatched)->toBe(0);
    expect($result->status)->toBe('scope_busy');
    expect($result->run->getKey())->toBe($blocking->getKey());
    expect(OperationRun::query()->where('managed_environment_id', $tenant->getKey())->count())->toBe(1);
});

it('returns blocked and stores reason metadata when no default connection exists', function (): void {
    $tenant = ManagedEnvironment::factory()->create();

    $dispatched = 0;
    $gate = app(ProviderOperationStartGate::class);

    $result = $gate->start(
        tenant: $tenant,
        connection: null,
        operationType: 'provider.connection.check',
        dispatcher: function () use (&$dispatched): void {
            $dispatched++;
        },
    );

    expect($dispatched)->toBe(0);
    expect($result->status)->toBe('blocked');
    expect($result->dispatched)->toBeFalse();
    expect($result->run->status)->toBe(OperationRunStatus::Completed->value);
    expect($result->run->outcome)->toBe(OperationRunOutcome::Blocked->value);
    expect($result->run->context['reason_code'] ?? null)->toBe(ProviderReasonCodes::ProviderConnectionMissing);
    expect($result->run->context['next_steps'] ?? [])->not->toBeEmpty();
    expect($result->run->context['verification_report'] ?? null)->toBeArray();
    expect(VerificationReportSchema::isValidReport($result->run->context['verification_report'] ?? []))->toBeTrue();
});

it('starts restore execution with explicit provider connection binding and operation capability metadata', function (): void {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'restore-entra-tenant-id',
    ]);
    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => 'restore-entra-tenant-id',
        'consent_status' => 'granted',
    ]);
    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
    ]);
    providerOperationStartGateSeedRequirementRows($tenant, ['permissions.intune_configuration', 'permissions.intune_rbac_assignments']);

    $dispatched = 0;
    $gate = app(ProviderOperationStartGate::class);

    $result = $gate->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'restore.execute',
        dispatcher: function (OperationRun $run) use (&$dispatched): void {
            $dispatched++;

            expect($run->type)->toBe('restore.execute');
        },
    );

    expect($dispatched)->toBe(1);
    expect($result->status)->toBe('started');
    expect($result->dispatched)->toBeTrue();

    $run = $result->run->fresh();

    expect($run)->not->toBeNull();
    expect($run->context)->toMatchArray([
        'provider_connection_id' => (int) $connection->getKey(),
        'required_capability' => Capabilities::TENANT_MANAGE,
    ]);
    expect($run->context['target_scope'] ?? [])->toMatchArray([
        'provider' => 'microsoft',
        'scope_kind' => 'tenant',
        'scope_identifier' => 'restore-entra-tenant-id',
    ])->not->toHaveKey('entra_tenant_id');
});

it('starts directory group sync with explicit provider connection binding and sync capability metadata', function (): void {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'directory-entra-tenant-id',
    ]);
    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => 'directory-entra-tenant-id',
        'consent_status' => 'granted',
    ]);
    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
    ]);
    providerOperationStartGateSeedRequirementRows($tenant, ['permissions.directory_groups']);

    $dispatched = 0;
    $gate = app(ProviderOperationStartGate::class);

    $result = $gate->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'directory.groups.sync',
        dispatcher: function (OperationRun $run) use (&$dispatched): void {
            $dispatched++;

            expect($run->type)->toBe('directory.groups.sync');
        },
    );

    expect($dispatched)->toBe(1);
    expect($result->status)->toBe('started');
    expect($result->dispatched)->toBeTrue();

    $run = $result->run->fresh();

    expect($run)->not->toBeNull();
    expect($run->context)->toMatchArray([
        'provider_connection_id' => (int) $connection->getKey(),
        'required_capability' => Capabilities::TENANT_SYNC,
    ]);
    expect($run->context['target_scope'] ?? [])->toMatchArray([
        'provider' => 'microsoft',
        'scope_kind' => 'tenant',
        'scope_identifier' => 'directory-entra-tenant-id',
    ])->not->toHaveKey('entra_tenant_id');
});

it('treats onboarding bootstrap provider starts as one protected scope', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'consent_status' => 'granted',
    ]);
    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
    ]);

    $blocking = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'type' => 'inventory.sync',
        'status' => 'running',
        'context' => [
            'provider_connection_id' => (int) $connection->getKey(),
        ],
    ]);

    $dispatched = 0;
    $gate = app(ProviderOperationStartGate::class);

    $result = $gate->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'compliance.snapshot',
        dispatcher: function () use (&$dispatched): void {
            $dispatched++;
        },
    );

    expect($dispatched)->toBe(0);
    expect($result->status)->toBe('scope_busy');
    expect($result->run->getKey())->toBe($blocking->getKey());
});

it('rejects legacy aliases before starting provider operations', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => 'directory-entra-tenant-id',
        'consent_status' => 'granted',
    ]);
    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
    ]);

    $gate = app(ProviderOperationStartGate::class);

    expect(fn () => $gate->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'entra_group_sync',
        dispatcher: fn () => null,
    ))->toThrow(\InvalidArgumentException::class);
});

it('blocks provider starts when no explicit provider binding supports the connection provider', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'contoso',
        'entra_tenant_id' => 'contoso-tenant-id',
        'consent_status' => 'granted',
    ]);
    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
    ]);

    $dispatched = 0;
    $gate = app(ProviderOperationStartGate::class);

    $result = $gate->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'provider.connection.check',
        dispatcher: function () use (&$dispatched): void {
            $dispatched++;
        },
    );

    expect($dispatched)->toBe(0);
    expect($result->status)->toBe('blocked');
    expect($result->run->context)->toMatchArray([
        'provider' => 'contoso',
        'module' => 'health_check',
        'reason_code' => ProviderReasonCodes::ProviderBindingUnsupported,
        'reason_code_extension' => 'ext.provider_binding_missing',
    ]);
    expect($result->run->context['provider_binding']['provider'] ?? null)->toBe('contoso')
        ->and($result->run->context['provider_binding']['binding_status'] ?? null)->toBe('unsupported');
});
