<?php

namespace App\Services\Intune;

use App\Models\Policy;
use App\Models\ProviderConnection;
use App\Models\ManagedEnvironment;
use App\Services\Graph\GraphClientInterface;
use App\Services\Graph\GraphContractRegistry;
use App\Services\Graph\GraphErrorMapper;
use App\Services\Graph\GraphLogger;
use App\Services\Graph\GraphResponse;
use App\Services\Providers\ProviderConnectionResolver;
use App\Services\Providers\ProviderGateway;
use App\Support\Providers\ProviderReasonCodes;
use Illuminate\Support\Arr;
use RuntimeException;
use Throwable;

class PolicySnapshotService
{
    public function __construct(
        private readonly GraphClientInterface $graphClient,
        private readonly GraphLogger $graphLogger,
        private readonly GraphContractRegistry $contracts,
        private readonly SnapshotValidator $snapshotValidator,
        private readonly SettingsCatalogDefinitionResolver $definitionResolver,
        private readonly ?ProviderConnectionResolver $providerConnections = null,
        private readonly ?ProviderGateway $providerGateway = null,
    ) {}

    /**
     * Fetch a policy snapshot from Graph (with optional hydration) for backup/version capture.
     *
     * @return array{payload:array,metadata:array,warnings:array}|array{failure:array}
     */
    public function fetch(ManagedEnvironment $tenant, Policy $policy, ?string $actorEmail = null): array
    {
        $tenantIdentifier = $tenant->managed_environment_id ?? $tenant->external_id;
        $connection = null;
        $graphOptions = [];

        $context = [
            'tenant' => $tenantIdentifier,
            'policy_type' => $policy->policy_type,
            'policy_id' => $policy->external_id,
        ];

        $this->graphLogger->logRequest('get_policy', $context);

        try {
            $connection = $this->resolveProviderConnection($tenant);
            $tenantIdentifier = (string) $connection->entra_tenant_id;
            $context['tenant'] = $tenantIdentifier;
            $context['provider_connection_id'] = (int) $connection->getKey();
            $graphOptions = $this->providerGateway()->graphOptions($connection);

            $options = ['platform' => $policy->platform] + $graphOptions;

            if ($this->isMetadataOnlyPolicyType($policy->policy_type)) {
                $select = $this->metadataOnlySelect($policy->policy_type);

                if ($select !== []) {
                    $options['select'] = $select;
                }
            }

            if ($policy->policy_type === 'deviceCompliancePolicy') {
                $options['expand'] = 'scheduledActionsForRule($expand=scheduledActionConfigurations)';
            }

            $response = $this->graphClient->getPolicy($policy->policy_type, $policy->external_id, $options);
        } catch (Throwable $throwable) {
            $mapped = GraphErrorMapper::fromThrowable($throwable, $context);

            // For certain policy types experiencing upstream Graph issues, fall back to metadata-only
            if ($this->shouldFallbackToMetadata($policy->policy_type, $mapped->status)) {
                return $this->createMetadataOnlySnapshot($policy, $mapped->getMessage(), $mapped->status);
            }

            return [
                'failure' => [
                    'policy_id' => $policy->id,
                    'reason' => $mapped->getMessage(),
                    'status' => $mapped->status,
                ],
            ];
        }

        $this->graphLogger->logResponse('get_policy', $response, $context);

        $payload = $response->data['payload'] ?? $response->data;
        $metadata = Arr::except($response->data, ['payload']);
        $metadataWarnings = $metadata['warnings'] ?? [];

        if ($policy->policy_type === 'windowsUpdateRing') {
            [$payload, $metadata] = $this->hydrateWindowsUpdateRing(
                graphOptions: $graphOptions,
                policyId: $policy->external_id,
                payload: is_array($payload) ? $payload : [],
                metadata: $metadata,
            );
        }

        if (in_array($policy->policy_type, ['settingsCatalogPolicy', 'endpointSecurityPolicy', 'securityBaselinePolicy'], true)) {
            [$payload, $metadata] = $this->hydrateConfigurationPolicySettings(
                policyType: $policy->policy_type,
                graphOptions: $graphOptions,
                policyId: $policy->external_id,
                payload: is_array($payload) ? $payload : [],
                metadata: $metadata
            );
        }

        if ($policy->policy_type === 'groupPolicyConfiguration') {
            [$payload, $metadata] = $this->hydrateGroupPolicyConfiguration(
                graphOptions: $graphOptions,
                policyId: $policy->external_id,
                payload: is_array($payload) ? $payload : [],
                metadata: $metadata
            );
        }

        if ($policy->policy_type === 'deviceCompliancePolicy') {
            [$payload, $metadata] = $this->hydrateComplianceActions(
                graphOptions: $graphOptions,
                policyId: $policy->external_id,
                payload: is_array($payload) ? $payload : [],
                metadata: $metadata
            );
        }

        if ($policy->policy_type === 'deviceEnrollmentNotificationConfiguration') {
            [$payload, $metadata] = $this->hydrateEnrollmentNotificationTemplates(
                graphOptions: $graphOptions,
                payload: is_array($payload) ? $payload : [],
                metadata: $metadata
            );
        }

        if ($response->failed()) {
            $reason = $this->formatGraphFailureReason($response);

            if ($this->shouldFallbackToMetadata($policy->policy_type, $response->status)) {
                return $this->createMetadataOnlySnapshot($policy, $reason, $response->status);
            }

            $failure = [
                'policy_id' => $policy->id,
                'reason' => $reason,
                'status' => $response->status,
            ];

            if (! config('graph.stub_on_failure')) {
                return ['failure' => $failure];
            }

            $payload = [
                'id' => $policy->external_id,
                'type' => $policy->policy_type,
                'source' => 'stub',
                'warning' => $reason,
            ];
            $metadataWarnings = $response->warnings ?? [$reason];
        }

        if (! $response->failed() && $this->isMetadataOnlyPolicyType($policy->policy_type)) {
            $payload = $this->filterMetadataOnlyPayload($policy->policy_type, is_array($payload) ? $payload : []);
        }

        $validation = $this->snapshotValidator->validate(is_array($payload) ? $payload : []);
        $metadataWarnings = array_merge($metadataWarnings, $validation['warnings']);

        $odataWarning = Policy::odataTypeWarning(is_array($payload) ? $payload : [], $policy->policy_type, $policy->platform);

        if ($odataWarning) {
            $metadataWarnings[] = $odataWarning;
        }

        if (! empty($metadataWarnings)) {
            $metadata['warnings'] = array_values(array_unique($metadataWarnings));
        }

        return [
            'payload' => is_array($payload) ? $payload : [],
            'metadata' => $metadata,
            'warnings' => $metadataWarnings,
        ];
    }

