<?php

use App\Models\BackupItem;
use App\Models\BackupSet;
use App\Models\Policy;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\Intune\FoundationMappingService;
use App\Services\Intune\RestoreService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery\MockInterface;

uses(RefreshDatabase::class);

class RestoreAssignmentGraphClient implements GraphClientInterface
{
    /**
     * @var array<int, array{method:string,path:string,payload:array|null}>
     */
    public array $requestCalls = [];

    /**
     * @param  array<int, GraphResponse>  $requestResponses
     */
    public function __construct(
        private readonly GraphResponse $applyPolicyResponse,
        private array $requestResponses = [],
    ) {}

    public function listPolicies(string $policyType, array $options = []): GraphResponse
    {
        return new GraphResponse(true, []);
    }

    public function getPolicy(string $policyType, string $policyId, array $options = []): GraphResponse
    {
        return new GraphResponse(true, ['payload' => []]);
    }

    public function getOrganization(array $options = []): GraphResponse
    {
        return new GraphResponse(true, []);
    }

    public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
    {
        return $this->applyPolicyResponse;
    }

    public function getServicePrincipalPermissions(array $options = []): GraphResponse
    {
        return new GraphResponse(true, []);
    }

    public function request(string $method, string $path, array $options = []): GraphResponse
    {
        $this->requestCalls[] = [
            'method' => strtoupper($method),
            'path' => $path,
            'payload' => $options['json'] ?? null,
        ];

        return array_shift($this->requestResponses) ?? new GraphResponse(true, []);
    }
}

test('restore applies assignments with mapped groups', function () {
    $applyResponse = new GraphResponse(true, []);
    $requestResponses = [
        new GraphResponse(true, []), // assign action
    ];

    $client = new RestoreAssignmentGraphClient($applyResponse, $requestResponses);
    app()->instance(GraphClientInterface::class, $client);

    $tenant = ManagedEnvironment::create([
        'managed_environment_id' => 'tenant-1',
        'name' => 'ManagedEnvironment One',
        'metadata' => [],
    ]);

    ensureDefaultProviderConnection($tenant);

    $policy = Policy::create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'scp-1',
        'policy_type' => 'settingsCatalogPolicy',
        'display_name' => 'Settings Catalog Alpha',
        '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,
        'payload' => ['id' => $policy->external_id, '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy'],
        'assignments' => [
            [
                'id' => 'assignment-1',
                'intent' => 'apply',
                'target' => [
                    '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                    'groupId' => 'source-group-1',
                    'group_display_name' => 'Source One',
                ],
            ],
            [
                'id' => 'assignment-2',
                'intent' => 'apply',
                'target' => [
                    '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                    'groupId' => 'source-group-2',
                ],
            ],
        ],
    ]);

    $user = User::factory()->create(['email' => 'tester@example.com']);
    $this->actingAs($user);

    $service = app(RestoreService::class);
    $run = $service->execute(
        tenant: $tenant,
        backupSet: $backupSet,
        selectedItemIds: [$backupItem->id],
        dryRun: false,
        actorEmail: $user->email,
        actorName: $user->name,
        groupMapping: [
            'source-group-1' => 'target-group-1',
            'source-group-2' => 'target-group-2',
        ],
    );

    $result = $run->results['items'][$backupItem->id] ?? null;
    expect($result)->not->toBeNull();

    $summary = $result['assignment_summary'] ?? null;

    expect($summary)->not->toBeNull();
    expect($summary['success'])->toBe(2);
    expect($summary['failed'])->toBe(0);

    $postCalls = collect($client->requestCalls)
        ->filter(fn (array $call) => $call['method'] === 'POST')
        ->values();

    expect($postCalls)->toHaveCount(1);
    expect($postCalls[0]['path'])->toBe('/deviceManagement/configurationPolicies/scp-1/assign');

    $payloadAssignments = $postCalls[0]['payload']['assignments'] ?? [];
    $groupIds = collect($payloadAssignments)->pluck('target.groupId')->all();

    expect($groupIds)->toBe(['target-group-1', 'target-group-2']);
    expect($payloadAssignments[0])->not->toHaveKey('id');
});

