<?php

declare(strict_types=1);

use App\Jobs\BulkPolicyDeleteJob;
use App\Models\Policy;
use App\Services\Auth\CapabilityResolver;
use App\Services\OperationRunService;
use App\Services\Operations\BulkSelectionIdentity;
use Illuminate\Support\Facades\Queue;

function runQueuedBulkJobThroughMiddleware(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('stores actor-bound execution metadata when bulk policy delete is queued', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $policies = Policy::factory()->count(2)->create([
        'managed_environment_id' => (int) $tenant->getKey(),
    ]);

    $selection = app(BulkSelectionIdentity::class)->fromIds($policies->pluck('id')->all());

    $run = app(OperationRunService::class)->enqueueBulkOperation(
        tenant: $tenant,
        type: 'policy.delete',
        targetScope: [
            'entra_tenant_id' => (string) ($tenant->managed_environment_id ?? $tenant->external_id),
        ],
        selectionIdentity: $selection,
        dispatcher: static fn (): null => null,
        initiator: $user,
        extraContext: [
            'policy_count' => $policies->count(),
        ],
    );

    expect($run->context)->toMatchArray([
        'execution_authority_mode' => 'actor_bound',
        'required_capability' => 'tenant.manage',
        'policy_count' => 2,
    ])
        ->and($run->context['selection']['kind'] ?? null)->toBe('ids')
        ->and($run->context['selection']['ids_count'] ?? null)->toBe(2)
        ->and($run->context['selection']['ids_hash'] ?? null)->toBeString();
});

it('blocks bulk policy delete before worker fan-out when the initiator loses capability', function (): void {
    Queue::fake();

    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $policies = Policy::factory()->count(2)->create([
        'managed_environment_id' => (int) $tenant->getKey(),
    ]);

    $selection = app(BulkSelectionIdentity::class)->fromIds($policies->pluck('id')->all());

    $run = app(OperationRunService::class)->enqueueBulkOperation(
        tenant: $tenant,
        type: 'policy.delete',
        targetScope: [
            'entra_tenant_id' => (string) ($tenant->managed_environment_id ?? $tenant->external_id),
        ],
        selectionIdentity: $selection,
        dispatcher: static fn (): null => null,
        initiator: $user,
        extraContext: [
            'policy_count' => $policies->count(),
        ],
    );

    $job = new BulkPolicyDeleteJob(
        tenantId: (int) $tenant->getKey(),
        userId: (int) $user->getKey(),
        policyIds: $policies->pluck('id')->all(),
        operationRun: $run,
    );

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

    $terminalInvoked = false;

    runQueuedBulkJobThroughMiddleware(
        $job,
        function (BulkPolicyDeleteJob $job) use (&$terminalInvoked): void {
            $terminalInvoked = true;
            $job->handle(app(OperationRunService::class));
        },
    );

    $run->refresh();

    expect($terminalInvoked)->toBeFalse()
        ->and($run->status)->toBe('completed')
        ->and($run->outcome)->toBe('blocked')
        ->and($run->context['reason_code'] ?? null)->toBe('missing_capability')
        ->and($run->context['execution_legitimacy']['target_scope']['managed_environment_id'] ?? null)->toBe((int) $tenant->getKey());

    Queue::assertNothingPushed();
});
