<?php

declare(strict_types=1);

use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Support\Auth\Capabilities;
use App\Services\Operations\QueuedExecutionLegitimacyGate;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Operations\ExecutionAuthorityMode;
use App\Support\Operations\ExecutionDenialClass;
use App\Support\Operations\ExecutionDenialReasonCode;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

it('allows actor-bound inventory execution when the initiator still has capability', 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' => [
            'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value,
        ],
    ]);

    $decision = app(QueuedExecutionLegitimacyGate::class)->evaluate($run);

    expect($decision->allowed)->toBeTrue()
        ->and($decision->authorityMode)->toBe(ExecutionAuthorityMode::ActorBound)
        ->and($decision->reasonCode)->toBeNull()
        ->and($decision->checks)->toMatchArray([
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'passed',
            'tenant_operability' => 'passed',
            'execution_prerequisites' => 'not_applicable',
        ])
        ->and($decision->toArray())->toMatchArray([
            'operation_type' => 'inventory.sync',
            'authority_mode' => 'actor_bound',
            'allowed' => true,
            'retryable' => false,
            'target_scope' => [
                'workspace_id' => (int) $tenant->workspace_id,
                'managed_environment_id' => (int) $tenant->getKey(),
                'provider_connection_id' => null,
            ],
        ]);
});

it('allows onboarding verification runs for onboarding tenants when they originate from the onboarding wizard', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $tenant->forceFill([
        'status' => 'onboarding',
    ])->save();

    $connection = ProviderConnection::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'provider' => 'microsoft',
        'entra_tenant_id' => (string) $tenant->managed_environment_id,
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'user_id' => (int) $user->getKey(),
        'type' => 'provider.connection.check',
        'status' => OperationRunStatus::Queued->value,
        'outcome' => OperationRunOutcome::Pending->value,
        'context' => [
            'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value,
            'provider_connection_id' => (int) $connection->getKey(),
            'wizard' => [
                'flow' => 'managed_environment_onboarding',
                'step' => 'verification',
            ],
        ],
    ]);

    $decision = app(QueuedExecutionLegitimacyGate::class)->evaluate($run);

    expect($decision->allowed)->toBeTrue()
        ->and($decision->reasonCode)->toBeNull()
        ->and($decision->checks)->toMatchArray([
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'passed',
            'tenant_operability' => 'passed',
            'execution_prerequisites' => 'passed',
        ]);
});

it('allows workspace-scoped onboarding bootstrap capabilities during queued reauthorization', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $tenant->forceFill([
        'status' => 'onboarding',
    ])->save();

    $connection = ProviderConnection::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'provider' => 'microsoft',
        'entra_tenant_id' => (string) $tenant->managed_environment_id,
    ]);

    $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' => [
            'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value,
            'provider_connection_id' => (int) $connection->getKey(),
            'required_capability' => Capabilities::WORKSPACE_MANAGED_ENVIRONMENT_ONBOARD_BOOTSTRAP_INVENTORY_SYNC,
            'wizard' => [
                'flow' => 'managed_environment_onboarding',
                'step' => 'bootstrap',
            ],
        ],
    ]);

    $decision = app(QueuedExecutionLegitimacyGate::class)->evaluate($run);

    expect($decision->allowed)->toBeTrue()
        ->and($decision->reasonCode)->toBeNull()
        ->and($decision->checks)->toMatchArray([
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'passed',
            'tenant_operability' => 'passed',
            'execution_prerequisites' => 'passed',
        ]);
});

it('denies actor-bound execution when the initiator loses capability', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'readonly');

    $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' => [
            'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value,
        ],
    ]);

    $decision = app(QueuedExecutionLegitimacyGate::class)->evaluate($run);

    expect($decision->allowed)->toBeFalse()
        ->and($decision->denialClass)->toBe(ExecutionDenialClass::CapabilityDenied)
        ->and($decision->reasonCode)->toBe(ExecutionDenialReasonCode::MissingCapability)
        ->and($decision->retryable)->toBeFalse()
        ->and($decision->checks)->toMatchArray([
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'failed',
        ]);
});

it('allows system-authority execution only for allowlisted operation types', function (): void {
    [, $tenant] = createUserWithTenant(role: 'owner');

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'user_id' => null,
        'type' => 'backup_schedule_run',
        'status' => OperationRunStatus::Queued->value,
        'outcome' => OperationRunOutcome::Pending->value,
        'context' => [
            'execution_authority_mode' => ExecutionAuthorityMode::SystemAuthority->value,
        ],
    ]);

    $decision = app(QueuedExecutionLegitimacyGate::class)->evaluate($run);

    expect($decision->allowed)->toBeTrue()
        ->and($decision->authorityMode)->toBe(ExecutionAuthorityMode::SystemAuthority)
        ->and($decision->initiator)->toMatchArray([
            'identity_type' => 'system',
            'user_id' => null,
        ]);
});