    private function formatGraphFailureReason(GraphResponse $response): string
    {
        $code = $response->meta['error_code']
            ?? ($response->errors[0]['code'] ?? null)
            ?? ($response->data['error']['code'] ?? null);

        $message = $response->meta['error_message']
            ?? ($response->errors[0]['message'] ?? null)
            ?? ($response->data['error']['message'] ?? null)
            ?? ($response->warnings[0] ?? null);

        $reason = 'Graph request failed';

        if (is_string($message) && $message !== '') {
            $reason = $message;
        }

        if (is_string($code) && $code !== '') {
            $reason = sprintf('%s: %s', $code, $reason);
        }

        $requestId = $response->meta['request_id'] ?? null;
        $clientRequestId = $response->meta['client_request_id'] ?? null;

        $suffixParts = [];

        if (is_string($clientRequestId) && $clientRequestId !== '') {
            $suffixParts[] = sprintf('client_request_id=%s', $clientRequestId);
        }

        if (is_string($requestId) && $requestId !== '') {
            $suffixParts[] = sprintf('request_id=%s', $requestId);
        }

        if ($suffixParts !== []) {
            $reason = sprintf('%s (%s)', $reason, implode(', ', $suffixParts));
        }

        return $reason;
    }

    /**
     * Hydrate Windows Update Ring payload via derived type cast to capture
     * windowsUpdateForBusinessConfiguration-specific properties.
     *
     * @return array{0:array,1:array}
     */
    private function hydrateWindowsUpdateRing(array $graphOptions, string $policyId, array $payload, array $metadata): array
    {
        $odataType = $payload['@odata.type'] ?? null;
        $castSegment = $this->deriveTypeCastSegment($odataType);

        if ($castSegment === null) {
            $metadata['properties_hydration'] = 'skipped';

            return [$payload, $metadata];
        }

        $castPath = sprintf('deviceManagement/deviceConfigurations/%s/%s', urlencode($policyId), $castSegment);

        $response = $this->graphClient->request('GET', $castPath, Arr::except($graphOptions, ['platform']));

        if ($response->failed() || ! is_array($response->data)) {
            $metadata['properties_hydration'] = 'failed';

            return [$payload, $metadata];
        }

        $metadata['properties_hydration'] = 'complete';

        return [array_merge($payload, $response->data), $metadata];
    }

