<?php

declare(strict_types=1);

use App\Services\Graph\GraphContractRegistry;
use App\Services\Graph\GraphLogger;
use App\Services\Graph\MicrosoftGraphClient;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

it('includes contract select fields when listing policies', function () {
    Http::fake([
        'graph.microsoft.com/*' => Http::response(['value' => []], 200),
    ]);

    $logger = mock(GraphLogger::class);
    $logger->shouldReceive('logRequest')->zeroOrMoreTimes()->andReturnNull();
    $logger->shouldReceive('logResponse')->zeroOrMoreTimes()->andReturnNull();

    $client = new MicrosoftGraphClient(
        logger: $logger,
        contracts: app(GraphContractRegistry::class),
    );

    $client->listPolicies('endpointSecurityPolicy', [
        'access_token' => 'test-token',
    ]);

    $client->listPolicies('securityBaselinePolicy', [
        'access_token' => 'test-token',
    ]);

    $client->listPolicies('settingsCatalogPolicy', [
        'access_token' => 'test-token',
    ]);

    Http::assertSent(function (Request $request) {
        $url = $request->url();

        if (! str_contains($url, '/deviceManagement/configurationPolicies')) {
            return false;
        }

        parse_str((string) parse_url($url, PHP_URL_QUERY), $query);

        expect($query)->toHaveKey('$select');

        $select = (string) $query['$select'];

        expect($select)->toContain('technologies')
            ->and($select)->toContain('templateReference')
            ->and($select)->toContain('name')
            ->and($select)->not->toContain('@odata.type');

        expect($select)->not->toContain('displayName');
        expect($select)->not->toContain('version');

        return true;
    });
});

it('retries list policies without $select on select/expand parsing errors', function () {
    Http::fake([
        'graph.microsoft.com/*' => Http::sequence()
            ->push([
                'error' => [
                    'code' => 'BadRequest',
                    'message' => "Parsing OData Select and Expand failed: Could not find a property named 'version' on type 'microsoft.graph.deviceManagementConfigurationPolicy'.",
                ],
            ], 400)
            ->push([
                'error' => [
                    'code' => 'BadRequest',
                    'message' => "Parsing OData Select and Expand failed: Could not find a property named 'version' on type 'microsoft.graph.deviceManagementConfigurationPolicy'.",
                ],
            ], 400)
            ->push(['value' => [['id' => 'policy-1', 'name' => 'Policy One']]], 200),
    ]);

    $logger = mock(GraphLogger::class);
    $logger->shouldReceive('logRequest')->zeroOrMoreTimes()->andReturnNull();
    $logger->shouldReceive('logResponse')->zeroOrMoreTimes()->andReturnNull();

    $client = new MicrosoftGraphClient(
        logger: $logger,
        contracts: app(GraphContractRegistry::class),
    );

    $response = $client->listPolicies('settingsCatalogPolicy', [
        'access_token' => 'test-token',
    ]);

    expect($response->successful())->toBeTrue();
    expect($response->data)->toHaveCount(1);
    expect($response->warnings)->toContain('Capability fallback applied: removed $select for compatibility.');

    $recorded = Http::recorded();

    expect($recorded)->toHaveCount(3);

    [$firstRequest] = $recorded[0];
    [$secondRequest] = $recorded[1];
    [$thirdRequest] = $recorded[2];

    parse_str((string) parse_url($firstRequest->url(), PHP_URL_QUERY), $firstQuery);
    parse_str((string) parse_url($secondRequest->url(), PHP_URL_QUERY), $secondQuery);
    parse_str((string) parse_url($thirdRequest->url(), PHP_URL_QUERY), $thirdQuery);

    expect($firstQuery)->toHaveKey('$select');
    expect($secondQuery)->toHaveKey('$select');
    expect($thirdQuery)->not->toHaveKey('$select');
});

it('paginates list policies when nextLink is present', function () {
    $nextLink = 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies?$skiptoken=page2';

    Http::fake([
        'graph.microsoft.com/*' => Http::sequence()
            ->push([
                'value' => [
                    ['id' => 'policy-1', 'name' => 'Policy One'],
                ],
                '@odata.nextLink' => $nextLink,
            ], 200)
            ->push([
                'value' => [
                    ['id' => 'policy-2', 'name' => 'Policy Two'],
                ],
            ], 200),
    ]);

    $logger = mock(GraphLogger::class);
    $logger->shouldReceive('logRequest')->zeroOrMoreTimes()->andReturnNull();
    $logger->shouldReceive('logResponse')->zeroOrMoreTimes()->andReturnNull();

    $client = new MicrosoftGraphClient(
        logger: $logger,
        contracts: app(GraphContractRegistry::class),
    );

    $response = $client->listPolicies('settingsCatalogPolicy', [
        'access_token' => 'test-token',
    ]);

    expect($response->successful())->toBeTrue();
    expect($response->data)->toHaveCount(2);
    expect(collect($response->data)->pluck('id')->all())->toMatchArray(['policy-1', 'policy-2']);

    $recorded = Http::recorded();

    expect($recorded)->toHaveCount(2);

    [$firstRequest] = $recorded[0];
    [$secondRequest] = $recorded[1];

    expect($firstRequest->url())->toContain('/deviceManagement/configurationPolicies');
    expect($secondRequest->url())->toBe($nextLink);
});

it('forwards allowlisted $expand when explicitly provided', function () {
    Http::fake([
        'graph.microsoft.com/*' => Http::response(['value' => []], 200),
    ]);

    $logger = mock(GraphLogger::class);
    $logger->shouldReceive('logRequest')->zeroOrMoreTimes()->andReturnNull();
    $logger->shouldReceive('logResponse')->zeroOrMoreTimes()->andReturnNull();

    $client = new MicrosoftGraphClient(
        logger: $logger,
        contracts: app(GraphContractRegistry::class),
    );

    $client->listPolicies('entraRoleAssignments', [
        'access_token' => 'test-token',
        'expand' => 'principal',
    ]);

    Http::assertSent(function (Request $request) {
        $url = $request->url();

        if (! str_contains($url, '/roleManagement/directory/roleAssignments')) {
            return false;
        }

        parse_str((string) parse_url($url, PHP_URL_QUERY), $query);

        expect($query['$expand'] ?? null)->toBe('principal');

        return true;
    });
});

it('omits $expand when not explicitly provided', function () {
    Http::fake([
        'graph.microsoft.com/*' => Http::response(['value' => []], 200),
    ]);

    $logger = mock(GraphLogger::class);
    $logger->shouldReceive('logRequest')->zeroOrMoreTimes()->andReturnNull();
    $logger->shouldReceive('logResponse')->zeroOrMoreTimes()->andReturnNull();

    $client = new MicrosoftGraphClient(
        logger: $logger,
        contracts: app(GraphContractRegistry::class),
    );

    $client->listPolicies('entraRoleAssignments', [
        'access_token' => 'test-token',
    ]);

    Http::assertSent(function (Request $request) {
        $url = $request->url();

        if (! str_contains($url, '/roleManagement/directory/roleAssignments')) {
            return false;
        }

        parse_str((string) parse_url($url, PHP_URL_QUERY), $query);

        expect($query)->not->toHaveKey('$expand');

        return true;
    });
});
