<?php

use App\Jobs\AddPoliciesToBackupSetJob;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Services\Intune\FoundationSnapshotService;
use App\Services\Intune\PolicyCaptureOrchestrator;
use App\Services\Intune\SnapshotValidator;
use App\Services\Intune\VersionService;
use App\Services\OperationRunService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery\MockInterface;

uses(RefreshDatabase::class);

it('records stable failure reason codes and keeps run counts consistent', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $this->actingAs($user);

    $backupSet = BackupSet::factory()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Test backup',
        'status' => 'completed',
        'metadata' => ['failures' => []],
    ]);

    $policyA = Policy::factory()->create([
        'managed_environment_id' => $tenant->id,
        'ignored_at' => null,
    ]);

    $policyB = Policy::factory()->create([
        'managed_environment_id' => $tenant->id,
        'ignored_at' => null,
    ]);

    $versionA = PolicyVersion::factory()->create([
        'managed_environment_id' => $tenant->id,
        'policy_id' => $policyA->id,
        'policy_type' => $policyA->policy_type,
        'platform' => $policyA->platform,
        'snapshot' => ['id' => $policyA->external_id],
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->id,
        'user_id' => $user->id,
        'initiator_name' => $user->name,
        'type' => 'backup_set.update',
        'status' => 'queued',
        'outcome' => 'pending',
        'context' => [
            'backup_set_id' => (int) $backupSet->getKey(),
            'policy_ids' => [(int) $policyA->getKey(), (int) $policyB->getKey()],
        ],
        'summary_counts' => [],
        'failure_summary' => [],
    ]);

    $this->mock(PolicyCaptureOrchestrator::class, function (MockInterface $mock) use ($policyA, $policyB, $tenant, $versionA) {
        $mock->shouldReceive('capture')
            ->twice()
            ->andReturnUsing(function (
                Policy $policy,
                \App\Models\ManagedEnvironment $tenantArg,
                bool $includeAssignments = false,
                bool $includeScopeTags = false,
                ?string $createdBy = null,
                array $metadata = []
            ) use ($policyA, $policyB, $tenant, $versionA) {
                expect($tenantArg->id)->toBe($tenant->id);
                expect($includeAssignments)->toBeTrue();
                expect($includeScopeTags)->toBeTrue();
                expect($metadata['backup_set_id'] ?? null)->not->toBeNull();

                if ($policy->is($policyA)) {
                    return [
                        'version' => $versionA,
                        'captured' => [
                            'payload' => [
                                'id' => $policyA->external_id,
                                '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
                            ],
                            'assignments' => [],
                            'scope_tags' => ['ids' => ['0'], 'names' => ['Default']],
                            'metadata' => [],
                        ],
                    ];
                }

                expect($policy->is($policyB))->toBeTrue();

                return [
                    'failure' => [
                        'policy_id' => $policyB->id,
                        'reason' => 'Forbidden',
                        'status' => 403,
                    ],
                ];
            });
    });

    $job = new AddPoliciesToBackupSetJob(
        tenantId: (int) $tenant->getKey(),
        userId: (int) $user->getKey(),
        backupSetId: (int) $backupSet->getKey(),
        policyIds: [(int) $policyA->getKey(), (int) $policyB->getKey()],
        options: [
            'include_assignments' => true,
            'include_scope_tags' => true,
            'include_foundations' => false,
        ],
        idempotencyKey: 'test-idempotency-key',
        operationRun: $run,
    );

    $job->handle(
        operationRunService: app(OperationRunService::class),
        captureOrchestrator: app(PolicyCaptureOrchestrator::class),
        foundationSnapshots: $this->mock(FoundationSnapshotService::class),
        snapshotValidator: app(SnapshotValidator::class),
        versionService: app(VersionService::class),
    );

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

    expect($run->status)->toBe('completed');
    expect($run->outcome)->toBe('partially_succeeded');
    expect((int) ($run->summary_counts['total'] ?? 0))->toBe(2);
    expect((int) ($run->summary_counts['processed'] ?? 0))->toBe(2);
    expect((int) ($run->summary_counts['succeeded'] ?? 0))->toBe(1);
    expect((int) ($run->summary_counts['failed'] ?? 0))->toBe(1);
    expect((int) ($run->summary_counts['skipped'] ?? 0))->toBe(0);

    expect(BackupItem::query()
        ->where('backup_set_id', $backupSet->id)
        ->where('policy_id', $policyA->id)
        ->exists())->toBeTrue();

    $failureEntry = collect($run->failure_summary ?? [])
        ->first(fn ($entry): bool => is_array($entry) && (($entry['code'] ?? null) === 'graph.graph_forbidden'));

    expect($failureEntry)->not->toBeNull();

    expect($backupSet->status)->toBe('partial');
});

