<?php

declare(strict_types=1);

use App\Jobs\ProviderConnectionHealthCheckJob;
use App\Models\AuditLog;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Models\ManagedEnvironmentPermission;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\OperationRunService;
use App\Services\Providers\MicrosoftProviderHealthCheck;
use App\Support\Audit\AuditActionId;
use App\Support\Verification\VerificationReportSchema;

it('writes a sanitized verification report for failed provider connection checks', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'operator');

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

    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
        'payload' => [
            'managed_environment_id' => (string) $connection->entra_tenant_id,
            'client_id' => fake()->uuid(),
            'client_secret' => fake()->sha1(),
        ],
    ]);

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

    $this->mock(GraphClientInterface::class, function ($mock): void {
        $mock->shouldReceive('getOrganization')
            ->once()
            ->andReturn(new GraphResponse(false, [], 401, ['Bearer super-secret-token']));
    });

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

    $job->handle(
        healthCheck: app(MicrosoftProviderHealthCheck::class),
        runs: app(OperationRunService::class),
    );

    $run = $run->fresh();

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

    $context = is_array($run->context) ? $run->context : [];
    $report = $context['verification_report'] ?? null;

    expect($report)->toBeArray();
    expect(VerificationReportSchema::isValidReport($report))->toBeTrue();
    expect(json_encode($report))->not->toContain('Bearer ');
    expect($report['checks'][0]['reason_code'] ?? null)->toBe('provider_auth_failed');

    foreach (($report['checks'] ?? []) as $check) {
        expect($check)->toBeArray();

        foreach (($check['evidence'] ?? []) as $pointer) {
            expect($pointer)->toBeArray();
            expect(array_keys($pointer))->toEqualCanonicalizing(['kind', 'value']);
        }
    }

    $audit = AuditLog::query()
        ->where('workspace_id', (int) $tenant->workspace_id)
        ->where('action', AuditActionId::VerificationCompleted->value)
        ->latest('id')
        ->first();

    expect($audit)->not->toBeNull();
    expect($audit?->metadata)->toMatchArray([
        'operation_run_id' => (int) $run->getKey(),
    ]);
});

it('writes a verification report for successful provider connection checks', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'operator');

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

    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
        'payload' => [
            'managed_environment_id' => (string) $connection->entra_tenant_id,
            'client_id' => fake()->uuid(),
            'client_secret' => fake()->sha1(),
        ],
    ]);

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

    $this->mock(GraphClientInterface::class, function ($mock): void {
        $mock->shouldReceive('getOrganization')
            ->once()
            ->andReturn(new GraphResponse(true, [
                'id' => 'org_123',
                'displayName' => 'Org 123',
            ], 200));

        $required = collect(config('intune_permissions.permissions', []))
            ->filter(fn (mixed $row): bool => is_array($row))
            ->map(fn (array $row): string => (string) ($row['key'] ?? ''))
            ->filter()
            ->values()
            ->all();

        $mock->shouldReceive('getServicePrincipalPermissions')
            ->once()
            ->andReturn(new GraphResponse(true, [
                'permissions' => $required,
            ], 200));
    });

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

    $job->handle(
        healthCheck: app(MicrosoftProviderHealthCheck::class),
        runs: app(OperationRunService::class),
    );

    $run = $run->fresh();

    expect($run)->not->toBeNull();
    expect($run->status)->toBe('completed');
    expect($run->outcome)->toBe('succeeded');

    $context = is_array($run->context) ? $run->context : [];
    $report = $context['verification_report'] ?? null;

    expect($report)->toBeArray();
    expect(VerificationReportSchema::isValidReport($report))->toBeTrue();

    $checks = is_array($report['checks'] ?? null) ? $report['checks'] : [];
    $checkKeys = collect($checks)
        ->filter(fn ($check) => is_array($check))
        ->map(fn (array $check): string => (string) ($check['key'] ?? ''))
        ->filter()
        ->values()
        ->all();

    expect($checkKeys)->toContain('provider.connection.check');
    expect($checkKeys)->toContain('permissions.admin_consent');

    $counts = is_array($report['summary']['counts'] ?? null) ? $report['summary']['counts'] : [];
    expect((int) ($counts['total'] ?? 0))->toBe(count($checks));
});

