<?php

use App\Filament\Resources\RestoreRunResource\Pages\CreateRestoreRun;
use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\Policy;
use App\Models\RestoreRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Graph\GroupResolver;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Mockery\MockInterface;

uses(RefreshDatabase::class);

beforeEach(function () {
    putenv('INTUNE_TENANT_ID');
    unset($_ENV['INTUNE_TENANT_ID'], $_SERVER['INTUNE_TENANT_ID']);
});

test('restore wizard can run safety checks and persists results on the restore run', function () {
    $tenant = ManagedEnvironment::create([
        'managed_environment_id' => 'tenant-1',
        'name' => 'ManagedEnvironment One',
        'metadata' => [],
        'rbac_status' => 'ok',
        'rbac_last_checked_at' => now(),
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');

    $policy = Policy::create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'policy-1',
        'policy_type' => 'settingsCatalogPolicy',
        'display_name' => 'Settings Catalog',
        'platform' => 'windows',
    ]);

    $backupSet = BackupSet::create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Backup',
        'status' => 'completed',
        'item_count' => 1,
    ]);

    $backupItem = BackupItem::create([
        'managed_environment_id' => $tenant->id,
        'backup_set_id' => $backupSet->id,
        'policy_id' => $policy->id,
        'policy_identifier' => $policy->external_id,
        'policy_type' => $policy->policy_type,
        'platform' => $policy->platform,
        'captured_at' => now(),
        'payload' => ['id' => $policy->external_id],
        'assignments' => [[
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'source-group-1',
                'group_display_name' => 'Source Group',
            ],
            'intent' => 'apply',
        ]],
    ]);

    $this->mock(GroupResolver::class, function (MockInterface $mock) {
        $mock->shouldReceive('resolveGroupIds')
            ->andReturnUsing(function (array $groupIds): array {
                return collect($groupIds)
                    ->mapWithKeys(fn (string $id) => [$id => [
                        'id' => $id,
                        'displayName' => null,
                        'orphaned' => true,
                    ]])
                    ->all();
            });
    });

    $user = User::factory()->create();
    [$user, $tenant] = createUserWithTenant(tenant: $tenant, user: $user, role: 'owner');
    $this->actingAs($user);
    setAdminPanelContext($tenant);

    $component = Livewire::test(CreateRestoreRun::class)
        ->fillForm([
            'backup_set_id' => $backupSet->id,
        ])
        ->goToNextWizardStep()
        ->fillForm([
            'scope_mode' => 'selected',
            'backup_item_ids' => [$backupItem->id],
        ])
        ->goToNextWizardStep()
        ->assertFormComponentActionVisible('check_results', 'run_restore_checks')
        ->callFormComponentAction('check_results', 'run_restore_checks');

    $summary = $component->get('data.check_summary');
    $results = $component->get('data.check_results');

    expect($summary)->toBeArray();
    expect($summary['blocking'] ?? null)->toBe(1);
    expect($summary['has_blockers'] ?? null)->toBeTrue();

    expect($results)->toBeArray();
    expect($results)->not->toBeEmpty();

    $assignmentCheck = collect($results)->firstWhere('code', 'assignment_groups');
    expect($assignmentCheck)->toBeArray();
    expect($assignmentCheck['severity'] ?? null)->toBe('blocking');

    $unmappedGroups = $assignmentCheck['meta']['unmapped'] ?? [];
    expect($unmappedGroups)->toBeArray();
    expect($unmappedGroups[0]['id'] ?? null)->toBe('source-group-1');

    $checksRanAt = $component->get('data.checks_ran_at');
    expect($checksRanAt)->toBeString();

    $component
        ->goToNextWizardStep()
        ->set('data.group_mapping.source-group-1', 'SKIP')
        ->set('data.check_summary', $summary)
        ->set('data.check_results', $results)
        ->set('data.checks_ran_at', $checksRanAt)
        ->callFormComponentAction('preview_diffs', 'run_restore_preview')
        ->goToNextWizardStep()
        ->call('create')
        ->assertHasNoFormErrors();

    $run = RestoreRun::query()->latest('id')->first();

    expect($run)->not->toBeNull();
    expect($run->metadata)->toHaveKeys([
        'check_summary',
        'check_results',
        'checks_ran_at',
    ]);
    expect($run->metadata['check_summary']['blocking'] ?? null)->toBe(1);
});

