<?php

use App\Models\Policy;
use App\Models\ManagedEnvironment;
use App\Services\Graph\AssignmentFetcher;
use App\Services\Graph\AssignmentFilterResolver;
use App\Services\Graph\GroupResolver;
use App\Services\Graph\ScopeTagResolver;
use App\Services\Intune\PolicySnapshotService;
use App\Services\Intune\VersionService;

beforeEach(function () {
    $this->tenant = ManagedEnvironment::factory()->create();
    ensureDefaultProviderConnection($this->tenant, 'microsoft');
    $this->policy = Policy::factory()->create([
        'managed_environment_id' => $this->tenant->id,
        'external_id' => 'test-policy-id',
    ]);

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

it('captures policy version with assignments from graph', function () {
    // Mock dependencies
    $this->mock(PolicySnapshotService::class, function ($mock) {
        $mock->shouldReceive('fetch')
            ->once()
            ->andReturn([
                'payload' => [
                    'id' => 'test-policy-id',
                    'name' => 'Test Policy',
                    'settings' => [],
                ],
            ]);
    });

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

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

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

    $versionService = app(VersionService::class);
    $version = $versionService->captureFromGraph(
        $this->tenant,
        $this->policy,
        'test@example.com'
    );

    expect($version)->not->toBeNull()
        ->and($version->assignments)->not->toBeNull()
        ->and($version->assignments)->toHaveCount(1)
        ->and($version->assignments[0]['target']['groupId'])->toBe('group-123')
        ->and($version->assignments_hash)->not->toBeNull()
        ->and($version->metadata['assignments_count'])->toBe(1)
        ->and($version->metadata['has_orphaned_assignments'])->toBeFalse()
        ->and($version->scope_tags)->toBe([
            'ids' => ['0'],
            'names' => ['Default'],
        ]);

    expect($version->assignments[0]['target']['group_display_name'])->toBe('Test Group');
    expect($version->assignments[0]['target']['assignment_filter_name'])->toBe('Targeted Devices');
});

it('captures enrollment limit configuration version with assignments from graph', function () {
    $this->policy->forceFill([
        'policy_type' => 'deviceEnrollmentLimitConfiguration',
        'platform' => 'all',
    ])->save();

    $this->mock(PolicySnapshotService::class, function ($mock) {
        $mock->shouldReceive('fetch')
            ->once()
            ->andReturn([
                'payload' => [
                    '@odata.type' => '#microsoft.graph.deviceEnrollmentLimitConfiguration',
                    'id' => 'test-policy-id',
                    'displayName' => 'Enrollment Limit',
                    'limit' => 5,
                ],
            ]);
    });

    $this->mock(AssignmentFetcher::class, function ($mock) {
        $mock->shouldReceive('fetch')
            ->once()
            ->withArgs(function (string $policyType): bool {
                return $policyType === 'deviceEnrollmentLimitConfiguration';
            })
            ->andReturn([
                [
                    'id' => 'assignment-1',
                    'intent' => 'apply',
                    'target' => [
                        '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                        'groupId' => 'group-123',
                    ],
                ],
            ]);
    });

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

    $this->mock(AssignmentFilterResolver::class, function ($mock) {
        $mock->shouldReceive('resolve')
            ->once()
            ->andReturn([]);
    });

    $versionService = app(VersionService::class);
    $version = $versionService->captureFromGraph(
        $this->tenant,
        $this->policy,
        'test@example.com'
    );

    expect($version)->not->toBeNull()
        ->and($version->policy_type)->toBe('deviceEnrollmentLimitConfiguration')
        ->and($version->snapshot['@odata.type'] ?? null)->toBe('#microsoft.graph.deviceEnrollmentLimitConfiguration')
        ->and($version->snapshot['limit'] ?? null)->toBe(5)
        ->and($version->assignments)->toHaveCount(1)
        ->and($version->metadata['assignments_count'])->toBe(1);
});

it('hydrates assignment filter names when filter data is stored at root', function () {
    $this->mock(PolicySnapshotService::class, function ($mock) {
        $mock->shouldReceive('fetch')
            ->once()
            ->andReturn([
                'payload' => [
                    'id' => 'test-policy-id',
                    'name' => 'Test Policy',
                    'settings' => [],
                ],
            ]);
    });

    $this->mock(AssignmentFetcher::class, function ($mock) {
        $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 ($mock) {
        $mock->shouldReceive('resolveGroupIds')
            ->once()
            ->andReturn([
                'group-123' => [
                    'id' => 'group-123',
                    'displayName' => 'Test Group',
                    'orphaned' => false,
                ],
            ]);
    });

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

    $versionService = app(VersionService::class);
    $version = $versionService->captureFromGraph(
        $this->tenant,
        $this->policy,
        'test@example.com'
    );

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

it('captures policy version without assignments when none exist', function () {
    // Mock dependencies
    $this->mock(PolicySnapshotService::class, function ($mock) {
        $mock->shouldReceive('fetch')
            ->once()
            ->andReturn([
                'payload' => [
                    'id' => 'test-policy-id',
                    'name' => 'Test Policy',
                    'settings' => [],
                ],
            ]);
    });

    $this->mock(AssignmentFetcher::class, function ($mock) {
        $mock->shouldReceive('fetch')
            ->once()
            ->andReturn([]);
    });

    $versionService = app(VersionService::class);
    $version = $versionService->captureFromGraph(
        $this->tenant,
        $this->policy,
        'test@example.com'
    );

    expect($version)->not->toBeNull()
        ->and($version->assignments)->toBeNull()
        ->and($version->assignments_hash)->toBeNull()
        ->and($version->metadata['assignments_fetched'])->toBeTrue()
        ->and($version->metadata['assignments_count'])->toBe(0);
});

it('handles assignment fetch failure gracefully', function () {
    // Mock dependencies
    $this->mock(PolicySnapshotService::class, function ($mock) {
        $mock->shouldReceive('fetch')
            ->once()
            ->andReturn([
                'payload' => [
                    'id' => 'test-policy-id',
                    'name' => 'Test Policy',
                    'settings' => [],
                ],
            ]);
    });

    $this->mock(AssignmentFetcher::class, function ($mock) {
        $mock->shouldReceive('fetch')
            ->once()
            ->andThrow(new \Exception('Graph API error'));
    });

    $versionService = app(VersionService::class);
    $version = $versionService->captureFromGraph(
        $this->tenant,
        $this->policy,
        'test@example.com'
    );

    expect($version)->not->toBeNull()
        ->and($version->assignments)->toBeNull()
        ->and($version->metadata['assignments_fetch_failed'])->toBeTrue()
        ->and($version->metadata['assignments_fetch_error'])->toBe('Graph API error');
});

it('calculates correct hash for assignments', function () {
    $assignments = [
        ['id' => '1', 'target' => ['groupId' => 'group-1']],
        ['id' => '2', 'target' => ['groupId' => 'group-2']],
    ];

    $version = $this->policy->versions()->create([
        'managed_environment_id' => $this->tenant->id,
        'policy_id' => $this->policy->id,
        'version_number' => 1,
        'policy_type' => 'deviceManagementConfigurationPolicy',
        'snapshot' => ['test' => 'data'],
        'assignments' => $assignments,
        'assignments_hash' => hash('sha256', json_encode($assignments)),
        'captured_at' => now(),
    ]);

    $expectedHash = hash('sha256', json_encode($assignments));

    expect($version->assignments_hash)->toBe($expectedHash);

    // Verify same assignments produce same hash
    $version2 = $this->policy->versions()->create([
        'managed_environment_id' => $this->tenant->id,
        'policy_id' => $this->policy->id,
        'version_number' => 2,
        'policy_type' => 'deviceManagementConfigurationPolicy',
        'snapshot' => ['test' => 'data'],
        'assignments' => $assignments,
        'assignments_hash' => hash('sha256', json_encode($assignments)),
        'captured_at' => now(),
    ]);

    expect($version2->assignments_hash)->toBe($version->assignments_hash);
});
