<?php

declare(strict_types=1);

use App\Jobs\ProviderComplianceSnapshotJob;
use App\Jobs\ProviderInventorySyncJob;
use App\Jobs\RunBackupScheduleJob;
use App\Jobs\SyncPoliciesJob;
use App\Models\BackupSchedule;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\Workspace;
use App\Services\Auth\CapabilityResolver;
use App\Services\OperationRunService;
use App\Services\Providers\ProviderOperationStartGate;
use App\Support\Auth\Capabilities;

function runQueuedContractMatrixJobThroughMiddleware(object $job, Closure $terminal): mixed
{
    $pipeline = array_reduce(
        array_reverse($job->middleware()),
        fn (Closure $next, object $middleware): Closure => fn (object $job): mixed => $middleware->handle($job, $next),
        $terminal,
    );

    return $pipeline($job);
}

it('blocks provider execution families consistently when provider capability is lost', function (string $operationType, string $jobClass): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $connection = ProviderConnection::factory()->platform()->consentGranted()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => fake()->uuid(),
        'consent_status' => 'granted',
    ]);

    $run = app(OperationRunService::class)->ensureRun(
        tenant: $tenant,
        type: $operationType,
        inputs: [
            'provider_connection_id' => (int) $connection->getKey(),
            'required_capability' => Capabilities::PROVIDER_RUN,
        ],
        initiator: $user,
    );

    $job = new $jobClass(
        tenantId: (int) $tenant->getKey(),
        userId: (int) $user->getKey(),
        providerConnectionId: (int) $connection->getKey(),
        operationRun: $run,
    );

    $user->workspaces()->updateExistingPivot((int) $tenant->workspace_id, ['role' => 'readonly']);
    app(CapabilityResolver::class)->clearCache();
    app(\App\Services\Auth\WorkspaceCapabilityResolver::class)->clearCache();

    $terminalInvoked = false;

    runQueuedContractMatrixJobThroughMiddleware(
        $job,
        function () use (&$terminalInvoked): string {
            $terminalInvoked = true;

            return 'ran';
        },
    );

    $run->refresh();

    expect($terminalInvoked)->toBeFalse()
        ->and($run->outcome)->toBe('blocked')
        ->and($run->context['reason_code'] ?? null)->toBe('missing_capability')
        ->and($run->context['execution_legitimacy']['metadata']['required_capability'] ?? null)->toBe(Capabilities::PROVIDER_RUN);
})->with([
    'provider inventory sync' => ['inventory.sync', ProviderInventorySyncJob::class],
    'provider compliance snapshot' => ['compliance.snapshot', ProviderComplianceSnapshotJob::class],
]);

it('blocks policy sync before side effects when sync capability is lost', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $run = app(OperationRunService::class)->ensureRun(
        tenant: $tenant,
        type: 'policy.sync',
        inputs: [
            'scope' => 'all',
            'types' => ['deviceConfiguration'],
        ],
        initiator: $user,
    );

    $job = new SyncPoliciesJob(
        tenantId: (int) $tenant->getKey(),
        types: ['deviceConfiguration'],
        policyIds: null,
        operationRun: $run,
    );

    $user->workspaces()->updateExistingPivot((int) $tenant->workspace_id, ['role' => 'readonly']);
    app(CapabilityResolver::class)->clearCache();
    app(\App\Services\Auth\WorkspaceCapabilityResolver::class)->clearCache();

    $terminalInvoked = false;

    runQueuedContractMatrixJobThroughMiddleware(
        $job,
        function () use (&$terminalInvoked): string {
            $terminalInvoked = true;

            return 'ran';
        },
    );

    $run->refresh();

    expect($terminalInvoked)->toBeFalse()
        ->and($run->outcome)->toBe('blocked')
        ->and($run->context['reason_code'] ?? null)->toBe('missing_capability')
        ->and($run->context['execution_legitimacy']['metadata']['required_capability'] ?? null)->toBe(Capabilities::TENANT_SYNC);
});

it('blocks scheduled backup runs under system authority when tenant operability drifts', function (): void {
    [, $tenant] = createUserWithTenant(role: 'owner');

    $schedule = BackupSchedule::query()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'name' => 'Queued legitimacy contract',
        'is_enabled' => true,
        'timezone' => 'UTC',
        'frequency' => 'daily',
        'time_of_day' => '01:00:00',
        'days_of_week' => null,
        'policy_types' => ['deviceConfiguration'],
        'include_foundations' => true,
        'retention_keep_last' => 30,
        'next_run_at' => null,
    ]);

    $run = app(OperationRunService::class)->ensureRunWithIdentityStrict(
        tenant: $tenant,
        type: 'backup.schedule.execute',
        identityInputs: [
            'backup_schedule_id' => (int) $schedule->getKey(),
            'scheduled_for' => now()->toDateTimeString(),
        ],
        context: [
            'backup_schedule_id' => (int) $schedule->getKey(),
            'trigger' => 'scheduled',
        ],
    );

    $otherWorkspace = Workspace::factory()->create();
    $tenant->forceFill([
        'workspace_id' => (int) $otherWorkspace->getKey(),
    ])->save();

    $job = new RunBackupScheduleJob(operationRun: $run, backupScheduleId: (int) $schedule->getKey());
    $terminalInvoked = false;

    runQueuedContractMatrixJobThroughMiddleware(
        $job,
        function () use (&$terminalInvoked): string {
            $terminalInvoked = true;

            return 'ran';
        },
    );

    $run->refresh();

    expect($terminalInvoked)->toBeFalse()
        ->and($run->outcome)->toBe('blocked')
        ->and($run->context['reason_code'] ?? null)->toBe('workspace_mismatch')
        ->and($run->context['execution_legitimacy']['authority_mode'] ?? null)->toBe('system_authority');
});

it('stores canonical execution metadata in existing operation run context structures', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $policyRun = app(OperationRunService::class)->ensureRun(
        tenant: $tenant,
        type: 'policy.sync',
        inputs: [
            'scope' => 'all',
            'types' => ['deviceConfiguration'],
        ],
        initiator: $user,
    );

    $scheduleRun = app(OperationRunService::class)->ensureRunWithIdentityStrict(
        tenant: $tenant,
        type: 'backup.schedule.execute',
        identityInputs: [
            'backup_schedule_id' => 99,
            'scheduled_for' => now()->toDateTimeString(),
        ],
        context: [
            'backup_schedule_id' => 99,
            'trigger' => 'scheduled',
        ],
    );

    $connection = ProviderConnection::factory()->platform()->consentGranted()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => fake()->uuid(),
        'consent_status' => 'granted',
    ]);

    $result = app(ProviderOperationStartGate::class)->start(
        tenant: $tenant,
        connection: $connection,
        operationType: 'inventory.sync',
        dispatcher: static fn (OperationRun $run): null => null,
        initiator: $user,
    );

    expect($policyRun->context)->toMatchArray([
        'execution_authority_mode' => 'actor_bound',
        'required_capability' => Capabilities::TENANT_SYNC,
    ])
        ->and($scheduleRun->context)->toMatchArray([
            'execution_authority_mode' => 'system_authority',
            'required_capability' => Capabilities::TENANT_BACKUP_SCHEDULES_RUN,
        ])
        ->and($result->run->context)->toMatchArray([
            'execution_authority_mode' => 'actor_bound',
            'required_capability' => Capabilities::PROVIDER_RUN,
            'provider_connection_id' => (int) $connection->getKey(),
        ]);
});
