<?php

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

uses(RefreshDatabase::class);

class FoundationMappingGraphClient implements GraphClientInterface
{
    public array $requests = [];

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

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

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

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

    public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
    {
        return new GraphResponse(success: true, data: []);
    }

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

    public function request(string $method, string $path, array $options = []): GraphResponse
    {
        $this->requests[] = [
            'method' => $method,
            'path' => $path,
            'options' => $options,
        ];

        return array_shift($this->responses) ?? new GraphResponse(success: true, data: []);
    }
}

it('maps existing foundations by display name', function () {
    $tenant = ManagedEnvironment::factory()->create();

    ensureDefaultProviderConnection($tenant, 'microsoft');
    $backupSet = BackupSet::factory()->for($tenant)->create();
    $item = BackupItem::factory()
        ->for($tenant)
        ->for($backupSet)
        ->state([
            'policy_id' => null,
            'policy_identifier' => 'filter-1',
            'policy_type' => 'assignmentFilter',
            'platform' => 'all',
            'payload' => [
                'id' => 'filter-1',
                'displayName' => 'Filter One',
            ],
            'metadata' => [
                'displayName' => 'Filter One',
            ],
        ])
        ->create();

    $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
        $mock->shouldReceive('fetchAll')
            ->once()
            ->andReturn([
                'items' => [
                    [
                        'source_id' => 'filter-2',
                        'display_name' => 'Filter One',
                        'payload' => [],
                        'metadata' => [],
                    ],
                ],
                'failures' => [],
            ]);
    });

    $client = new FoundationMappingGraphClient;
    app()->instance(GraphClientInterface::class, $client);

    $service = app(FoundationMappingService::class);
    $result = $service->map($tenant, collect([$item]), false);

    expect($result['failed'])->toBe(0);
    expect($result['skipped'])->toBe(0);
    expect($result['mapping'])->toBe(['filter-1' => 'filter-2']);
    expect($result['entries'])->toHaveCount(1);
    expect($result['entries'][0]['decision'])->toBe('mapped_existing');
    expect($result['entries'][0]['targetId'])->toBe('filter-2');
    expect($result['entries'][0]['sourceName'])->toBe('Filter One');
});

it('creates missing foundations when executing', function () {
    config()->set('graph_contracts.types.assignmentFilter', [
        'resource' => 'deviceManagement/assignmentFilters',
        'create_method' => 'POST',
        'update_strip_keys' => ['isBuiltIn'],
    ]);

    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'tenant-1',
        'app_client_id' => 'client-1',
        'app_client_secret' => 'secret-1',
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');
    $backupSet = BackupSet::factory()->for($tenant)->create();
    $item = BackupItem::factory()
        ->for($tenant)
        ->for($backupSet)
        ->state([
            'policy_id' => null,
            'policy_identifier' => 'filter-1',
            'policy_type' => 'assignmentFilter',
            'platform' => 'all',
            'payload' => [
                'id' => 'filter-1',
                '@odata.type' => '#microsoft.graph.deviceAndAppManagementAssignmentFilter',
                'displayName' => 'Filter One',
                'isBuiltIn' => false,
            ],
            'metadata' => [
                'displayName' => 'Filter One',
            ],
        ])
        ->create();

    $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
        $mock->shouldReceive('fetchAll')
            ->once()
            ->andReturn([
                'items' => [
                    [
                        'source_id' => 'filter-2',
                        'display_name' => 'Filter One',
                        'payload' => [],
                        'metadata' => [],
                    ],
                    [
                        'source_id' => 'filter-3',
                        'display_name' => 'Filter One',
                        'payload' => [],
                        'metadata' => [],
                    ],
                ],
                'failures' => [],
            ]);
    });

    $client = new FoundationMappingGraphClient([
        new GraphResponse(true, [
            'id' => 'filter-99',
            'displayName' => 'Filter One (Copy)',
        ]),
    ]);
    app()->instance(GraphClientInterface::class, $client);

    $service = app(FoundationMappingService::class);
    $result = $service->map($tenant, collect([$item]), true);

    expect($result['mapping'])->toBe(['filter-1' => 'filter-99']);
    expect($result['entries'][0]['decision'])->toBe('created_copy');
    expect($result['entries'][0]['targetName'])->toBe('Filter One (Copy)');
    expect($client->requests)->toHaveCount(1);
    expect($client->requests[0]['method'])->toBe('POST');
    expect($client->requests[0]['path'])->toBe('deviceManagement/assignmentFilters');

    $payload = $client->requests[0]['options']['json'];
    expect($payload['displayName'])->toBe('Filter One (Copy)');
    expect($payload)->not->toHaveKey('id');
    expect($payload)->not->toHaveKey('@odata.type');
    expect($payload)->not->toHaveKey('isBuiltIn');
});

it('skips built-in scope tags', function () {
    $tenant = ManagedEnvironment::factory()->create();

    ensureDefaultProviderConnection($tenant, 'microsoft');
    $backupSet = BackupSet::factory()->for($tenant)->create();
    $item = BackupItem::factory()
        ->for($tenant)
        ->for($backupSet)
        ->state([
            'policy_id' => null,
            'policy_identifier' => '0',
            'policy_type' => 'roleScopeTag',
            'platform' => 'all',
            'payload' => [
                'id' => '0',
                'displayName' => 'Default',
                'isBuiltIn' => true,
            ],
            'metadata' => [
                'displayName' => 'Default',
            ],
        ])
        ->create();

    $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
        $mock->shouldReceive('fetchAll')
            ->once()
            ->andReturn([
                'items' => [],
                'failures' => [],
            ]);
    });

    $client = new FoundationMappingGraphClient;
    app()->instance(GraphClientInterface::class, $client);

    $service = app(FoundationMappingService::class);
    $result = $service->map($tenant, collect([$item]), false);

    expect($result['skipped'])->toBe(1);
    expect($result['entries'][0]['decision'])->toBe('skipped');
    expect($result['entries'][0]['reason'])->toBe('Built-in scope tag cannot be created.');
});

it('marks failures when foundation listing fails', function () {
    $tenant = ManagedEnvironment::factory()->create();
    $backupSet = BackupSet::factory()->for($tenant)->create();
    $item = BackupItem::factory()
        ->for($tenant)
        ->for($backupSet)
        ->state([
            'policy_id' => null,
            'policy_identifier' => 'filter-1',
            'policy_type' => 'assignmentFilter',
            'platform' => 'all',
            'payload' => [
                'id' => 'filter-1',
                'displayName' => 'Filter One',
            ],
            'metadata' => [
                'displayName' => 'Filter One',
            ],
        ])
        ->create();

    $this->mock(FoundationSnapshotService::class, function (MockInterface $mock) {
        $mock->shouldReceive('fetchAll')
            ->once()
            ->andReturn([
                'items' => [],
                'failures' => [
                    [
                        'foundation_type' => 'assignmentFilter',
                        'reason' => 'Graph failure',
                        'status' => 500,
                    ],
                ],
            ]);
    });

    $client = new FoundationMappingGraphClient;
    app()->instance(GraphClientInterface::class, $client);

    $service = app(FoundationMappingService::class);
    $result = $service->map($tenant, collect([$item]), false);

    expect($result['failed'])->toBe(1);
    expect($result['entries'][0]['decision'])->toBe('failed');
    expect($result['entries'][0]['reason'])->toBe('Graph failure');
});
