<?php

use App\Models\BackupItem;
use App\Models\ManagedEnvironment;
use App\Services\AssignmentBackupService;
use App\Services\Graph\AssignmentFetcher;
use App\Services\Graph\AssignmentFilterResolver;
use App\Services\Graph\GroupResolver;
use App\Services\Graph\ScopeTagResolver;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery\MockInterface;

uses(RefreshDatabase::class);
it('enriches assignment filter names when filter data is stored at root', function () {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'tenant-123',
        'external_id' => 'tenant-123',
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');

    $backupItem = BackupItem::factory()->create([
        'managed_environment_id' => $tenant->id,
        'metadata' => [],
        'assignments' => null,
    ]);

    $policyPayload = [
        'roleScopeTagIds' => ['0'],
    ];

    $this->mock(AssignmentFetcher::class, function (MockInterface $mock) {
        $mock->shouldReceive('supportsStandardAssignments')
            ->once()
            ->with('settingsCatalogPolicy', null)
            ->andReturnTrue();

        $mock->shouldReceive('fetch')
            ->once()
            ->andReturn([
                [
                    'id' => 'assignment-1',
                    'intent' => 'apply',
                    'deviceAndAppManagementAssignmentFilterId' => 'filter-123',
                    'deviceAndAppManagementAssignmentFilterType' => 'include',
                    'target' => [
                        '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                        'groupId' => 'group-123',
                    ],
                ],
            ]);
    });

    $this->mock(GroupResolver::class, function (MockInterface $mock) {
        $mock->shouldReceive('resolveGroupIds')
            ->once()
            ->andReturn([
                'group-123' => [
                    'id' => 'group-123',
                    'displayName' => 'Test Group',
                    'orphaned' => false,
                ],
            ]);
    });

    $this->mock(AssignmentFilterResolver::class, function (MockInterface $mock) use ($tenant) {
        $mock->shouldReceive('resolve')
            ->once()
            ->with(['filter-123'], $tenant)
            ->andReturn([
                ['id' => 'filter-123', 'displayName' => 'Targeted Devices'],
            ]);
    });

    $this->mock(ScopeTagResolver::class, function (MockInterface $mock) use ($tenant) {
        $mock->shouldReceive('resolve')
            ->once()
            ->with(['0'], $tenant)
            ->andReturn([
                ['id' => '0', 'displayName' => 'Default'],
            ]);
    });

    $service = app(AssignmentBackupService::class);
    $updated = $service->enrichWithAssignments(
        backupItem: $backupItem,
        tenant: $tenant,
        policyType: 'settingsCatalogPolicy',
        policyId: 'policy-123',
        policyPayload: $policyPayload,
        includeAssignments: true
    );

    expect($updated->assignments)->toHaveCount(1)
        ->and($updated->assignments[0]['target']['deviceAndAppManagementAssignmentFilterId'])->toBe('filter-123')
        ->and($updated->assignments[0]['target']['deviceAndAppManagementAssignmentFilterType'])->toBe('include')
        ->and($updated->assignments[0]['target']['assignment_filter_name'])->toBe('Targeted Devices');
});

it('marks role definitions as not using standard policy assignments', function () {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'tenant-123',
        'external_id' => 'tenant-123',
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');

    $backupItem = BackupItem::factory()->create([
        'managed_environment_id' => $tenant->id,
        'metadata' => [
            'assignments_fetch_failed' => true,
            'assignments_fetch_error' => 'old error',
        ],
        'assignments' => null,
    ]);

    $this->mock(AssignmentFetcher::class, function (MockInterface $mock) {
        $mock->shouldReceive('supportsStandardAssignments')
            ->once()
            ->with('intuneRoleDefinition', '#microsoft.graph.roleDefinition')
            ->andReturnFalse();

        $mock->shouldReceive('fetch')->never();
    });

    $this->mock(ScopeTagResolver::class, function (MockInterface $mock) use ($tenant) {
        $mock->shouldReceive('resolve')
            ->once()
            ->with(['0'], $tenant)
            ->andReturn([
                ['id' => '0', 'displayName' => 'Default'],
            ]);
    });

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

    $this->mock(AssignmentFilterResolver::class, function (MockInterface $mock) {
        $mock->shouldReceive('resolve')->never();
    });

    $service = app(AssignmentBackupService::class);
    $updated = $service->enrichWithAssignments(
        backupItem: $backupItem,
        tenant: $tenant,
        policyType: 'intuneRoleDefinition',
        policyId: 'role-def-1',
        policyPayload: [
            '@odata.type' => '#microsoft.graph.roleDefinition',
            'roleScopeTagIds' => ['0'],
        ],
        includeAssignments: true,
    );

    expect($updated->assignments)->toBe([])
        ->and(data_get($updated->metadata, 'assignments_not_applicable'))->toBeTrue()
        ->and(data_get($updated->metadata, 'assignment_capture_reason'))->toBe('separate_role_assignments')
        ->and(data_get($updated->metadata, 'assignments_fetch_failed'))->toBeFalse()
        ->and(data_get($updated->metadata, 'assignments_fetch_error'))->toBeNull();
});

it('records orphaned assignment metadata when resolved groups are missing in the tenant', function () {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'tenant-123',
        'external_id' => 'tenant-123',
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');

    $backupItem = BackupItem::factory()->create([
        'managed_environment_id' => $tenant->id,
        'metadata' => [],
        'assignments' => null,
    ]);

    $this->mock(AssignmentFetcher::class, function (MockInterface $mock) {
        $mock->shouldReceive('supportsStandardAssignments')
            ->once()
            ->with('settingsCatalogPolicy', null)
            ->andReturnTrue();

        $mock->shouldReceive('fetch')
            ->once()
            ->andReturn([
                [
                    'id' => 'assignment-1',
                    'intent' => 'apply',
                    'target' => [
                        '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                        'groupId' => 'group-123',
                    ],
                ],
            ]);
    });

    $this->mock(GroupResolver::class, function (MockInterface $mock) {
        $mock->shouldReceive('resolveGroupIds')
            ->once()
            ->andReturn([
                'group-123' => [
                    'id' => 'group-123',
                    'displayName' => 'Missing Group',
                    'orphaned' => true,
                ],
            ]);
    });

    $this->mock(AssignmentFilterResolver::class, function (MockInterface $mock) {
        $mock->shouldReceive('resolve')
            ->once()
            ->with([], \Mockery::type(ManagedEnvironment::class))
            ->andReturn([]);
    });

    $this->mock(ScopeTagResolver::class, function (MockInterface $mock) use ($tenant) {
        $mock->shouldReceive('resolve')
            ->once()
            ->with(['0'], $tenant)
            ->andReturn([
                ['id' => '0', 'displayName' => 'Default'],
            ]);
    });

    $updated = app(AssignmentBackupService::class)->enrichWithAssignments(
        backupItem: $backupItem,
        tenant: $tenant,
        policyType: 'settingsCatalogPolicy',
        policyId: 'policy-123',
        policyPayload: [
            'roleScopeTagIds' => ['0'],
        ],
        includeAssignments: true,
    );

    expect($updated->assignments)->toHaveCount(1)
        ->and(data_get($updated->metadata, 'assignments_fetch_failed'))->toBeFalse()
        ->and(data_get($updated->metadata, 'has_orphaned_assignments'))->toBeTrue();
});
