<?php

declare(strict_types=1);

use App\Jobs\Operations\BulkOperationWorkerJob;
use App\Models\OperationRun;
use App\Services\OperationRunService;
use App\Services\Operations\QueuedExecutionLegitimacyGate;
use App\Support\Operations\ExecutionAuthorityMode;
use App\Support\Operations\ExecutionDenialReasonCode;
use App\Support\Operations\QueuedExecutionContext;
use App\Support\Operations\QueuedExecutionLegitimacyDecision;

function runQueuedRetryJobThroughMiddleware(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('re-evaluates legitimacy on each bulk worker retry attempt', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $run = app(OperationRunService::class)->ensureRun(
        tenant: $tenant,
        type: 'policy.delete',
        inputs: [
            'target_scope' => [
                'entra_tenant_id' => (string) ($tenant->managed_environment_id ?? $tenant->external_id),
            ],
        ],
        initiator: $user,
    );

    $job = new class((int) $tenant->getKey(), (int) $user->getKey(), 'policy-123', $run) extends BulkOperationWorkerJob
    {
        protected function process(OperationRunService $runs): void {}
    };

    $context = new QueuedExecutionContext(
        run: $run,
        operationType: 'policy.delete',
        workspaceId: (int) $tenant->workspace_id,
        tenant: $tenant,
        initiator: $user,
        authorityMode: ExecutionAuthorityMode::ActorBound,
        requiredCapability: 'tenant.manage',
        workspaceRequiredCapability: null,
        providerConnectionId: null,
        targetScope: [
            'workspace_id' => (int) $tenant->workspace_id,
            'managed_environment_id' => (int) $tenant->getKey(),
            'provider_connection_id' => null,
        ],
    );

    $allowDecision = QueuedExecutionLegitimacyDecision::allow(
        context: $context,
        checks: [
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'passed',
            'tenant_operability' => 'passed',
            'execution_prerequisites' => 'not_applicable',
        ],
    );

    $denyDecision = QueuedExecutionLegitimacyDecision::deny(
        context: $context,
        checks: [
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'failed',
            'tenant_operability' => 'passed',
            'execution_prerequisites' => 'not_applicable',
        ],
        reasonCode: ExecutionDenialReasonCode::MissingCapability,
        metadata: [
            'required_capability' => 'tenant.manage',
        ],
    );

    app()->instance(QueuedExecutionLegitimacyGate::class, new class($allowDecision, $denyDecision)
    {
        private int $callCount = 0;

        public function __construct(
            private readonly QueuedExecutionLegitimacyDecision $allowDecision,
            private readonly QueuedExecutionLegitimacyDecision $denyDecision,
        ) {}

        public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision
        {
            return $this->callCount++ === 0 ? $this->allowDecision : $this->denyDecision;
        }
    });

    $executionCount = 0;

    runQueuedRetryJobThroughMiddleware(
        $job,
        function () use (&$executionCount): string {
            $executionCount++;

            return 'attempt-1';
        },
    );

    $blockedResponse = runQueuedRetryJobThroughMiddleware(
        $job,
        function () use (&$executionCount): string {
            $executionCount++;

            return 'attempt-2';
        },
    );

    $run->refresh();

    expect($executionCount)->toBe(1)
        ->and($blockedResponse)->toBeNull()
        ->and($run->status)->toBe('completed')
        ->and($run->outcome)->toBe('blocked')
        ->and($run->context['reason_code'] ?? null)->toBe('missing_capability')
        ->and($run->context['execution_legitimacy']['checks']['capability'] ?? null)->toBe('failed');
});