it('degrades permission clusters to warnings when live permissions refresh is throttled', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'operator');

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

    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
        'payload' => [
            'managed_environment_id' => (string) $connection->entra_tenant_id,
            'client_id' => fake()->uuid(),
            'client_secret' => fake()->sha1(),
        ],
    ]);

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

    $this->mock(GraphClientInterface::class, function ($mock): void {
        $mock->shouldReceive('getOrganization')
            ->once()
            ->andReturn(new GraphResponse(true, [
                'id' => 'org_123',
                'displayName' => 'Org 123',
            ], 200));

        $mock->shouldReceive('getServicePrincipalPermissions')
            ->once()
            ->andReturn(new GraphResponse(false, [], 429, ['Too Many Requests']));
    });

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

    $job->handle(
        healthCheck: app(MicrosoftProviderHealthCheck::class),
        runs: app(OperationRunService::class),
    );

    $run = $run->fresh();

    expect($run?->outcome)->toBe('succeeded');

    $context = is_array($run?->context) ? $run->context : [];
    $report = $context['verification_report'] ?? null;

    expect($report)->toBeArray();
    expect(VerificationReportSchema::isValidReport($report))->toBeTrue();

    $checks = is_array($report['checks'] ?? null) ? $report['checks'] : [];

    $adminConsentCheck = collect($checks)
        ->filter(fn (mixed $check): bool => is_array($check))
        ->firstWhere('key', 'permissions.admin_consent');

    expect($adminConsentCheck)->toBeArray();
    expect($adminConsentCheck['status'] ?? null)->toBe('warn');
    expect($adminConsentCheck['blocking'] ?? null)->toBeFalse();
    expect($adminConsentCheck['reason_code'] ?? null)->toBe('rate_limited');
    expect((string) ($adminConsentCheck['message'] ?? ''))->toContain('Unable to refresh observed permissions inventory');

    expect(ManagedEnvironmentPermission::query()->where('managed_environment_id', (int) $tenant->getKey())->count())->toBe(0);
});

it('degrades permission clusters to warnings when live permissions refresh cannot map assignments', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'operator');

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

    ProviderCredential::factory()->create([
        'provider_connection_id' => (int) $connection->getKey(),
        'payload' => [
            'managed_environment_id' => (string) $connection->entra_tenant_id,
            'client_id' => fake()->uuid(),
            'client_secret' => fake()->sha1(),
        ],
    ]);

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

    $this->mock(GraphClientInterface::class, function ($mock): void {
        $mock->shouldReceive('getOrganization')
            ->once()
            ->andReturn(new GraphResponse(true, [
                'id' => 'org_123',
                'displayName' => 'Org 123',
            ], 200));

        $mock->shouldReceive('getServicePrincipalPermissions')
            ->twice()
            ->andReturn(new GraphResponse(true, [
                'permissions' => [],
                'diagnostics' => [
                    'assignments_total' => 1,
                    'mapped_total' => 0,
                    'graph_roles_total' => 1,
                ],
            ], 200));
    });

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

    $job->handle(
        healthCheck: app(MicrosoftProviderHealthCheck::class),
        runs: app(OperationRunService::class),
    );

    $credential = ProviderCredential::query()
        ->where('provider_connection_id', (int) $connection->getKey())
        ->first();

    $payload = is_array($credential?->payload) ? $credential->payload : [];

    $comparison = app(\App\Services\Intune\ManagedEnvironmentPermissionService::class)->compare(
        $tenant,
        persist: true,
        liveCheck: true,
        useConfiguredStub: false,
        graphOptions: [
            'tenant' => (string) $connection->entra_tenant_id,
            'client_id' => (string) ($payload['client_id'] ?? ''),
            'client_secret' => (string) ($payload['client_secret'] ?? ''),
        ],
    );

    expect($comparison['live_check']['reason_code'] ?? null)->toBe('permission_mapping_failed');

    $run = $run->fresh();

    $context = is_array($run?->context) ? $run->context : [];
    $report = $context['verification_report'] ?? null;

    expect($report)->toBeArray();
    expect(VerificationReportSchema::isValidReport($report))->toBeTrue();

    $checks = is_array($report['checks'] ?? null) ? $report['checks'] : [];

    $adminConsentCheck = collect($checks)
        ->filter(fn (mixed $check): bool => is_array($check))
        ->firstWhere('key', 'permissions.admin_consent');

    expect($adminConsentCheck)->toBeArray();
    expect($adminConsentCheck['status'] ?? null)->toBe('warn');
    expect($adminConsentCheck['blocking'] ?? null)->toBeFalse();
    expect($adminConsentCheck['reason_code'] ?? null)->toBeIn(['provider_permission_denied', 'unknown_error']);
    expect((string) ($adminConsentCheck['message'] ?? ''))->toContain('Unable to refresh observed permissions inventory');

    expect(ManagedEnvironmentPermission::query()->where('managed_environment_id', (int) $tenant->getKey())->count())->toBe(0);
});