    private function deriveTypeCastSegment(mixed $odataType): ?string
    {
        if (! is_string($odataType) || $odataType === '') {
            return null;
        }

        if (! str_starts_with($odataType, '#')) {
            return null;
        }

        $segment = ltrim($odataType, '#');

        return $segment !== '' ? $segment : null;
    }

    private function isMetadataOnlyPolicyType(string $policyType): bool
    {
        foreach (config('tenantpilot.supported_policy_types', []) as $type) {
            if (($type['type'] ?? null) === $policyType) {
                return ($type['backup'] ?? null) === 'metadata-only';
            }
        }

        return false;
    }

    /**
     * @return array<int, string>
     */
    private function metadataOnlySelect(string $policyType): array
    {
        $contract = $this->contracts->get($policyType);
        $allowedSelect = $contract['allowed_select'] ?? [];

        if (! is_array($allowedSelect)) {
            return [];
        }

        return array_values(array_filter(
            $allowedSelect,
            static fn (mixed $key) => is_string($key) && $key !== '@odata.type'
        ));
    }

    private function filterMetadataOnlyPayload(string $policyType, array $payload): array
    {
        $contract = $this->contracts->get($policyType);
        $allowedSelect = $contract['allowed_select'] ?? [];

        if (! is_array($allowedSelect) || $allowedSelect === []) {
            return $payload;
        }

        $filtered = [];

        foreach ($allowedSelect as $key) {
            if (is_string($key) && array_key_exists($key, $payload)) {
                $filtered[$key] = $payload[$key];
            }
        }

        return $filtered;
    }

    /**
     * Hydrate configurationPolicies settings via settings subresource (Settings Catalog / Endpoint Security / Baselines).
     *
     * @return array{0:array,1:array}
     */
    private function hydrateConfigurationPolicySettings(string $policyType, array $graphOptions, string $policyId, array $payload, array $metadata): array
    {
        $strategy = $this->contracts->memberHydrationStrategy($policyType);
        $settingsPath = $this->contracts->subresourceSettingsPath($policyType, $policyId);

        if ($strategy !== 'subresource_settings' || ! $settingsPath) {
            return [$payload, $metadata];
        }

        $settings = [];
        $nextPath = $settingsPath;
        $hydrationStatus = 'complete';

        while ($nextPath) {
            $response = $this->graphClient->request('GET', $nextPath, Arr::except($graphOptions, ['platform']));

            if ($response->failed()) {
                $hydrationStatus = 'failed';

                break;
            }

            $data = $response->data;
            $pageItems = $data['value'] ?? (is_array($data) ? $data : []);
            $settings = array_merge($settings, $pageItems);
            $nextLink = $data['@odata.nextLink'] ?? null;

            if (! $nextLink) {
                break;
            }

            $nextPath = $this->stripGraphBaseUrl((string) $nextLink);
        }

        if (! empty($settings)) {
            $payload['settings'] = $settings;

            // Extract definition IDs and warm cache (T008-T010)
            $definitionIds = $this->extractDefinitionIds($settings);
            $metadata['definition_count'] = count($definitionIds);

            // Warm cache for definitions (non-blocking)
            $this->definitionResolver->warmCache($definitionIds);
            $metadata['definitions_cached'] = true;
        }

        $metadata['settings_hydration'] = $hydrationStatus;

        return [$payload, $metadata];
    }