it('seeds and advances counted progress for the base backup-set policy selection', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $this->actingAs($user);

    $backupSet = BackupSet::factory()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Progress test backup',
        'status' => 'completed',
        'metadata' => ['failures' => []],
    ]);

    $policyA = Policy::factory()->create([
        'managed_environment_id' => $tenant->id,
        'ignored_at' => null,
    ]);

    $policyB = Policy::factory()->create([
        'managed_environment_id' => $tenant->id,
        'ignored_at' => null,
    ]);

    $versionA = PolicyVersion::factory()->create([
        'managed_environment_id' => $tenant->id,
        'policy_id' => $policyA->id,
        'policy_type' => $policyA->policy_type,
        'platform' => $policyA->platform,
        'snapshot' => ['id' => $policyA->external_id],
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->id,
        'user_id' => $user->id,
        'initiator_name' => $user->name,
        'type' => 'backup_set.update',
        'status' => 'queued',
        'outcome' => 'pending',
        'context' => [
            'backup_set_id' => (int) $backupSet->getKey(),
            'policy_ids' => [(int) $policyA->getKey(), (int) $policyB->getKey()],
        ],
        'summary_counts' => [],
        'failure_summary' => [],
    ]);

    $this->mock(PolicyCaptureOrchestrator::class, function (MockInterface $mock) use ($policyA, $policyB, $tenant, $versionA) {
        $mock->shouldReceive('capture')
            ->twice()
            ->andReturnUsing(function (Policy $policy) use ($policyA, $policyB, $versionA) {
                if ($policy->is($policyA)) {
                    return [
                        'version' => $versionA,
                        'captured' => [
                            'payload' => [
                                'id' => $policyA->external_id,
                                '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
                            ],
                            'assignments' => [],
                            'scope_tags' => null,
                            'metadata' => [],
                        ],
                    ];
                }

                expect($policy->is($policyB))->toBeTrue();

                return [
                    'failure' => [
                        'policy_id' => $policyB->id,
                        'reason' => 'Forbidden',
                        'status' => 403,
                    ],
                ];
            });
    });

    $seededCounts = [];
    $increments = [];
    $realOperationRuns = app(OperationRunService::class);

    $spyOperationRuns = new class($realOperationRuns, $seededCounts, $increments) extends OperationRunService
    {
        private array $seededCounts;

        private array $increments;

        public function __construct(private readonly OperationRunService $inner, array &$seededCounts, array &$increments)
        {
            $this->seededCounts = &$seededCounts;
            $this->increments = &$increments;
        }

        public function updateRun(OperationRun $run, string $status, ?string $outcome = null, array $summaryCounts = [], array $failures = []): OperationRun
        {
            if ($status === 'running' && $summaryCounts !== []) {
                $this->seededCounts[] = $summaryCounts;
            }

            return $this->inner->updateRun($run, $status, $outcome, $summaryCounts, $failures);
        }

        public function incrementSummaryCounts(OperationRun $run, array $delta): OperationRun
        {
            $this->increments[] = $delta;

            return $this->inner->incrementSummaryCounts($run, $delta);
        }
    };

    $job = new AddPoliciesToBackupSetJob(
        tenantId: (int) $tenant->getKey(),
        userId: (int) $user->getKey(),
        backupSetId: (int) $backupSet->getKey(),
        policyIds: [(int) $policyA->getKey(), (int) $policyB->getKey()],
        options: [
            'include_assignments' => false,
            'include_scope_tags' => false,
            'include_foundations' => false,
        ],
        idempotencyKey: 'base-progress-counted',
        operationRun: $run,
    );

    $job->handle(
        operationRunService: $spyOperationRuns,
        captureOrchestrator: app(PolicyCaptureOrchestrator::class),
        foundationSnapshots: $this->mock(FoundationSnapshotService::class),
        snapshotValidator: app(SnapshotValidator::class),
        versionService: app(VersionService::class),
    );

    $run->refresh();

    expect($seededCounts)->toHaveCount(1)
        ->and($seededCounts[0])->toMatchArray([
            'total' => 2,
            'items' => 2,
            'processed' => 0,
            'succeeded' => 0,
            'failed' => 0,
            'skipped' => 0,
        ]);

    expect(collect($increments)->contains(fn (array $delta): bool => array_key_exists('total', $delta)))->toBeFalse();
    expect($run->summary_counts ?? [])->toMatchArray([
        'total' => 2,
        'processed' => 2,
        'succeeded' => 1,
        'failed' => 1,
    ]);
});

