<?php

declare(strict_types=1);

use App\Jobs\ProviderConnectionHealthCheckJob;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\OperationRunService;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Providers\ProviderConsentStatus;
use App\Support\Providers\ProviderVerificationStatus;

it('updates connection health and marks the run succeeded on success', function (): void {
    app()->instance(GraphClientInterface::class, new class implements GraphClientInterface
    {
        public function listPolicies(string $policyType, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getOrganization(array $options = []): GraphResponse
        {
            return new GraphResponse(true, data: ['id' => 'org-id', 'displayName' => 'Contoso']);
        }

        public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getServicePrincipalPermissions(array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function request(string $method, string $path, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }
    });

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

    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => fake()->uuid(),
        'is_enabled' => true,
        'verification_status' => ProviderVerificationStatus::Unknown->value,
    ]);

    ProviderCredential::factory()->create([
        'provider_connection_id' => $connection->getKey(),
        'payload' => [
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
        ],
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'user_id' => $user->getKey(),
        'initiator_name' => $user->name,
        'type' => 'provider.connection.check',
        'status' => 'running',
        'outcome' => 'pending',
        'context' => [
            'provider' => 'microsoft',
            'module' => 'health_check',
            'provider_connection_id' => (int) $connection->getKey(),
            'target_scope' => [
                'entra_tenant_id' => $connection->entra_tenant_id,
            ],
        ],
    ]);

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

    $job->handle(app(\App\Services\Providers\MicrosoftProviderHealthCheck::class), app(OperationRunService::class));

    $connection->refresh();
    $run->refresh();

    expect($connection->is_enabled)->toBeTrue()
        ->and($connection->consent_status)->toBe(ProviderConsentStatus::Granted)
        ->and($connection->verification_status)->toBe(ProviderVerificationStatus::Healthy)
        ->and($connection->last_health_check_at)->not->toBeNull()
        ->and($connection->last_error_reason_code)->toBeNull()
        ->and($connection->last_error_message)->toBeNull();

    expect($run->status)->toBe('completed');
    expect($run->outcome)->toBe('succeeded');
    expect($run->context)->toMatchArray([
        'connection_type' => 'dedicated',
    ]);
    expect($run->context['provider_context'] ?? [])->toMatchArray([
        'provider' => 'microsoft',
    ]);
    expect($run->context['target_scope'] ?? [])->toMatchArray([
        'provider' => 'microsoft',
        'scope_kind' => 'tenant',
        'scope_identifier' => $connection->entra_tenant_id,
        'scope_display_name' => 'Contoso',
    ])->not->toHaveKey('entra_tenant_id');

    expect($connection->metadata)->toMatchArray([
        'entra_tenant_name' => 'Contoso',
    ]);
});

it('finalizes the verification run as blocked when admin consent is missing', function (): void {
    config()->set('graph.client_id', 'platform-client-id');
    config()->set('graph.client_secret', 'platform-client-secret');

    app()->instance(GraphClientInterface::class, new class implements GraphClientInterface
    {
        public function listPolicies(string $policyType, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getOrganization(array $options = []): GraphResponse
        {
            throw new RuntimeException('provider_consent_missing');
        }

        public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getServicePrincipalPermissions(array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function request(string $method, string $path, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }
    });

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

    $connection = ProviderConnection::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => fake()->uuid(),
        'is_enabled' => true,
        'consent_status' => ProviderConsentStatus::Granted->value,
        'verification_status' => ProviderVerificationStatus::Healthy->value,
    ]);

    ProviderCredential::factory()->create([
        'provider_connection_id' => $connection->getKey(),
        'payload' => [
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
        ],
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'user_id' => $user->getKey(),
        'initiator_name' => $user->name,
        'type' => 'provider.connection.check',
        'status' => OperationRunStatus::Running->value,
        'outcome' => OperationRunOutcome::Pending->value,
        'context' => [
            'provider' => 'microsoft',
            'module' => 'health_check',
            'provider_connection_id' => (int) $connection->getKey(),
            'target_scope' => [
                'entra_tenant_id' => $connection->entra_tenant_id,
            ],
        ],
    ]);

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

    $job->handle(app(\App\Services\Providers\MicrosoftProviderHealthCheck::class), app(OperationRunService::class));

    $connection->refresh();
    $run->refresh();

    expect($connection->consent_status)->toBe(ProviderConsentStatus::Revoked)
        ->and($connection->verification_status)->toBe(ProviderVerificationStatus::Blocked);

    expect($run->status)->toBe(OperationRunStatus::Completed->value);
    expect($run->outcome)->toBe(OperationRunOutcome::Blocked->value);

    $context = is_array($run->context ?? null) ? $run->context : [];
    expect($context['reason_code'] ?? null)->toBe('provider_consent_revoked');

    $nextSteps = $context['next_steps'] ?? null;
    expect($nextSteps)->toBeArray();
    expect($nextSteps)->not->toBeEmpty();

    $first = $nextSteps[0] ?? null;
    expect($first)->toBeArray();
    expect($first['label'] ?? null)->toBe('Grant admin consent');
    expect($first['url'] ?? null)->toBeString()->not->toBeEmpty();
});

