<?php

use App\Models\BackupItem;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\ManagedEnvironment;
use App\Services\Graph\AssignmentFetcher;
use App\Services\Graph\GroupResolver;
use App\Services\Intune\BackupService;
use App\Services\Intune\PolicySnapshotService;
use App\Support\Baselines\PolicyVersionCapturePurpose;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery\MockInterface;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->tenant = ManagedEnvironment::factory()->create(['status' => 'active']);

    ensureDefaultProviderConnection($this->tenant, 'microsoft');

    $this->policy = Policy::factory()->create([
        'managed_environment_id' => $this->tenant->id,
        'external_id' => 'test-policy-123',
        'policy_type' => 'settingsCatalogPolicy',
        'platform' => 'windows10',
        'display_name' => 'Test Policy',
    ]);

    $this->snapshotPayload = [
        '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
        'id' => 'test-policy-123',
        'name' => 'Test Policy',
        'description' => 'Test Description',
        'platforms' => 'windows10',
        'technologies' => 'mdm',
        'settings' => [],
    ];

    $this->assignmentsPayload = [
        [
            'id' => 'assignment-1',
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'group-123',
            ],
        ],
        [
            'id' => 'assignment-2',
            'target' => [
                '@odata.type' => '#microsoft.graph.allDevicesAssignmentTarget',
            ],
        ],
    ];

    $this->resolvedAssignments = [
        [
            'id' => 'assignment-1',
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'group-123',
                'group_name' => 'Test Group',
            ],
        ],
        [
            'id' => 'assignment-2',
            'target' => [
                '@odata.type' => '#microsoft.graph.allDevicesAssignmentTarget',
            ],
        ],
    ];

    // Mock PolicySnapshotService
    $this->mock(PolicySnapshotService::class, function (MockInterface $mock) {
        $mock->shouldReceive('fetch')
            ->andReturn([
                'payload' => $this->snapshotPayload,
                'metadata' => ['fetched_at' => now()->toISOString()],
                'warnings' => [],
            ]);
    });

    // Mock AssignmentFetcher
    $this->mock(AssignmentFetcher::class, function (MockInterface $mock) {
        $mock->shouldReceive('fetch')
            ->andReturn($this->assignmentsPayload);
    });

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

it('creates backup with includeAssignments=true and both BackupItem and PolicyVersion have assignments', function () {
    $backupService = app(BackupService::class);

    $backupSet = $backupService->createBackupSet(
        tenant: $this->tenant,
        policyIds: [$this->policy->id],
        actorEmail: 'test@example.com',
        actorName: 'Test User',
        name: 'Test Backup With Assignments',
        includeAssignments: true,
    );

    expect($backupSet)->not->toBeNull();
    expect($backupSet->items)->toHaveCount(1);

    $backupItem = $backupSet->items->first();
    expect($backupItem->assignments)->not->toBeNull();
    expect($backupItem->assignments)->toBeArray();
    expect($backupItem->assignments)->toHaveCount(2);
    expect($backupItem->assignments[0]['target']['groupId'])->toBe('group-123');

    // CRITICAL: PolicyVersion must also have assignments (domain consistency)
    expect($backupItem->policy_version_id)->not->toBeNull();
    $version = PolicyVersion::find($backupItem->policy_version_id);
    expect($version)->not->toBeNull();
    expect($version->assignments)->not->toBeNull();
    expect($version->assignments)->toBeArray();
    expect($version->assignments)->toHaveCount(2);
    expect($version->assignments[0]['target']['groupId'])->toBe('group-123');

    // Verify assignments match between BackupItem and PolicyVersion
    expect($backupItem->assignments)->toEqual($version->assignments);
});

it('creates backup with includeAssignments=false and both BackupItem and PolicyVersion have no assignments', function () {
    $backupService = app(BackupService::class);

    $backupSet = $backupService->createBackupSet(
        tenant: $this->tenant,
        policyIds: [$this->policy->id],
        actorEmail: 'test@example.com',
        actorName: 'Test User',
        name: 'Test Backup Without Assignments',
        includeAssignments: false,
    );

    expect($backupSet)->not->toBeNull();
    expect($backupSet->items)->toHaveCount(1);

    $backupItem = $backupSet->items->first();
    expect($backupItem->assignments)->toBeNull();

    // CRITICAL: PolicyVersion must also have no assignments (domain consistency)
    expect($backupItem->policy_version_id)->not->toBeNull();
    $version = PolicyVersion::find($backupItem->policy_version_id);
    expect($version)->not->toBeNull();
    expect($version->assignments)->toBeNull();
});