it('captures RBAC foundation items with linked policy versions when include_foundations is enabled', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $this->actingAs($user);

    config()->set('tenantpilot.foundation_types', [
        [
            'type' => 'intuneRoleDefinition',
            'label' => 'Intune Role Definition',
            'category' => 'RBAC',
            'platform' => 'all',
            'endpoint' => 'deviceManagement/roleDefinitions',
            'backup' => 'full',
            'restore' => 'preview-only',
            'risk' => 'high',
        ],
        [
            'type' => 'intuneRoleAssignment',
            'label' => 'Intune Role Assignment',
            'category' => 'RBAC',
            'platform' => 'all',
            'endpoint' => 'deviceManagement/roleAssignments',
            'backup' => 'full',
            'restore' => 'preview-only',
            'risk' => 'high',
        ],
    ]);

    $backupSet = BackupSet::factory()->create([
        'managed_environment_id' => $tenant->id,
        'name' => 'RBAC foundations',
        'status' => 'completed',
        'metadata' => ['failures' => []],
    ]);

    $policy = Policy::factory()->create([
        'managed_environment_id' => $tenant->id,
        'policy_type' => 'deviceConfiguration',
        'platform' => 'windows',
        'ignored_at' => null,
    ]);

    $version = PolicyVersion::factory()->create([
        'managed_environment_id' => $tenant->id,
        'policy_id' => $policy->id,
        'policy_type' => $policy->policy_type,
        'platform' => $policy->platform,
        'snapshot' => ['id' => $policy->external_id],
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => $tenant->id,
        'user_id' => $user->id,
        'initiator_name' => $user->name,
        'type' => 'backup_set.update',
        'status' => 'queued',
        'outcome' => 'pending',
        'context' => [
            'backup_set_id' => (int) $backupSet->getKey(),
            'policy_ids' => [(int) $policy->getKey()],
        ],
        'summary_counts' => [],
        'failure_summary' => [],
    ]);

    $this->mock(PolicyCaptureOrchestrator::class, function (MockInterface $mock) use ($policy, $tenant, $version) {
        $mock->shouldReceive('capture')
            ->once()
            ->andReturnUsing(function (
                Policy $capturedPolicy,
                \App\Models\ManagedEnvironment $tenantArg,
                bool $includeAssignments = false,
                bool $includeScopeTags = false,
                ?string $createdBy = null,
                array $metadata = []
            ) use ($policy, $tenant, $version) {
                expect($capturedPolicy->is($policy))->toBeTrue();
                expect($tenantArg->is($tenant))->toBeTrue();
                expect($metadata['backup_set_id'] ?? null)->toBe((int) $metadata['backup_set_id']);

                return [
                    'version' => $version,
                    'captured' => [
                        'payload' => [
                            'id' => $policy->external_id,
                            '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
                        ],
                        'assignments' => [],
                        'scope_tags' => null,
                        'metadata' => [],
                    ],
                ];
            });
    });

    $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
        $mock->shouldReceive('fetchAll')
            ->twice()
            ->andReturnUsing(function (\App\Models\ManagedEnvironment $tenant, string $foundationType): array {
                return match ($foundationType) {
                    'intuneRoleDefinition' => [
                        'items' => [[
                            'source_id' => 'role-def-1',
                            'display_name' => 'Policy and Profile Manager',
                            'payload' => [
                                'id' => 'role-def-1',
                                'displayName' => 'Policy and Profile Manager',
                                'description' => 'Built-in RBAC role',
                                'isBuiltIn' => true,
                            ],
                            'metadata' => [
                                'displayName' => 'Policy and Profile Manager',
                                'kind' => 'intuneRoleDefinition',
                            ],
                        ]],
                        'failures' => [],
                    ],
                    'intuneRoleAssignment' => [
                        'items' => [[
                            'source_id' => 'role-assign-1',
                            'display_name' => 'Helpdesk Assignment',
                            'payload' => [
                                'id' => 'role-assign-1',
                                'displayName' => 'Helpdesk Assignment',
                                'members' => ['group-1'],
                                'resourceScopes' => ['/'],
                                'roleDefinition' => [
                                    'id' => 'role-def-1',
                                    'displayName' => 'Policy and Profile Manager',
                                ],
                            ],
                            'metadata' => [
                                'displayName' => 'Helpdesk Assignment',
                                'kind' => 'intuneRoleAssignment',
                            ],
                        ]],
                        'failures' => [],
                    ],
                    default => [
                        'items' => [],
                        'failures' => [],
                    ],
                };
            });
    });

    $job = new AddPoliciesToBackupSetJob(
        tenantId: (int) $tenant->getKey(),
        userId: (int) $user->getKey(),
        backupSetId: (int) $backupSet->getKey(),
        policyIds: [(int) $policy->getKey()],
        options: [
            'include_assignments' => false,
            'include_scope_tags' => false,
            'include_foundations' => true,
        ],
        idempotencyKey: 'rbac-foundation-additions',
        operationRun: $run,
    );

    $job->handle(
        operationRunService: app(OperationRunService::class),
        captureOrchestrator: app(PolicyCaptureOrchestrator::class),
        foundationSnapshots: app(FoundationSnapshotService::class),
        snapshotValidator: app(SnapshotValidator::class),
        versionService: app(VersionService::class),
    );

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

    $definitionItem = BackupItem::query()
        ->where('backup_set_id', $backupSet->id)
        ->where('policy_type', 'intuneRoleDefinition')
        ->first();
    $assignmentItem = BackupItem::query()
        ->where('backup_set_id', $backupSet->id)
        ->where('policy_type', 'intuneRoleAssignment')
        ->first();

    expect($run->outcome)->toBe('succeeded');
    expect($run->summary_counts)->toMatchArray([
        'total' => 3,
        'processed' => 3,
        'succeeded' => 3,
        'created' => 3,
        'items' => 3,
    ]);
    expect($backupSet->status)->toBe('completed');
    expect($backupSet->item_count)->toBe(3);
    expect($definitionItem)->not->toBeNull();
    expect($definitionItem?->policy_id)->not->toBeNull();
    expect($definitionItem?->policy_version_id)->not->toBeNull();
    expect($definitionItem?->resolvedDisplayName())->toBe('Policy and Profile Manager');
    expect($assignmentItem)->not->toBeNull();
    expect($assignmentItem?->policy_id)->not->toBeNull();
    expect($assignmentItem?->policy_version_id)->not->toBeNull();
    expect($assignmentItem?->resolvedDisplayName())->toBe('Helpdesk Assignment');
});
