<?php

use App\Models\InventoryItem;
use App\Models\InventoryLink;
use App\Models\ManagedEnvironment;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphResponse;
use App\Services\Inventory\DependencyQueryService;
use App\Services\Inventory\InventorySyncService;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;

class FakeGraphClientForDeps implements GraphClientInterface
{
    public function listPolicies(string $policyType, array $options = []): GraphResponse
    {
        return new GraphResponse(true, [
            [
                'id' => 'pol-1',
                'displayName' => 'Test Policy',
                'assignments' => [
                    ['target' => ['groupId' => 'group-1']],
                    ['target' => ['groupId' => 'group-2']],
                ],
                'roleScopeTagIds' => ['scope-tag-1', 'scope-tag-2'],
            ],
        ], 200);
    }

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

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

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

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

    public function request(string $method, string $path, array $options = []): GraphResponse
    {
        return new GraphResponse(true, [], 200);
    }
}

it('extracts edges during inventory sync and marks missing appropriately', function () {
    $tenant = ManagedEnvironment::factory()->create();
    ensureDefaultProviderConnection($tenant);
    $this->app->bind(GraphClientInterface::class, fn () => new FakeGraphClientForDeps);

    $svc = app(InventorySyncService::class);

    $run = $svc->syncNow($tenant, [
        'policy_types' => ['deviceConfiguration'],
        'categories' => [],
        'include_foundations' => false,
        'include_dependencies' => true,
    ]);

    expect($run->status)->toBe(OperationRunStatus::Completed->value);
    expect($run->outcome)->toBe(OperationRunOutcome::Succeeded->value);

    $edges = InventoryLink::query()->where('managed_environment_id', $tenant->getKey())->get();
    // 2 assigned_to + 2 scoped_by = 4
    expect($edges->count())->toBe(4);
});

it('respects 50-edge limit for outbound extraction', function () {
    $tenant = ManagedEnvironment::factory()->create();
    ensureDefaultProviderConnection($tenant);
    // Fake client returning 60 group assignments
    $this->app->bind(GraphClientInterface::class, function () {
        return new class implements GraphClientInterface
        {
            public function listPolicies(string $policyType, array $options = []): GraphResponse
            {
                $assignments = [];
                for ($i = 1; $i <= 60; $i++) {
                    $assignments[] = ['target' => ['groupId' => 'g-'.$i]];
                }

                return new GraphResponse(true, [[
                    'id' => 'pol-2',
                    'displayName' => 'Big Assignments',
                    'assignments' => $assignments,
                ]]);
            }

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

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

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

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

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

    $svc = app(InventorySyncService::class);
    $svc->syncNow($tenant, [
        'policy_types' => ['deviceConfiguration'],
        'categories' => [],
        'include_foundations' => false,
        'include_dependencies' => true,
    ]);

    $count = InventoryLink::query()->where('managed_environment_id', $tenant->getKey())->count();
    expect($count)->toBe(50);
});

it('persists unsupported reference warnings on the sync run record', function () {
    $tenant = ManagedEnvironment::factory()->create();
    ensureDefaultProviderConnection($tenant);

    $this->app->bind(GraphClientInterface::class, function () {
        return new class implements GraphClientInterface
        {
            public function listPolicies(string $policyType, array $options = []): GraphResponse
            {
                return new GraphResponse(true, [[
                    'id' => 'pol-warn-1',
                    'displayName' => 'Unsupported Assignment Target',
                    'assignments' => [
                        ['target' => ['filterId' => 'filter-only-no-group']],
                    ],
                ]]);
            }

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

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

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

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

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

    $svc = app(InventorySyncService::class);
    $run = $svc->syncNow($tenant, [
        'policy_types' => ['deviceConfiguration'],
        'categories' => [],
        'include_foundations' => false,
        'include_dependencies' => true,
    ]);

    $warnings = $run->context['result']['error_context']['warnings'] ?? null;
    expect($warnings)->toBeArray()->toHaveCount(1);
    expect($warnings[0]['type'] ?? null)->toBe('unsupported_reference');

    expect(InventoryLink::query()->where('managed_environment_id', $tenant->getKey())->count())->toBe(0);
});

it('orders inbound/outbound edges by created_at desc and applies limit-only behavior', function () {
    $tenant = ManagedEnvironment::factory()->create();

    /** @var InventoryItem $item */
    $item = InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
    ]);

    $svc = app(DependencyQueryService::class);

    InventoryLink::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'source_type' => 'inventory_item',
        'source_id' => $item->external_id,
        'target_type' => 'foundation_object',
        'target_id' => '11111111-1111-1111-1111-111111111111',
        'relationship_type' => 'assigned_to',
        'created_at' => now()->subMinutes(10),
        'updated_at' => now()->subMinutes(10),
    ]);
    InventoryLink::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'source_type' => 'inventory_item',
        'source_id' => $item->external_id,
        'target_type' => 'foundation_object',
        'target_id' => '22222222-2222-2222-2222-222222222222',
        'relationship_type' => 'assigned_to',
        'created_at' => now()->subMinutes(5),
        'updated_at' => now()->subMinutes(5),
    ]);
    InventoryLink::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'source_type' => 'inventory_item',
        'source_id' => $item->external_id,
        'target_type' => 'foundation_object',
        'target_id' => '33333333-3333-3333-3333-333333333333',
        'relationship_type' => 'assigned_to',
        'created_at' => now()->subMinutes(1),
        'updated_at' => now()->subMinutes(1),
    ]);

    $outbound = $svc->getOutboundEdges($item, null, 2);
    expect($outbound)->toHaveCount(2);
    expect($outbound[0]->target_id)->toBe('33333333-3333-3333-3333-333333333333');
    expect($outbound[1]->target_id)->toBe('22222222-2222-2222-2222-222222222222');
    expect($outbound[0]->created_at->greaterThan($outbound[1]->created_at))->toBeTrue();

    InventoryLink::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'source_type' => 'inventory_item',
        'source_id' => 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
        'target_type' => 'inventory_item',
        'target_id' => $item->external_id,
        'relationship_type' => 'depends_on',
        'created_at' => now()->subMinutes(9),
        'updated_at' => now()->subMinutes(9),
    ]);
    InventoryLink::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'source_type' => 'inventory_item',
        'source_id' => 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
        'target_type' => 'inventory_item',
        'target_id' => $item->external_id,
        'relationship_type' => 'depends_on',
        'created_at' => now()->subMinutes(2),
        'updated_at' => now()->subMinutes(2),
    ]);
    InventoryLink::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'source_type' => 'inventory_item',
        'source_id' => 'cccccccc-cccc-cccc-cccc-cccccccccccc',
        'target_type' => 'inventory_item',
        'target_id' => $item->external_id,
        'relationship_type' => 'depends_on',
        'created_at' => now()->subMinutes(1),
        'updated_at' => now()->subMinutes(1),
    ]);

    $inbound = $svc->getInboundEdges($item, null, 2);
    expect($inbound)->toHaveCount(2);
    expect($inbound[0]->source_id)->toBe('cccccccc-cccc-cccc-cccc-cccccccccccc');
    expect($inbound[1]->source_id)->toBe('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb');
    expect($inbound[0]->created_at->greaterThan($inbound[1]->created_at))->toBeTrue();
});