test('restore wizard treats skipped orphaned groups as a warning instead of a blocker', function () {
    $tenant = ManagedEnvironment::create([
        'managed_environment_id' => 'tenant-1',
        'name' => 'ManagedEnvironment One',
        'metadata' => [],
        'rbac_status' => 'ok',
        'rbac_last_checked_at' => now(),
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');

    $policy = Policy::create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'policy-1',
        'policy_type' => 'settingsCatalogPolicy',
        'display_name' => 'Settings Catalog',
        'platform' => 'windows',
    ]);

    $backupSet = BackupSet::create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Backup',
        'status' => 'completed',
        'item_count' => 1,
    ]);

    $backupItem = BackupItem::create([
        'managed_environment_id' => $tenant->id,
        'backup_set_id' => $backupSet->id,
        'policy_id' => $policy->id,
        'policy_identifier' => $policy->external_id,
        'policy_type' => $policy->policy_type,
        'platform' => $policy->platform,
        'captured_at' => now(),
        'payload' => ['id' => $policy->external_id],
        'assignments' => [[
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'source-group-1',
                'group_display_name' => 'Source Group',
            ],
            'intent' => 'apply',
        ]],
    ]);

    $this->mock(GroupResolver::class, function (MockInterface $mock) {
        $mock->shouldReceive('resolveGroupIds')
            ->andReturnUsing(function (array $groupIds): array {
                return collect($groupIds)
                    ->mapWithKeys(fn (string $id) => [$id => [
                        'id' => $id,
                        'displayName' => null,
                        'orphaned' => true,
                    ]])
                    ->all();
            });
    });

    $user = User::factory()->create();
    [$user, $tenant] = createUserWithTenant(tenant: $tenant, user: $user, role: 'owner');
    $this->actingAs($user);
    setAdminPanelContext($tenant);

    $component = Livewire::test(CreateRestoreRun::class)
        ->fillForm([
            'backup_set_id' => $backupSet->id,
        ])
        ->goToNextWizardStep()
        ->fillForm([
            'scope_mode' => 'selected',
            'backup_item_ids' => [$backupItem->id],
        ])
        ->goToNextWizardStep()
        ->set('data.group_mapping', (object) [
            'source-group-1' => 'SKIP',
        ])
        ->callFormComponentAction('check_results', 'run_restore_checks');

    $summary = $component->get('data.check_summary');
    $results = $component->get('data.check_results');

    expect($summary)->toBeArray();
    expect($summary['blocking'] ?? null)->toBe(0);
    expect($summary['has_blockers'] ?? null)->toBeFalse();
    expect($summary['warning'] ?? null)->toBe(1);

    $assignmentCheck = collect($results)->firstWhere('code', 'assignment_groups');
    expect($assignmentCheck)->toBeArray();
    expect($assignmentCheck['severity'] ?? null)->toBe('warning');

    $skippedGroups = $assignmentCheck['meta']['skipped'] ?? [];
    expect($skippedGroups)->toBeArray();
    expect($skippedGroups[0]['id'] ?? null)->toBe('source-group-1');
});

