<?php

use App\Models\ManagedEnvironment;
use App\Services\AssignmentRestoreService;
use App\Services\Graph\AssignmentFilterResolver;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphContractRegistry;
use App\Services\Graph\GraphLogger;
use App\Services\Graph\GraphResponse;
use App\Services\Providers\MicrosoftGraphOptionsResolver;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function () {
    config()->set('graph_contracts.types.deviceManagementScript', [
        'assignments_create_path' => '/deviceManagement/deviceManagementScripts/{id}/assign',
        'assignments_create_method' => 'POST',
        'assignments_payload_key' => 'deviceManagementScriptAssignments',
    ]);
    config()->set('graph_contracts.types.settingsCatalogPolicy', [
        'assignments_create_path' => '/deviceManagement/configurationPolicies/{id}/assign',
        'assignments_create_method' => 'POST',
    ]);
    config()->set('graph_contracts.types.appProtectionPolicy', [
        'assignments_create_path' => '/deviceAppManagement/managedAppPolicies/{id}/assign',
        'assignments_create_method' => 'POST',
        'assignments_payload_key' => 'assignments',
    ]);

    $this->graphClient = Mockery::mock(GraphClientInterface::class);
    $this->filterResolver = Mockery::mock(AssignmentFilterResolver::class);
    $this->filterResolver->shouldReceive('resolve')->andReturn([])->byDefault();

    $this->service = new AssignmentRestoreService(
        $this->graphClient,
        app(GraphContractRegistry::class),
        app(GraphLogger::class),
        $this->filterResolver,
        app(MicrosoftGraphOptionsResolver::class),
    );
});

it('uses the contract assignment payload key for assign actions', function () {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'tenant-123',
        'app_client_id' => null,
        'app_client_secret' => null,
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');
    $policyId = 'policy-123';
    $assignments = [
        [
            'id' => 'assignment-1',
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'group-1',
            ],
        ],
    ];
    $expectedAssignments = [
        [
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'group-1',
            ],
        ],
    ];

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('POST', "/deviceManagement/deviceManagementScripts/{$policyId}/assign", Mockery::on(
            fn (array $options) => ($options['json']['deviceManagementScriptAssignments'] ?? null) === $expectedAssignments
        ))
        ->andReturn(new GraphResponse(success: true, data: []));

    $result = $this->service->restore(
        $tenant,
        'deviceManagementScript',
        $policyId,
        $assignments,
        []
    );

    expect($result['summary']['success'])->toBe(1);
    expect($result['summary']['failed'])->toBe(0);
    expect($result['summary']['skipped'])->toBe(0);
});

it('uses derived assign endpoints for app protection policies', function () {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'tenant-123',
        'app_client_id' => null,
        'app_client_secret' => null,
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');
    $policyId = 'policy-123';
    $assignments = [
        [
            'id' => 'assignment-1',
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'group-1',
            ],
        ],
    ];

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('POST', "/deviceAppManagement/androidManagedAppProtections/{$policyId}/assign", Mockery::on(
            fn (array $options) => isset($options['json']['assignments'])
        ))
        ->andReturn(new GraphResponse(success: true, data: []));

    $result = $this->service->restore(
        $tenant,
        'appProtectionPolicy',
        $policyId,
        $assignments,
        [],
        [],
        null,
        null,
        null,
        '#microsoft.graph.androidManagedAppProtection',
    );

    expect($result['summary']['success'])->toBe(1);
    expect($result['summary']['failed'])->toBe(0);
    expect($result['summary']['skipped'])->toBe(0);
});

it('maps assignment filter ids stored at the root of assignments', function () {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'tenant-123',
        'app_client_id' => null,
        'app_client_secret' => null,
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');
    $policyId = 'policy-789';
    $assignments = [
        [
            'id' => 'assignment-1',
            'deviceAndAppManagementAssignmentFilterId' => 'filter-source',
            'deviceAndAppManagementAssignmentFilterType' => 'include',
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'group-1',
            ],
        ],
    ];
    $expectedAssignments = [
        [
            'deviceAndAppManagementAssignmentFilterId' => 'filter-target',
            'deviceAndAppManagementAssignmentFilterType' => 'include',
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'group-1',
            ],
        ],
    ];

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('POST', "/deviceManagement/configurationPolicies/{$policyId}/assign", Mockery::on(
            fn (array $options) => ($options['json']['assignments'] ?? null) === $expectedAssignments
        ))
        ->andReturn(new GraphResponse(success: true, data: []));

    $result = $this->service->restore(
        $tenant,
        'settingsCatalogPolicy',
        $policyId,
        $assignments,
        [],
        [
            'assignmentFilter' => [
                'filter-source' => 'filter-target',
            ],
        ]
    );

    expect($result['summary']['success'])->toBe(1);
    expect($result['summary']['failed'])->toBe(0);
    expect($result['summary']['skipped'])->toBe(0);
});

it('keeps assignment filters when mapping is missing but filter exists in target', function () {
    $tenant = ManagedEnvironment::factory()->create([
        'managed_environment_id' => 'tenant-123',
        'app_client_id' => null,
        'app_client_secret' => null,
    ]);

    ensureDefaultProviderConnection($tenant, 'microsoft');
    $policyId = 'policy-999';
    $assignments = [
        [
            'id' => 'assignment-1',
            'deviceAndAppManagementAssignmentFilterId' => 'filter-1',
            'deviceAndAppManagementAssignmentFilterType' => 'include',
            'target' => [
                '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                'groupId' => 'group-1',
            ],
        ],
    ];

    $this->filterResolver
        ->shouldReceive('resolve')
        ->once()
        ->with(['filter-1'], $tenant)
        ->andReturn([['id' => 'filter-1', 'displayName' => 'Test']]);

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('POST', "/deviceManagement/configurationPolicies/{$policyId}/assign", Mockery::on(
            fn (array $options) => ($options['json']['assignments'][0]['deviceAndAppManagementAssignmentFilterId'] ?? null) === 'filter-1'
        ))
        ->andReturn(new GraphResponse(success: true, data: []));

    $result = $this->service->restore(
        $tenant,
        'settingsCatalogPolicy',
        $policyId,
        $assignments,
        [],
        []
    );

    expect($result['summary']['success'])->toBe(1);
    expect($result['summary']['failed'])->toBe(0);
    expect($result['summary']['skipped'])->toBe(0);
});