    /**
     * Hydrate Administrative Templates (Group Policy Configurations) with definitionValues and presentationValues.
     *
     * @return array{0:array,1:array}
     */
    private function hydrateGroupPolicyConfiguration(array $graphOptions, string $policyId, array $payload, array $metadata): array
    {
        $strategy = $this->contracts->memberHydrationStrategy('groupPolicyConfiguration');
        $definitionValuesPath = $this->contracts->subresourcePath('groupPolicyConfiguration', 'definitionValues', [
            '{id}' => $policyId,
        ]);

        if ($strategy !== 'subresource_definition_values' || ! $definitionValuesPath) {
            return [$payload, $metadata];
        }

        $graphBase = rtrim((string) config('graph.base_url', 'https://graph.microsoft.com'), '/')
            .'/'.trim((string) config('graph.version', 'beta'), '/');
        $definitionValues = [];
        $nextPath = $definitionValuesPath;
        $hydrationStatus = 'complete';

        while ($nextPath) {
            $response = $this->graphClient->request('GET', $nextPath, Arr::except($graphOptions, ['platform']));

            if ($response->failed()) {
                $hydrationStatus = 'failed';
                break;
            }

            $definitionValues = array_merge($definitionValues, $response->data['value'] ?? []);
            $nextLink = $response->data['@odata.nextLink'] ?? null;

            if (! $nextLink) {
                break;
            }

            $nextPath = $this->stripGraphBaseUrl((string) $nextLink);
        }

        if ($hydrationStatus === 'failed') {
            $metadata['warnings'] = array_values(array_unique(array_merge(
                $metadata['warnings'] ?? [],
                ['Hydration failed: could not load Administrative Templates definition values.']
            )));

            return [$payload, $metadata];
        }

        $settings = [];

        foreach ($definitionValues as $definitionValue) {
            if (! is_array($definitionValue)) {
                continue;
            }

            $definition = $definitionValue['definition'] ?? null;
            $definitionId = is_array($definition) ? ($definition['id'] ?? null) : null;
            $definitionValueId = $definitionValue['id'] ?? null;

            if (! is_string($definitionValueId) || $definitionValueId === '') {
                continue;
            }

            if (! is_string($definitionId) || $definitionId === '') {
                continue;
            }

            $presentationValuesPath = $this->contracts->subresourcePath('groupPolicyConfiguration', 'presentationValues', [
                '{id}' => $policyId,
                '{definitionValueId}' => $definitionValueId,
            ]);

            $setting = [
                'enabled' => (bool) ($definitionValue['enabled'] ?? false),
                'definition@odata.bind' => "{$graphBase}/deviceManagement/groupPolicyDefinitions('{$definitionId}')",
                '#Definition_Id' => $definitionId,
                '#Definition_displayName' => is_array($definition) ? ($definition['displayName'] ?? null) : null,
                '#Definition_classType' => is_array($definition) ? ($definition['classType'] ?? null) : null,
                '#Definition_categoryPath' => is_array($definition) ? ($definition['categoryPath'] ?? null) : null,
            ];

            $setting = array_filter($setting, static fn ($value) => $value !== null);

            if (! $presentationValuesPath) {
                $settings[] = $setting;

                continue;
            }

            $presentationValues = [];
            $presentationNext = $presentationValuesPath;

            while ($presentationNext) {
                $pvResponse = $this->graphClient->request('GET', $presentationNext, Arr::except($graphOptions, ['platform']));

                if ($pvResponse->failed()) {
                    $metadata['warnings'] = array_values(array_unique(array_merge(
                        $metadata['warnings'] ?? [],
                        ['Hydration warning: could not load some Administrative Templates presentation values.']
                    )));
                    break;
                }

                $presentationValues = array_merge($presentationValues, $pvResponse->data['value'] ?? []);
                $presentationNextLink = $pvResponse->data['@odata.nextLink'] ?? null;

                if (! $presentationNextLink) {
                    break;
                }

                $presentationNext = $this->stripGraphBaseUrl((string) $presentationNextLink);
            }

            if ($presentationValues !== []) {
                $setting['presentationValues'] = [];

                foreach ($presentationValues as $presentationValue) {
                    if (! is_array($presentationValue)) {
                        continue;
                    }

                    $presentation = $presentationValue['presentation'] ?? null;
                    $presentationId = is_array($presentation) ? ($presentation['id'] ?? null) : null;

                    if (! is_string($presentationId) || $presentationId === '') {
                        continue;
                    }

                    $cleanPresentationValue = Arr::except($presentationValue, [
                        'presentation',
                        'id',
                        'lastModifiedDateTime',
                        'createdDateTime',
                    ]);

                    $cleanPresentationValue['presentation@odata.bind'] = "{$graphBase}/deviceManagement/groupPolicyDefinitions('{$definitionId}')/presentations('{$presentationId}')";

                    $label = is_array($presentation) ? ($presentation['label'] ?? null) : null;

                    if (is_string($label) && $label !== '') {
                        $cleanPresentationValue['#Presentation_Label'] = $label;
                    }

                    $cleanPresentationValue['#Presentation_Id'] = $presentationId;

                    $setting['presentationValues'][] = $cleanPresentationValue;
                }

                if ($setting['presentationValues'] === []) {
                    unset($setting['presentationValues']);
                }
            }

            $settings[] = $setting;
        }

        $payload['definitionValues'] = $settings;

        return [$payload, $metadata];
    }