test('restore wizard keeps prior checks evidence visible and marks it invalidated after scope drift', function () {
    $tenant = ManagedEnvironment::create([
        'managed_environment_id' => 'tenant-drift',
        'name' => 'ManagedEnvironment Drift',
        'metadata' => [],
        'rbac_status' => 'ok',
        'rbac_last_checked_at' => now(),
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');

    $policy = Policy::create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'policy-drift',
        'policy_type' => 'deviceConfiguration',
        'display_name' => 'Config Drift',
        'platform' => 'windows',
    ]);

    $backupSet = BackupSet::create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Backup',
        'status' => 'completed',
        'item_count' => 2,
    ]);

    $firstItem = BackupItem::create([
        'managed_environment_id' => $tenant->id,
        'backup_set_id' => $backupSet->id,
        'policy_id' => $policy->id,
        'policy_identifier' => 'policy-first',
        'policy_type' => $policy->policy_type,
        'platform' => $policy->platform,
        'payload' => ['id' => 'policy-first'],
    ]);

    $secondItem = BackupItem::create([
        'managed_environment_id' => $tenant->id,
        'backup_set_id' => $backupSet->id,
        'policy_id' => $policy->id,
        'policy_identifier' => 'policy-second',
        'policy_type' => $policy->policy_type,
        'platform' => $policy->platform,
        'payload' => ['id' => 'policy-second'],
    ]);

    $user = User::factory()->create();
    [$user, $tenant] = createUserWithTenant(tenant: $tenant, user: $user, role: 'owner');
    $this->actingAs($user);
    setAdminPanelContext($tenant);

    $component = Livewire::test(CreateRestoreRun::class)
        ->fillForm([
            'backup_set_id' => $backupSet->id,
        ])
        ->goToNextWizardStep()
        ->fillForm([
            'scope_mode' => 'selected',
            'backup_item_ids' => [$firstItem->id],
        ])
        ->goToNextWizardStep()
        ->callFormComponentAction('check_results', 'run_restore_checks');

    expect($component->get('data.check_summary'))->toBeArray();
    expect($component->get('data.check_basis'))->toBeArray();

    $component
        ->goToPreviousWizardStep()
        ->fillForm([
            'backup_item_ids' => [$secondItem->id],
        ])
        ->goToWizardStep(3)
        ->assertSee('Invalidated')
        ->assertSee('selected items changed');

    expect($component->get('data.check_summary'))->toBeArray();
    expect($component->get('data.check_invalidation_reasons'))->toContain('selected_items_changed');
});

test('restore wizard flags metadata-only snapshots as blocking for restore-enabled types', function () {
    $tenant = ManagedEnvironment::create([
        'managed_environment_id' => 'tenant-1',
        'name' => 'ManagedEnvironment One',
        'metadata' => [],
        'rbac_status' => 'ok',
        'rbac_last_checked_at' => now(),
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');

    $policy = Policy::create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'policy-1',
        'policy_type' => 'mamAppConfiguration',
        'display_name' => 'MAM App Config',
        'platform' => 'mobile',
    ]);

    $backupSet = BackupSet::create([
        'managed_environment_id' => $tenant->id,
        'name' => 'Backup',
        'status' => 'completed',
        'item_count' => 1,
    ]);

    $backupItem = BackupItem::create([
        'managed_environment_id' => $tenant->id,
        'backup_set_id' => $backupSet->id,
        'policy_id' => $policy->id,
        'policy_identifier' => $policy->external_id,
        'policy_type' => $policy->policy_type,
        'platform' => $policy->platform,
        'captured_at' => now(),
        'payload' => ['id' => $policy->external_id, 'displayName' => $policy->display_name],
        'assignments' => [],
        'metadata' => [
            'source' => 'metadata_only',
            'warnings' => [
                'Graph returned 500 for this policy type. Only local metadata was saved; settings and restore are unavailable until Graph works again.',
            ],
        ],
    ]);

    $this->mock(GroupResolver::class, function (MockInterface $mock) {
        $mock->shouldReceive('resolveGroupIds')
            ->andReturn([]);
    });

    $user = User::factory()->create();
    [$user, $tenant] = createUserWithTenant(tenant: $tenant, user: $user, role: 'owner');
    $this->actingAs($user);
    setAdminPanelContext($tenant);

    $component = Livewire::test(CreateRestoreRun::class)
        ->fillForm([
            'backup_set_id' => $backupSet->id,
        ])
        ->goToNextWizardStep()
        ->fillForm([
            'scope_mode' => 'selected',
            'backup_item_ids' => [$backupItem->id],
        ])
        ->goToNextWizardStep()
        ->callFormComponentAction('check_results', 'run_restore_checks');

    $summary = $component->get('data.check_summary');
    $results = $component->get('data.check_results');

    expect($summary['blocking'] ?? null)->toBe(1);
    expect($summary['has_blockers'] ?? null)->toBeTrue();

    $metadataOnly = collect($results)->firstWhere('code', 'metadata_only');
    expect($metadataOnly)->toBeArray();
    expect($metadataOnly['severity'] ?? null)->toBe('blocking');
});