test('restore handles assignment failures gracefully', function () {
    $applyResponse = new GraphResponse(true, []);
    $requestResponses = [
        new GraphResponse(false, ['error' => ['message' => 'Bad request']], 400, [
            ['code' => 'BadRequest', 'message' => 'Bad request'],
        ], [], [
            'error_code' => 'BadRequest',
            'error_message' => 'Bad request',
        ]), // assign action fails
    ];

    $client = new RestoreAssignmentGraphClient($applyResponse, $requestResponses);
    app()->instance(GraphClientInterface::class, $client);

    $tenant = ManagedEnvironment::create([
        'managed_environment_id' => 'tenant-1',
        'name' => 'ManagedEnvironment One',
        'metadata' => [],
    ]);

    ensureDefaultProviderConnection($tenant);

    $policy = Policy::create([
        'managed_environment_id' => $tenant->id,
        'external_id' => 'scp-1',
        'policy_type' => 'settingsCatalogPolicy',
        'display_name' => 'Settings Catalog Alpha',
        '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,
        'payload' => ['id' => $policy->external_id, '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy'],
        'assignments' => [
            [
                'id' => 'assignment-1',
                'intent' => 'apply',
                'target' => [
                    '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                    'groupId' => 'source-group-1',
                ],
            ],
            [
                'id' => 'assignment-2',
                'intent' => 'apply',
                'target' => [
                    '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                    'groupId' => 'source-group-2',
                ],
            ],
        ],
    ]);

    $user = User::factory()->create(['email' => 'tester@example.com']);
    $this->actingAs($user);

    $service = app(RestoreService::class);
    $run = $service->execute(
        tenant: $tenant,
        backupSet: $backupSet,
        selectedItemIds: [$backupItem->id],
        dryRun: false,
        actorEmail: $user->email,
        actorName: $user->name,
        groupMapping: [
            'source-group-1' => 'target-group-1',
            'source-group-2' => 'target-group-2',
        ],
    );

    $result = $run->results['items'][$backupItem->id] ?? null;
    expect($result)->not->toBeNull();

    $summary = $result['assignment_summary'] ?? null;

    expect($summary)->not->toBeNull();
    expect($summary['success'])->toBe(0);
    expect($summary['failed'])->toBe(2);
    expect($result['status'] ?? null)->toBe('partial');
});

test('restore maps assignment filter identifiers', function () {
    $applyResponse = new GraphResponse(true, []);
    $requestResponses = [
        new GraphResponse(true, []), // assign action
    ];

    $client = new RestoreAssignmentGraphClient($applyResponse, $requestResponses);
    app()->instance(GraphClientInterface::class, $client);

    $tenant = ManagedEnvironment::create([
        'managed_environment_id' => 'tenant-1',
        'name' => 'ManagedEnvironment One',
        'metadata' => [],
    ]);

    ensureDefaultProviderConnection($tenant);

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

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

    $foundationItem = BackupItem::create([
        'managed_environment_id' => $tenant->id,
        'backup_set_id' => $backupSet->id,
        'policy_id' => null,
        'policy_identifier' => 'filter-old',
        'policy_type' => 'assignmentFilter',
        'platform' => 'all',
        'payload' => ['id' => 'filter-old', 'displayName' => 'Filter Old'],
        'metadata' => ['displayName' => 'Filter Old'],
    ]);

    $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,
        'payload' => ['id' => $policy->external_id, '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy'],
        'assignments' => [
            [
                'id' => 'assignment-1',
                'intent' => 'apply',
                'target' => [
                    '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                    'groupId' => 'source-group-1',
                    'deviceAndAppManagementAssignmentFilterId' => 'filter-old',
                    'deviceAndAppManagementAssignmentFilterType' => 'include',
                ],
            ],
        ],
    ]);

    $entries = [
        [
            'type' => 'assignmentFilter',
            'sourceId' => 'filter-old',
            'sourceName' => 'Filter Old',
            'decision' => 'created',
            'targetId' => 'filter-new',
            'targetName' => 'Filter Old',
        ],
    ];

    $this->mock(FoundationMappingService::class, function (MockInterface $mock) use ($entries) {
        $mock->shouldReceive('map')
            ->twice()
            ->andReturn([
                'entries' => $entries,
                'mapping' => ['filter-old' => 'filter-new'],
                'failed' => 0,
                'skipped' => 0,
            ]);
    });

    $user = User::factory()->create(['email' => 'tester@example.com']);
    $this->actingAs($user);

    $service = app(RestoreService::class);
    $service->execute(
        tenant: $tenant,
        backupSet: $backupSet,
        selectedItemIds: [$foundationItem->id, $backupItem->id],
        dryRun: false,
        actorEmail: $user->email,
        actorName: $user->name,
        groupMapping: [
            'source-group-1' => 'target-group-1',
        ],
    );

    $payloadAssignments = $client->requestCalls[0]['payload']['assignments'] ?? [];

    expect($payloadAssignments[0]['target']['deviceAndAppManagementAssignmentFilterId'])->toBe('filter-new');
});