    /**
     * Hydrate compliance policies with scheduled actions (notification templates).
     *
     * @return array{0:array,1:array}
     */
    private function hydrateComplianceActions(array $graphOptions, string $policyId, array $payload, array $metadata): array
    {
        $existingActions = $payload['scheduledActionsForRule'] ?? null;

        if (is_array($existingActions) && $existingActions !== []) {
            $metadata['compliance_actions_hydration'] = 'embedded';

            return [$payload, $metadata];
        }

        $path = sprintf('deviceManagement/deviceCompliancePolicies/%s/scheduledActionsForRule', urlencode($policyId));
        $options = Arr::except($graphOptions, ['platform']);

        $actions = [];
        $nextPath = $path;
        $hydrationStatus = 'complete';

        while ($nextPath) {
            $response = $this->graphClient->request('GET', $nextPath, $options);

            if ($response->failed()) {
                $hydrationStatus = 'failed';

                break;
            }

            $data = $response->data;
            $pageItems = $data['value'] ?? (is_array($data) ? $data : []);

            foreach ($pageItems as $item) {
                if (is_array($item)) {
                    $actions[] = $item;
                }
            }

            $nextLink = $data['@odata.nextLink'] ?? null;

            if (! $nextLink) {
                break;
            }

            $nextPath = $this->stripGraphBaseUrl((string) $nextLink);
        }

        if (! empty($actions)) {
            $payload['scheduledActionsForRule'] = $actions;
        }

        $metadata['compliance_actions_hydration'] = $hydrationStatus;

        return [$payload, $metadata];
    }

    /**
     * Hydrate enrollment notifications with message template details.
     *
     * @return array{0:array,1:array}
     */
    private function hydrateEnrollmentNotificationTemplates(array $graphOptions, array $payload, array $metadata): array
    {
        $existing = $payload['notificationTemplateSnapshots'] ?? null;

        if (is_array($existing) && $existing !== []) {
            $metadata['enrollment_notification_templates_hydration'] = 'embedded';

            return [$payload, $metadata];
        }

        $templateRefs = $payload['notificationTemplates'] ?? null;

        if (! is_array($templateRefs) || $templateRefs === []) {
            $metadata['enrollment_notification_templates_hydration'] = 'none';

            return [$payload, $metadata];
        }

        $options = Arr::except($graphOptions, ['platform']);

        $snapshots = [];
        $failures = 0;

        foreach ($templateRefs as $templateRef) {
            if (! is_string($templateRef) || $templateRef === '') {
                continue;
            }

            [$channel, $templateId] = $this->parseEnrollmentNotificationTemplateRef($templateRef);

            if ($templateId === null) {
                $failures++;

                continue;
            }

            $templatePath = sprintf('deviceManagement/notificationMessageTemplates/%s', urlencode($templateId));
            $templateResponse = $this->graphClient->request('GET', $templatePath, $options);

            if ($templateResponse->failed() || ! is_array($templateResponse->data)) {
                $failures++;

                continue;
            }

            $template = Arr::except($templateResponse->data, ['@odata.context']);

            $messagesPath = sprintf(
                'deviceManagement/notificationMessageTemplates/%s/localizedNotificationMessages',
                urlencode($templateId)
            );
            $messagesResponse = $this->graphClient->request('GET', $messagesPath, $options);

            $messages = [];

            if ($messagesResponse->failed()) {
                $failures++;
            } else {
                $pageItems = $messagesResponse->data['value'] ?? [];

                if (is_array($pageItems)) {
                    foreach ($pageItems as $message) {
                        if (is_array($message)) {
                            $messages[] = Arr::except($message, ['@odata.context']);
                        }
                    }
                }
            }

            $snapshots[] = [
                'channel' => $channel,
                'template_id' => $templateId,
                'template' => $template,
                'localized_notification_messages' => $messages,
            ];
        }

        if ($snapshots === []) {
            $metadata['enrollment_notification_templates_hydration'] = 'failed';

            return [$payload, $metadata];
        }

        $payload['notificationTemplateSnapshots'] = $snapshots;

        $metadata['enrollment_notification_templates_hydration'] = $failures > 0 ? 'partial' : 'complete';

        return [$payload, $metadata];
    }

    /**
     * @return array{0:?string,1:?string}
     */
    private function parseEnrollmentNotificationTemplateRef(string $templateRef): array
    {
        if (! str_contains($templateRef, '_')) {
            return [null, $templateRef];
        }

        [$channel, $templateId] = explode('_', $templateRef, 2);

        $channel = trim($channel);
        $templateId = trim($templateId);

        if ($templateId === '') {
            return [$channel !== '' ? $channel : null, null];
        }

        return [$channel !== '' ? $channel : null, $templateId];
    }