it('hydrates settings catalog assignments and extracts include/exclude/filter edges', function () {
    $tenant = ManagedEnvironment::factory()->create();
    ensureDefaultProviderConnection($tenant);

    $this->app->bind(GraphClientInterface::class, function () {
        return new class implements GraphClientInterface
        {
            public function listPolicies(string $policyType, array $options = []): GraphResponse
            {
                return new GraphResponse(true, [[
                    'id' => 'sc-1',
                    'name' => 'Settings Catalog Policy',
                    'roleScopeTagIds' => ['scope-tag-1'],
                    // assignments present but empty (must still be hydrated via /assignments)
                    'assignments' => [],
                ]], 200);
            }

            public function request(string $method, string $path, array $options = []): GraphResponse
            {
                if ($method === 'GET' && $path === '/deviceManagement/configurationPolicies/sc-1/assignments') {
                    return new GraphResponse(true, [
                        'value' => [
                            [
                                'target' => [
                                    '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                                    'groupId' => 'group-inc-1',
                                ],
                            ],
                            [
                                'target' => [
                                    '@odata.type' => '#microsoft.graph.exclusionGroupAssignmentTarget',
                                    'groupId' => 'group-exc-1',
                                ],
                            ],
                            [
                                'target' => [
                                    '@odata.type' => '#microsoft.graph.groupAssignmentTarget',
                                    'groupId' => 'group-inc-2',
                                ],
                                'deviceAndAppManagementAssignmentFilterId' => 'filter-1',
                                'deviceAndAppManagementAssignmentFilterType' => 'include',
                            ],
                        ],
                    ], 200);
                }

                return new GraphResponse(true, [], 200);
            }

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

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

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

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

    $svc = app(InventorySyncService::class);

    $run = $svc->syncNow($tenant, [
        'policy_types' => ['settingsCatalogPolicy'],
        'categories' => [],
        'include_foundations' => false,
        'include_dependencies' => true,
    ]);

    expect($run->status)->toBe(OperationRunStatus::Completed->value);
    expect($run->outcome)->toBe(OperationRunOutcome::Succeeded->value);

    $edges = InventoryLink::query()->where('managed_environment_id', $tenant->getKey())->get();
    expect($edges->where('relationship_type', 'scoped_by'))->toHaveCount(1);
    expect($edges->where('relationship_type', 'assigned_to_include'))->toHaveCount(2);
    expect($edges->where('relationship_type', 'assigned_to_exclude'))->toHaveCount(1);
    expect($edges->where('relationship_type', 'uses_assignment_filter'))->toHaveCount(1);

    $filterEdge = $edges->firstWhere('relationship_type', 'uses_assignment_filter');
    expect($filterEdge)->not->toBeNull();
    expect($filterEdge->metadata['foundation_type'] ?? null)->toBe('assignment_filter');
    expect($filterEdge->metadata['filter_mode'] ?? null)->toBe('include');
});
