<?php

declare(strict_types=1);

use App\Jobs\Middleware\EnsureQueuedExecutionLegitimate;
use App\Jobs\Middleware\TrackOperationRun;
use App\Models\OperationRun;
use App\Services\Operations\QueuedExecutionLegitimacyGate;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Operations\ExecutionAuthorityMode;
use App\Support\Operations\ExecutionDenialReasonCode;
use App\Support\Operations\QueuedExecutionContext;
use App\Support\Operations\QueuedExecutionLegitimacyDecision;

it('blocks before track middleware can mark the run running', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'user_id' => (int) $user->getKey(),
        'type' => 'inventory_sync',
        'status' => OperationRunStatus::Queued->value,
        'outcome' => OperationRunOutcome::Pending->value,
    ]);

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

    $decision = QueuedExecutionLegitimacyDecision::deny(
        context: $context,
        checks: [
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'failed',
            'tenant_operability' => 'not_applicable',
            'execution_prerequisites' => 'not_applicable',
        ],
        reasonCode: ExecutionDenialReasonCode::MissingCapability,
    );

    app()->instance(QueuedExecutionLegitimacyGate::class, new class($decision)
    {
        public function __construct(private readonly QueuedExecutionLegitimacyDecision $decision) {}

        public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision
        {
            return $this->decision;
        }
    });

    $ensure = new EnsureQueuedExecutionLegitimate;
    $track = new TrackOperationRun;
    $executed = false;
    $job = new class($run)
    {
        public function __construct(public OperationRun $operationRun) {}
    };

    $response = $ensure->handle($job, function ($job) use (&$executed, $track) {
        return $track->handle($job, function () use (&$executed): string {
            $executed = true;

            return 'ran';
        });
    });

    $run->refresh();

    expect($response)->toBeNull()
        ->and($executed)->toBeFalse()
        ->and($run->status)->toBe(OperationRunStatus::Completed->value)
        ->and($run->outcome)->toBe(OperationRunOutcome::Blocked->value)
        ->and($run->started_at)->toBeNull()
        ->and($run->context['blocked_by'] ?? null)->toBe('queued_execution_legitimacy')
        ->and($run->context['execution_legitimacy']['reason_code'] ?? null)->toBe('missing_capability');
});

it('marks legitimate execution running before completing it', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'user_id' => (int) $user->getKey(),
        'type' => 'inventory_sync',
        'status' => OperationRunStatus::Queued->value,
        'outcome' => OperationRunOutcome::Pending->value,
    ]);

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

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

    app()->instance(QueuedExecutionLegitimacyGate::class, new class($decision)
    {
        public function __construct(private readonly QueuedExecutionLegitimacyDecision $decision) {}

        public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision
        {
            return $this->decision;
        }
    });

    $ensure = new EnsureQueuedExecutionLegitimate;
    $track = new TrackOperationRun;
    $executed = false;
    $job = new class($run)
    {
        public function __construct(public OperationRun $operationRun) {}
    };

    $response = $ensure->handle($job, function ($job) use (&$executed, $track) {
        return $track->handle($job, function () use (&$executed): string {
            $executed = true;

            return 'ran';
        });
    });

    $run->refresh();

    expect($response)->toBe('ran')
        ->and($executed)->toBeTrue()
        ->and($run->status)->toBe(OperationRunStatus::Completed->value)
        ->and($run->outcome)->toBe(OperationRunOutcome::Succeeded->value)
        ->and($run->started_at)->not->toBeNull()
        ->and($run->completed_at)->not->toBeNull();
});

it('persists write-gate denials as blocked before track middleware runs', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'user_id' => (int) $user->getKey(),
        'type' => 'restore.execute',
        'status' => OperationRunStatus::Queued->value,
        'outcome' => OperationRunOutcome::Pending->value,
    ]);

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

    $decision = QueuedExecutionLegitimacyDecision::deny(
        context: $context,
        checks: [
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'passed',
            'tenant_operability' => 'passed',
            'execution_prerequisites' => 'failed',
        ],
        reasonCode: ExecutionDenialReasonCode::WriteGateBlocked,
    );

    app()->instance(QueuedExecutionLegitimacyGate::class, new class($decision)
    {
        public function __construct(private readonly QueuedExecutionLegitimacyDecision $decision) {}

        public function evaluate(OperationRun $run): QueuedExecutionLegitimacyDecision
        {
            return $this->decision;
        }
    });

    $ensure = new EnsureQueuedExecutionLegitimate;
    $track = new TrackOperationRun;
    $executed = false;
    $job = new class($run)
    {
        public function __construct(public OperationRun $operationRun) {}
    };

    $response = $ensure->handle($job, function ($job) use (&$executed, $track) {
        return $track->handle($job, function () use (&$executed): string {
            $executed = true;

            return 'ran';
        });
    });

    $run->refresh();

    expect($response)->toBeNull()
        ->and($executed)->toBeFalse()
        ->and($run->status)->toBe(OperationRunStatus::Completed->value)
        ->and($run->outcome)->toBe(OperationRunOutcome::Blocked->value)
        ->and($run->started_at)->toBeNull()
        ->and($run->context['reason_code'] ?? null)->toBe('write_gate_blocked')
        ->and($run->context['execution_legitimacy']['reason_code'] ?? null)->toBe('write_gate_blocked');
});