it('backfills existing PolicyVersion without assignments when creating backup with includeAssignments=true', function () {
    // Create an existing PolicyVersion without assignments (simulate old backup)
    $existingVersion = PolicyVersion::create([
        'policy_id' => $this->policy->id,
        'managed_environment_id' => $this->tenant->id,
        'version_number' => 1,
        'capture_purpose' => PolicyVersionCapturePurpose::Backup,
        'policy_type' => 'settingsCatalogPolicy',
        'platform' => 'windows10',
        'snapshot' => $this->snapshotPayload,
        'assignments' => null, // NO ASSIGNMENTS
        'scope_tags' => null,
        'assignments_hash' => null,
        'scope_tags_hash' => null,
        'created_by' => 'legacy-system@example.com',
    ]);

    expect($existingVersion->assignments)->toBeNull();
    expect($existingVersion->assignments_hash)->toBeNull();

    $backupService = app(BackupService::class);

    // Create new backup with includeAssignments=true
    // Orchestrator should detect existing version and backfill it
    $backupSet = $backupService->createBackupSet(
        tenant: $this->tenant,
        policyIds: [$this->policy->id],
        actorEmail: 'test@example.com',
        actorName: 'Test User',
        name: 'Test Backup Backfills Version',
        includeAssignments: true,
    );

    expect($backupSet)->not->toBeNull();
    expect($backupSet->items)->toHaveCount(1);

    $backupItem = $backupSet->items->first();

    // BackupItem should have assignments
    expect($backupItem->assignments)->not->toBeNull();
    expect($backupItem->assignments)->toHaveCount(2);

    // CRITICAL: Existing PolicyVersion should now be backfilled (idempotent)
    // The orchestrator should have detected same payload_hash and enriched it
    $existingVersion->refresh();
    expect($existingVersion->assignments)->not->toBeNull();
    expect($existingVersion->assignments)->toHaveCount(2);
    expect($existingVersion->assignments_hash)->not->toBeNull();
    expect($existingVersion->assignments[0]['target']['groupId'])->toBe('group-123');

    // BackupItem should reference the backfilled version
    expect($backupItem->policy_version_id)->toBe($existingVersion->id);
});

it('does not overwrite existing PolicyVersion assignments when they already exist (idempotent)', function () {
    // Create an existing PolicyVersion WITH assignments
    $existingAssignments = [
        [
            'id' => 'old-assignment',
            'target' => ['@odata.type' => '#microsoft.graph.allLicensedUsersAssignmentTarget'],
        ],
    ];

    $existingVersion = PolicyVersion::create([
        'policy_id' => $this->policy->id,
        'managed_environment_id' => $this->tenant->id,
        'version_number' => 1,
        'capture_purpose' => PolicyVersionCapturePurpose::Backup,
        'policy_type' => 'settingsCatalogPolicy',
        'platform' => 'windows10',
        'snapshot' => $this->snapshotPayload,
        'assignments' => $existingAssignments,
        'scope_tags' => null,
        'assignments_hash' => hash('sha256', json_encode($existingAssignments)),
        'scope_tags_hash' => null,
        'created_by' => 'previous-backup@example.com',
    ]);

    $backupService = app(BackupService::class);

    // Create new backup - orchestrator should NOT overwrite existing assignments
    $backupSet = $backupService->createBackupSet(
        tenant: $this->tenant,
        policyIds: [$this->policy->id],
        actorEmail: 'test@example.com',
        actorName: 'Test User',
        name: 'Test Backup Preserves Existing',
        includeAssignments: true,
    );

    expect($backupSet)->not->toBeNull();
    expect($backupSet->items)->toHaveCount(1);

    $backupItem = $backupSet->items->first();

    // BackupItem should have NEW assignments (from current fetch)
    expect($backupItem->assignments)->not->toBeNull();
    expect($backupItem->assignments)->toHaveCount(2);
    expect($backupItem->assignments[0]['target']['groupId'])->toBe('group-123');

    // CRITICAL: Existing PolicyVersion should NOT be modified (idempotent)
    $existingVersion->refresh();
    expect($existingVersion->assignments)->toEqual($existingAssignments);
    expect($existingVersion->assignments)->toHaveCount(1);
    expect($existingVersion->assignments[0]['id'])->toBe('old-assignment');

    // BackupItem should reference the existing version (reused)
    expect($backupItem->policy_version_id)->toBe($existingVersion->id);
});
