<?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\RestoreRiskChecker;
use App\Services\Intune\RestoreService;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

class EndpointSecurityRestoreGraphClient implements GraphClientInterface
{
    /** @var array<int, array{policyType:string,policyId:string,payload:array,options:array<string,mixed>}> */
    public array $applyPolicyCalls = [];

    /** @var array<int, array{method:string,path:string,options:array<string,mixed>}> */
    public array $requestCalls = [];

    /**
     * @param  array<string, GraphResponse>  $requestMap
     */
    public function __construct(
        private readonly GraphResponse $applyPolicyResponse,
        private readonly array $requestMap = [],
    ) {}

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

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

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

    public function applyPolicy(string $policyType, string $policyId, array $payload, array $options = []): GraphResponse
    {
        $this->applyPolicyCalls[] = [
            'policyType' => $policyType,
            'policyId' => $policyId,
            'payload' => $payload,
            'options' => $options,
        ];

        return $this->applyPolicyResponse;
    }

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

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

        foreach ($this->requestMap as $needle => $response) {
            if (is_string($needle) && $needle !== '' && str_contains($path, $needle)) {
                return $response;
            }
        }

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

test('restore executes endpoint security policy settings via settings endpoint', function () {
    $client = new EndpointSecurityRestoreGraphClient(new GraphResponse(true, []));
    app()->instance(GraphClientInterface::class, $client);

    $tenant = ManagedEnvironment::factory()->create();

    ensureDefaultProviderConnection($tenant);
    $backupSet = BackupSet::factory()->for($tenant)->create([
        'status' => 'completed',
        'item_count' => 1,
    ]);

    $backupItem = BackupItem::factory()->for($tenant)->for($backupSet)->create([
        'policy_id' => null,
        'policy_identifier' => 'esp-1',
        'policy_type' => 'endpointSecurityPolicy',
        'platform' => 'windows',
        'payload' => [
            'id' => 'esp-1',
            '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
            'name' => 'Endpoint Security Policy',
            'platforms' => ['windows10'],
            'technologies' => ['endpointSecurity'],
            'templateReference' => [
                'templateId' => 'template-1',
                'templateFamily' => 'endpointSecurityFirewall',
                'templateDisplayName' => 'Windows Firewall Rules',
                'templateDisplayVersion' => 'Version 1',
            ],
            'settings' => [
                [
                    'id' => 's1',
                    'settingInstance' => [
                        '@odata.type' => '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance',
                        'settingDefinitionId' => 'device_vendor_msft_policy_config_defender_allowrealtimemonitoring',
                        'simpleSettingValue' => [
                            'value' => 1,
                        ],
                    ],
                ],
            ],
        ],
        'assignments' => null,
    ]);

    $service = app(RestoreService::class);
    $run = $service->execute(
        tenant: $tenant,
        backupSet: $backupSet,
        selectedItemIds: [$backupItem->id],
        dryRun: false,
    );

    expect($run->status)->toBe('completed');
    expect($client->applyPolicyCalls)->toHaveCount(1);
    expect($client->applyPolicyCalls[0]['policyType'])->toBe('endpointSecurityPolicy');
    expect($client->applyPolicyCalls[0]['payload'])->not->toHaveKey('settings');

    $settingsCalls = collect($client->requestCalls)
        ->filter(fn (array $call) => $call['method'] === 'POST' && str_contains($call['path'], '/settings'))
        ->values();

    expect($settingsCalls)->toHaveCount(1);
    expect($settingsCalls[0]['path'])->toContain('deviceManagement/configurationPolicies/esp-1/settings');

    $body = $settingsCalls[0]['options']['json'] ?? null;
    expect($body)->toBeArray()->not->toBeEmpty();
    expect($body[0]['settingInstance']['settingDefinitionId'] ?? null)
        ->toBe('device_vendor_msft_policy_config_defender_allowrealtimemonitoring');
});

test('restore fails when endpoint security template is missing', function () {
    $applyNotFound = new GraphResponse(false, ['error' => ['message' => 'Not found']], 404, [], [], [
        'error_code' => 'NotFound',
        'error_message' => 'Not found',
    ]);

    $templateNotFound = new GraphResponse(false, ['error' => ['message' => 'Template missing']], 404, [], [], [
        'error_code' => 'NotFound',
        'error_message' => 'Template missing',
    ]);

    $client = new EndpointSecurityRestoreGraphClient($applyNotFound, [
        'configurationPolicyTemplates' => $templateNotFound,
    ]);
    app()->instance(GraphClientInterface::class, $client);

    $tenant = ManagedEnvironment::factory()->create();

    ensureDefaultProviderConnection($tenant);
    $backupSet = BackupSet::factory()->for($tenant)->create([
        'status' => 'completed',
        'item_count' => 1,
    ]);

    $backupItem = BackupItem::factory()->for($tenant)->for($backupSet)->create([
        'policy_id' => null,
        'policy_identifier' => 'esp-missing',
        'policy_type' => 'endpointSecurityPolicy',
        'platform' => 'windows',
        'payload' => [
            'id' => 'esp-missing',
            '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
            'name' => 'Endpoint Security Policy',
            'platforms' => ['windows10'],
            'technologies' => ['endpointSecurity'],
            'templateReference' => [
                'templateId' => 'missing-template',
            ],
            'settings' => [
                [
                    'id' => 's1',
                    'settingInstance' => [
                        '@odata.type' => '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance',
                        'settingDefinitionId' => 'device_vendor_msft_policy_config_defender_allowrealtimemonitoring',
                        'simpleSettingValue' => [
                            'value' => 1,
                        ],
                    ],
                ],
            ],
        ],
        'assignments' => null,
    ]);

    $service = app(RestoreService::class);
    $run = $service->execute(
        tenant: $tenant,
        backupSet: $backupSet,
        selectedItemIds: [$backupItem->id],
        dryRun: false,
    );

    expect($run->status)->toBe('failed');

    $createCalls = collect($client->requestCalls)
        ->filter(fn (array $call) => $call['method'] === 'POST' && $call['path'] === 'deviceManagement/configurationPolicies')
        ->values();

    expect($createCalls)->toHaveCount(0);
});

test('restore risk checks flag missing endpoint security templates as blocking', function () {
    $templateNotFound = new GraphResponse(false, ['error' => ['message' => 'Template missing']], 404, [], [], [
        'error_code' => 'NotFound',
        'error_message' => 'Template missing',
    ]);

    $client = new EndpointSecurityRestoreGraphClient(new GraphResponse(true, []), [
        'configurationPolicyTemplates' => $templateNotFound,
    ]);
    app()->instance(GraphClientInterface::class, $client);

    $tenant = ManagedEnvironment::factory()->create();

    ensureDefaultProviderConnection($tenant);
    $backupSet = BackupSet::factory()->for($tenant)->create([
        'status' => 'completed',
        'item_count' => 1,
    ]);

    $backupItem = BackupItem::factory()->for($tenant)->for($backupSet)->create([
        'policy_id' => null,
        'policy_identifier' => 'esp-missing',
        'policy_type' => 'endpointSecurityPolicy',
        'platform' => 'windows',
        'payload' => [
            'id' => 'esp-missing',
            '@odata.type' => '#microsoft.graph.deviceManagementConfigurationPolicy',
            'templateReference' => [
                'templateId' => 'missing-template',
                'templateFamily' => 'endpointSecurityFirewall',
            ],
            'settings' => [],
        ],
        'assignments' => null,
    ]);

    $checker = app(RestoreRiskChecker::class);
    $result = $checker->check(
        tenant: $tenant,
        backupSet: $backupSet,
        selectedItemIds: [$backupItem->id],
        groupMapping: [],
    );

    $results = collect($result['results'] ?? []);
    $templateCheck = $results->firstWhere('code', 'endpoint_security_templates');

    expect($templateCheck)->not->toBeNull();
    expect($templateCheck['severity'] ?? null)->toBe('blocking');
});