it('denies non-allowlisted system-authority execution with retryable prerequisite semantics', function (): void {
    [, $tenant] = createUserWithTenant(role: 'owner');

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

    $decision = app(QueuedExecutionLegitimacyGate::class)->evaluate($run);

    expect($decision->allowed)->toBeFalse()
        ->and($decision->denialClass)->toBe(ExecutionDenialClass::PrerequisiteInvalid)
        ->and($decision->reasonCode)->toBe(ExecutionDenialReasonCode::ExecutionPrerequisiteInvalid)
        ->and($decision->retryable)->toBeTrue()
        ->and($decision->checks['execution_prerequisites'])->toBe('failed');
});

it('maps write-gate blocks to retryable prerequisite denials', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $tenant->forceFill([
        'status' => 'archived',
        'deleted_at' => now(),
        'rbac_status' => 'not_configured',
        'rbac_last_checked_at' => null,
    ])->save();

    $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' => [
            'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value,
        ],
    ]);

    $decision = app(QueuedExecutionLegitimacyGate::class)->evaluate($run);

    expect($decision->allowed)->toBeFalse()
        ->and($decision->reasonCode)->toBe(ExecutionDenialReasonCode::WriteGateBlocked)
        ->and($decision->denialClass)->toBe(ExecutionDenialClass::PrerequisiteInvalid)
        ->and($decision->retryable)->toBeTrue()
        ->and($decision->checks['execution_prerequisites'])->toBe('failed');
});

it('denies promotion execution when the initiator loses workspace baseline manage capability', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'readonly');

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'user_id' => (int) $user->getKey(),
        'type' => 'promotion.execute',
        'status' => OperationRunStatus::Queued->value,
        'outcome' => OperationRunOutcome::Pending->value,
        'context' => [
            'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value,
            'required_capability' => Capabilities::TENANT_MANAGE,
            'workspace_required_capability' => Capabilities::WORKSPACE_BASELINES_MANAGE,
        ],
    ]);

    $decision = app(QueuedExecutionLegitimacyGate::class)->evaluate($run);

    expect($decision->allowed)->toBeFalse()
        ->and($decision->denialClass)->toBe(ExecutionDenialClass::CapabilityDenied)
        ->and($decision->reasonCode)->toBe(ExecutionDenialReasonCode::MissingCapability)
        ->and($decision->retryable)->toBeFalse()
        ->and($decision->checks)->toMatchArray([
            'workspace_scope' => 'passed',
            'tenant_scope' => 'passed',
            'capability' => 'failed',
        ]);
});

it('infers tenant sync capability for policy sync runs from the central resolver', 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' => 'policy.sync',
        'status' => OperationRunStatus::Queued->value,
        'outcome' => OperationRunOutcome::Pending->value,
        'context' => [
            'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value,
        ],
    ]);

    $context = app(QueuedExecutionLegitimacyGate::class)->buildContext($run);

    expect($context->requiredCapability)->toBe('tenant.sync')
        ->and($context->authorityMode)->toBe(ExecutionAuthorityMode::ActorBound);
});

it('infers system-authority schedule capability for backup schedule runs from the central resolver', function (): void {
    [, $tenant] = createUserWithTenant(role: 'owner');

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

    $context = app(QueuedExecutionLegitimacyGate::class)->buildContext($run);

    expect($context->requiredCapability)->toBe('tenant_backup_schedules.run')
        ->and($context->authorityMode)->toBe(ExecutionAuthorityMode::SystemAuthority)
        ->and($context->initiatorSnapshot())->toMatchArray([
            'identity_type' => 'system',
            'user_id' => null,
        ]);
});

it('builds promotion execution context with workspace reauthorization and write-gate prerequisites', 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' => 'promotion.execute',
        'status' => OperationRunStatus::Queued->value,
        'outcome' => OperationRunOutcome::Pending->value,
        'context' => [
            'execution_authority_mode' => ExecutionAuthorityMode::ActorBound->value,
            'required_capability' => Capabilities::TENANT_MANAGE,
            'workspace_required_capability' => Capabilities::WORKSPACE_BASELINES_MANAGE,
        ],
    ]);

    $context = app(QueuedExecutionLegitimacyGate::class)->buildContext($run);

    expect($context->requiredCapability)->toBe(Capabilities::TENANT_MANAGE)
        ->and($context->workspaceRequiredCapability)->toBe(Capabilities::WORKSPACE_BASELINES_MANAGE)
        ->and($context->prerequisiteClasses)->toContain('write_gate');
});