it('uses provider connection credentials when refreshing observed permissions', function (): void {
    $graph = new class implements GraphClientInterface
    {
        /** @var array<string, mixed> */
        public array $servicePrincipalPermissionOptions = [];

        public function listPolicies(string $policyType, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getOrganization(array $options = []): GraphResponse
        {
            return new GraphResponse(true, data: ['id' => 'org-id', 'displayName' => 'Contoso']);
        }

        public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getServicePrincipalPermissions(array $options = []): GraphResponse
        {
            $this->servicePrincipalPermissionOptions = $options;

            return new GraphResponse(true, data: ['permissions' => []]);
        }

        public function request(string $method, string $path, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }
    };

    app()->instance(GraphClientInterface::class, $graph);

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

    $tenant->update([
        'app_client_id' => null,
        'app_client_secret' => null,
    ]);

    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => fake()->uuid(),
        'is_enabled' => true,
        'verification_status' => ProviderVerificationStatus::Unknown->value,
    ]);

    ProviderCredential::factory()->create([
        'provider_connection_id' => $connection->getKey(),
        'type' => 'client_secret',
        'payload' => [
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
        ],
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'user_id' => $user->getKey(),
        'initiator_name' => $user->name,
        'type' => 'provider.connection.check',
        'status' => 'running',
        'outcome' => 'pending',
        'context' => [
            'provider' => 'microsoft',
            'module' => 'health_check',
            'provider_connection_id' => (int) $connection->getKey(),
            'target_scope' => [
                'entra_tenant_id' => $connection->entra_tenant_id,
            ],
        ],
    ]);

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

    $job->handle(app(\App\Services\Providers\MicrosoftProviderHealthCheck::class), app(OperationRunService::class));

    expect($graph->servicePrincipalPermissionOptions)->toMatchArray([
        'tenant' => $connection->entra_tenant_id,
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
    ]);
});

it('categorizes auth failures and stores sanitized reason codes and messages', function (): void {
    app()->instance(GraphClientInterface::class, new class implements GraphClientInterface
    {
        public function listPolicies(string $policyType, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getOrganization(array $options = []): GraphResponse
        {
            return new GraphResponse(
                success: false,
                data: [],
                status: 401,
                errors: ['invalid_client Authorization: Bearer super-secret-token client_secret=ghi'],
            );
        }

        public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function getServicePrincipalPermissions(array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }

        public function request(string $method, string $path, array $options = []): GraphResponse
        {
            return new GraphResponse(true);
        }
    });

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

    $connection = ProviderConnection::factory()->dedicated()->consentGranted()->create([
        'managed_environment_id' => $tenant->getKey(),
        'provider' => 'microsoft',
        'entra_tenant_id' => fake()->uuid(),
        'is_enabled' => true,
        'verification_status' => ProviderVerificationStatus::Unknown->value,
    ]);

    ProviderCredential::factory()->create([
        'provider_connection_id' => $connection->getKey(),
        'payload' => [
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
        ],
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'user_id' => $user->getKey(),
        'initiator_name' => $user->name,
        'type' => 'provider.connection.check',
        'status' => 'running',
        'outcome' => 'pending',
        'context' => [
            'provider' => 'microsoft',
            'module' => 'health_check',
            'provider_connection_id' => (int) $connection->getKey(),
            'target_scope' => [
                'entra_tenant_id' => $connection->entra_tenant_id,
            ],
        ],
    ]);

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

    $job->handle(app(\App\Services\Providers\MicrosoftProviderHealthCheck::class), app(OperationRunService::class));

    $connection->refresh();
    $run->refresh();

    expect($connection->consent_status)->toBe(ProviderConsentStatus::Granted)
        ->and($connection->verification_status)->toBe(ProviderVerificationStatus::Error)
        ->and($connection->last_error_reason_code)->toBe('provider_auth_failed');
    expect((string) $connection->last_error_message)
        ->not->toContain('Authorization')
        ->not->toContain('Bearer ')
        ->not->toContain('client_secret');

    expect($run->status)->toBe('completed');
    expect($run->outcome)->toBe('failed');

    $failures = $run->failure_summary;
    expect($failures)->toBeArray()->not->toBeEmpty();

    $message = (string) ($failures[0]['message'] ?? '');
    expect($failures[0]['reason_code'] ?? null)->toBe('provider_auth_failed');
    expect($message)
        ->not->toContain('Authorization')
        ->not->toContain('Bearer ')
        ->not->toContain('client_secret');
});