    /**
     * Extract all settingDefinitionId from settings array, including nested children.
     */
    private function extractDefinitionIds(array $settings): array
    {
        $definitionIds = [];

        foreach ($settings as $setting) {
            // Extract definition ID from settingInstance
            if (isset($setting['settingInstance']['settingDefinitionId'])) {
                $definitionIds[] = $setting['settingInstance']['settingDefinitionId'];
            }

            // Handle groupSettingCollectionInstance with children
            if (isset($setting['settingInstance']['@odata.type']) &&
                str_contains($setting['settingInstance']['@odata.type'], 'groupSettingCollectionInstance')) {
                if (isset($setting['settingInstance']['groupSettingCollectionValue'])) {
                    foreach ($setting['settingInstance']['groupSettingCollectionValue'] as $group) {
                        if (isset($group['children'])) {
                            $childIds = $this->extractDefinitionIds($group['children']);
                            $definitionIds = array_merge($definitionIds, $childIds);
                        }
                    }
                }
            }
        }

        return array_unique($definitionIds);
    }

    private function resolveProviderConnection(ManagedEnvironment $tenant): ProviderConnection
    {
        $resolution = $this->providerConnections()->resolveDefault($tenant, 'microsoft');

        if ($resolution->resolved && $resolution->connection instanceof ProviderConnection) {
            return $resolution->connection;
        }

        $reasonCode = $resolution->effectiveReasonCode();
        $reasonMessage = $resolution->message ?? 'Provider connection is not configured.';

        throw new RuntimeException(sprintf(
            '[%s] %s',
            ProviderReasonCodes::isKnown($reasonCode) ? $reasonCode : ProviderReasonCodes::UnknownError,
            $reasonMessage,
        ));
    }

    private function providerConnections(): ProviderConnectionResolver
    {
        return $this->providerConnections ?? app(ProviderConnectionResolver::class);
    }

    private function providerGateway(): ProviderGateway
    {
        return $this->providerGateway ?? app(ProviderGateway::class);
    }

    private function stripGraphBaseUrl(string $nextLink): string
    {
        $base = rtrim(config('graph.base_url', 'https://graph.microsoft.com'), '/').'/'.trim(config('graph.version', 'beta'), '/');

        if (str_starts_with($nextLink, $base)) {
            return ltrim(substr($nextLink, strlen($base)), '/');
        }

        return $nextLink;
    }

    /**
     * Determine if we should fall back to metadata-only for this policy type and error.
     */
    private function shouldFallbackToMetadata(string $policyType, ?int $status): bool
    {
        // Only fallback on 5xx server errors
        if ($status === null || $status < 500 || $status >= 600) {
            return false;
        }

        // Enable fallback for policy types experiencing upstream Graph issues
        $fallbackTypes = [
            'mamAppConfiguration',
            'managedDeviceAppConfiguration',
        ];

        return in_array($policyType, $fallbackTypes, true);
    }

    /**
     * Create a metadata-only snapshot from the Policy model when Graph is unavailable.
     *
     * @return array{payload:array,metadata:array,warnings:array}
     */
    private function createMetadataOnlySnapshot(Policy $policy, string $failureReason, ?int $status): array
    {
        $odataType = match ($policy->policy_type) {
            'mamAppConfiguration' => '#microsoft.graph.targetedManagedAppConfiguration',
            'managedDeviceAppConfiguration' => '#microsoft.graph.managedDeviceMobileAppConfiguration',
            default => '#microsoft.graph.'.$policy->policy_type,
        };

        $payload = [
            'id' => $policy->external_id,
            'displayName' => $policy->display_name,
            '@odata.type' => $odataType,
            'createdDateTime' => $policy->created_at?->toIso8601String(),
            'lastModifiedDateTime' => $policy->updated_at?->toIso8601String(),
        ];

        if ($policy->platform) {
            $payload['platform'] = $policy->platform;
        }

        $metadata = [
            'source' => 'metadata_only',
            'original_failure' => $failureReason,
            'original_status' => $status,
            'warnings' => [
                sprintf(
                    'Snapshot captured from local metadata only (Graph API returned %s). Restore preview available, full restore not possible.',
                    $status ?? 'error'
                ),
            ],
        ];

        return [
            'payload' => $payload,
            'metadata' => $metadata,
            'warnings' => $metadata['warnings'],
        ];
    }
}
