<?php

use App\Services\Graph\AssignmentFetcher;
use App\Services\Graph\GraphContractRegistry;
use App\Services\Graph\GraphException;
use App\Services\Graph\GraphResponse;
use App\Services\Graph\MicrosoftGraphClient;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);
beforeEach(function () {
    $this->graphClient = Mockery::mock(MicrosoftGraphClient::class);
    $this->fetcher = new AssignmentFetcher($this->graphClient, app(GraphContractRegistry::class));
});

test('primary endpoint success', function () {
    $tenantId = 'tenant-123';
    $policyId = 'policy-456';
    $policyType = 'settingsCatalogPolicy';
    $assignments = [
        ['id' => 'assign-1', 'target' => ['@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'group-1']],
        ['id' => 'assign-2', 'target' => ['@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'group-2']],
    ];

    $response = new GraphResponse(
        success: true,
        data: ['value' => $assignments]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('GET', "/deviceManagement/configurationPolicies/{$policyId}/assignments", [
            'tenant' => $tenantId,
        ])
        ->andReturn($response);

    $result = $this->fetcher->fetch($policyType, $tenantId, $policyId);

    expect($result)->toBe($assignments);
});

test('app protection uses derived assignments list endpoint', function () {
    $tenantId = 'tenant-123';
    $policyId = 'policy-456';
    $policyType = 'appProtectionPolicy';
    $assignments = [
        ['id' => 'assign-1', 'target' => ['@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'group-1']],
    ];

    $response = new GraphResponse(
        success: true,
        data: ['value' => $assignments]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('GET', "/deviceAppManagement/androidManagedAppProtections/{$policyId}/assignments", [
            'tenant' => $tenantId,
        ])
        ->andReturn($response);

    $result = $this->fetcher->fetch(
        $policyType,
        $tenantId,
        $policyId,
        [],
        false,
        '#microsoft.graph.androidManagedAppProtection'
    );

    expect($result)->toBe($assignments);
});

test('returns empty without calling Graph when standard assignments do not apply', function () {
    $tenantId = 'tenant-123';
    $policyId = 'role-def-456';
    $policyType = 'intuneRoleDefinition';

    $this->graphClient
        ->shouldReceive('request')
        ->never();

    $result = $this->fetcher->fetch($policyType, $tenantId, $policyId, [], true, '#microsoft.graph.roleDefinition');

    expect($result)->toBe([]);
});

test('does not use fallback when primary succeeds with empty assignments', function () {
    $tenantId = 'tenant-123';
    $policyId = 'policy-456';
    $policyType = 'settingsCatalogPolicy';

    $primaryResponse = new GraphResponse(
        success: true,
        data: ['value' => []]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('GET', "/deviceManagement/configurationPolicies/{$policyId}/assignments", [
            'tenant' => $tenantId,
        ])
        ->andReturn($primaryResponse);

    $result = $this->fetcher->fetch($policyType, $tenantId, $policyId);

    expect($result)->toBe([]);
});

test('uses fallback when primary endpoint fails', function () {
    $tenantId = 'tenant-123';
    $policyId = 'policy-456';
    $policyType = 'settingsCatalogPolicy';
    $assignments = [
        ['id' => 'assign-1', 'target' => ['@odata.type' => '#microsoft.graph.groupAssignmentTarget', 'groupId' => 'group-1']],
    ];

    $primaryFailure = new GraphResponse(
        success: false,
        data: [],
        status: 400,
        errors: [['message' => 'Bad Request']]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('GET', "/deviceManagement/configurationPolicies/{$policyId}/assignments", [
            'tenant' => $tenantId,
        ])
        ->andReturn($primaryFailure);

    $fallbackResponse = new GraphResponse(
        success: true,
        data: ['value' => [['id' => $policyId, 'assignments' => $assignments]]]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('GET', 'deviceManagement/configurationPolicies', [
            'tenant' => $tenantId,
            'query' => [
                '$expand' => 'assignments',
                '$filter' => "id eq '{$policyId}'",
            ],
        ])
        ->andReturn($fallbackResponse);

    $result = $this->fetcher->fetch($policyType, $tenantId, $policyId);

    expect($result)->toBe($assignments);
});

test('fail soft on error', function () {
    $tenantId = 'tenant-123';
    $policyId = 'policy-456';
    $policyType = 'settingsCatalogPolicy';

    $this->graphClient
        ->shouldReceive('request')
        ->twice()
        ->andThrow(new GraphException('Graph API error', 500, ['request_id' => 'request-id-123']));

    $result = $this->fetcher->fetch($policyType, $tenantId, $policyId);

    expect($result)->toBe([]);
});

test('returns empty array when both endpoints return empty', function () {
    $tenantId = 'tenant-123';
    $policyId = 'policy-456';
    $policyType = 'settingsCatalogPolicy';

    // Primary returns empty
    $primaryResponse = new GraphResponse(
        success: true,
        data: ['value' => []]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('GET', "/deviceManagement/configurationPolicies/{$policyId}/assignments", Mockery::any())
        ->andReturn($primaryResponse);

    $result = $this->fetcher->fetch($policyType, $tenantId, $policyId);

    expect($result)->toBe([]);
});

test('fallback handles missing assignments key', function () {
    $tenantId = 'tenant-123';
    $policyId = 'policy-456';
    $policyType = 'settingsCatalogPolicy';

    $primaryResponse = new GraphResponse(
        success: false,
        data: ['value' => []]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->andReturn($primaryResponse);

    // Fallback returns policy without assignments key
    $fallbackResponse = new GraphResponse(
        success: true,
        data: ['value' => [['id' => $policyId]]]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->andReturn($fallbackResponse);

    $result = $this->fetcher->fetch($policyType, $tenantId, $policyId);

    expect($result)->toBe([]);
});

test('throws when both endpoints fail with throwOnFailure enabled', function () {
    $tenantId = 'tenant-123';
    $policyId = 'policy-456';
    $policyType = 'settingsCatalogPolicy';

    $failureResponse = new GraphResponse(
        success: false,
        data: [],
        status: 403,
        errors: [['message' => 'Forbidden']]
    );

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('GET', "/deviceManagement/configurationPolicies/{$policyId}/assignments", Mockery::any())
        ->andReturn($failureResponse);

    $this->graphClient
        ->shouldReceive('request')
        ->once()
        ->with('GET', 'deviceManagement/configurationPolicies', Mockery::any())
        ->andReturn($failureResponse);

    $this->fetcher->fetch($policyType, $tenantId, $policyId, [], true);
})->throws(GraphException::class);
