<?php

namespace App\Services\Intune;

use Illuminate\Support\Str;

class CompliancePolicyNormalizer implements PolicyTypeNormalizer
{
    public function __construct(
        private readonly DefaultPolicyNormalizer $defaultNormalizer,
    ) {}

    public function supports(string $policyType): bool
    {
        return $policyType === 'deviceCompliancePolicy';
    }

    /**
     * @return array{status: string, settings: array<int, array<string, mixed>>, settings_table?: array<string, mixed>, warnings: array<int, string>}
     */
    public function normalize(?array $snapshot, string $policyType, ?string $platform = null): array
    {
        $snapshot = $snapshot ?? [];
        $normalized = $this->defaultNormalizer->normalize($snapshot, $policyType, $platform);

        if ($snapshot === []) {
            return $normalized;
        }

        $normalized['settings'] = array_values(array_filter(
            $normalized['settings'],
            fn (array $block) => strtolower((string) ($block['title'] ?? '')) !== 'general'
        ));

        foreach ($this->buildComplianceBlocks($snapshot) as $block) {
            $normalized['settings'][] = $block;
        }

        return $normalized;
    }

    /**
     * @return array<string, mixed>
     */
    public function flattenForDiff(?array $snapshot, string $policyType, ?string $platform = null): array
    {
        $snapshot = $snapshot ?? [];

        $normalized = $this->normalize($snapshot, $policyType, $platform);
        $flat = $this->defaultNormalizer->flattenNormalizedForDiff($normalized);

        return array_merge($flat, $this->flattenComplianceNotificationsForDiff($snapshot));
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function buildComplianceBlocks(array $snapshot): array
    {
        $blocks = [];
        $groups = $this->groupedFields();
        $usedKeys = [];

        foreach ($groups as $title => $group) {
            $rows = $this->buildRows($snapshot, $group['keys'], $group['labels'] ?? []);

            if ($rows === []) {
                continue;
            }

            $blocks[] = [
                'type' => 'table',
                'title' => $title,
                'rows' => $rows,
            ];

            $usedKeys = array_merge($usedKeys, $group['keys']);
        }

        $additionalRows = $this->buildAdditionalRows($snapshot, $usedKeys);

        if ($additionalRows !== []) {
            $blocks[] = [
                'type' => 'table',
                'title' => 'Additional Settings',
                'rows' => $additionalRows,
            ];
        }

        return $blocks;
    }

    /**
     * @return array<string, mixed>
     */
    private function flattenComplianceNotificationsForDiff(array $snapshot): array
    {
        $scheduled = $snapshot['scheduledActionsForRule'] ?? null;

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

        $templateIds = [];

        foreach ($scheduled as $rule) {
            if (! is_array($rule)) {
                continue;
            }

            $configs = $rule['scheduledActionConfigurations'] ?? null;

            if (! is_array($configs)) {
                continue;
            }

            foreach ($configs as $config) {
                if (! is_array($config)) {
                    continue;
                }

                if (($config['actionType'] ?? null) !== 'notification') {
                    continue;
                }

                $templateKey = $this->resolveNotificationTemplateKey($config);

                if ($templateKey === null) {
                    continue;
                }

                $templateId = $config[$templateKey] ?? null;

                if (! is_string($templateId) || $templateId === '' || $this->isEmptyGuid($templateId)) {
                    continue;
                }

                $templateIds[] = $templateId;
            }
        }

        $templateIds = array_values(array_unique($templateIds));
        sort($templateIds);

        if ($templateIds === []) {
            return [];
        }

        return [
            'Compliance notifications > Template IDs' => $templateIds,
        ];
    }

    private function resolveNotificationTemplateKey(array $config): ?string
    {
        if (array_key_exists('notificationTemplateId', $config)) {
            return 'notificationTemplateId';
        }

        if (array_key_exists('notificationMessageTemplateId', $config)) {
            return 'notificationMessageTemplateId';
        }

        return null;
    }

    private function isEmptyGuid(string $value): bool
    {
        return strtolower($value) === '00000000-0000-0000-0000-000000000000';
    }

    /**
     * @return array{keys: array<int, string>, labels?: array<string, string>}
     */
    private function groupedFields(): array
    {
        return [
            'Password & Access' => [
                'keys' => [
                    'passwordRequired',
                    'passwordRequiredType',
                    'passwordBlockSimple',
                    'passwordMinimumLength',
                    'passwordMinimumCharacterSetCount',
                    'passwordExpirationDays',
                    'passwordMinutesOfInactivityBeforeLock',
                    'passwordPreviousPasswordBlockCount',
                    'passwordRequiredToUnlockFromIdle',
                ],
                'labels' => [
                    'passwordRequired' => 'Password required',
                    'passwordRequiredType' => 'Password required type',
                    'passwordBlockSimple' => 'Block simple passwords',
                    'passwordMinimumLength' => 'Password minimum length',
                    'passwordMinimumCharacterSetCount' => 'Password minimum character set count',
                    'passwordExpirationDays' => 'Password expiration days',
                    'passwordMinutesOfInactivityBeforeLock' => 'Password idle lock (minutes)',
                    'passwordPreviousPasswordBlockCount' => 'Password history count',
                    'passwordRequiredToUnlockFromIdle' => 'Password required to unlock from idle',
                ],
            ],
            'Defender & Threat Protection' => [
                'keys' => [
                    'defenderEnabled',
                    'defenderVersion',
                    'antivirusRequired',
                    'antiSpywareRequired',
                    'rtpEnabled',
                    'signatureOutOfDate',
                    'deviceThreatProtectionEnabled',
                    'deviceThreatProtectionRequiredSecurityLevel',
                    'requireHealthyDeviceReport',
                ],
                'labels' => [
                    'defenderEnabled' => 'Microsoft Defender enabled',
                    'defenderVersion' => 'Defender version',
                    'antivirusRequired' => 'Antivirus required',
                    'antiSpywareRequired' => 'Anti-spyware required',
                    'rtpEnabled' => 'Real-time protection enabled',
                    'signatureOutOfDate' => 'Signature out of date (days)',
                    'deviceThreatProtectionEnabled' => 'Device threat protection enabled',
                    'deviceThreatProtectionRequiredSecurityLevel' => 'Threat protection required level',
                    'requireHealthyDeviceReport' => 'Require healthy device report',
                ],
            ],
            'Encryption & Integrity' => [
                'keys' => [
                    'bitLockerEnabled',
                    'storageRequireEncryption',
                    'tpmRequired',
                    'secureBootEnabled',
                    'codeIntegrityEnabled',
                    'memoryIntegrityEnabled',
                    'kernelDmaProtectionEnabled',
                    'firmwareProtectionEnabled',
                    'virtualizationBasedSecurityEnabled',
                    'earlyLaunchAntiMalwareDriverEnabled',
                ],
                'labels' => [
                    'bitLockerEnabled' => 'BitLocker required',
                    'storageRequireEncryption' => 'Storage encryption required',
                    'tpmRequired' => 'TPM required',
                    'secureBootEnabled' => 'Secure boot required',
                    'codeIntegrityEnabled' => 'Code integrity required',
                    'memoryIntegrityEnabled' => 'Memory integrity required',
                    'kernelDmaProtectionEnabled' => 'Kernel DMA protection required',
                    'firmwareProtectionEnabled' => 'Firmware protection required',
                    'virtualizationBasedSecurityEnabled' => 'Virtualization-based security required',
                    'earlyLaunchAntiMalwareDriverEnabled' => 'Early launch anti-malware required',
                ],
            ],
            'Operating System' => [
                'keys' => [
                    'osMinimumVersion',
                    'osMaximumVersion',
                    'mobileOsMinimumVersion',
                    'mobileOsMaximumVersion',
                    'validOperatingSystemBuildRanges',
                    'wslDistributions',
                ],
                'labels' => [
                    'osMinimumVersion' => 'OS minimum version',
                    'osMaximumVersion' => 'OS maximum version',
                    'mobileOsMinimumVersion' => 'Mobile OS minimum version',
                    'mobileOsMaximumVersion' => 'Mobile OS maximum version',
                    'validOperatingSystemBuildRanges' => 'Valid OS build ranges',
                    'wslDistributions' => 'Allowed WSL distributions',
                ],
            ],
            'Firewall' => [
                'keys' => [
                    'activeFirewallRequired',
                ],
                'labels' => [
                    'activeFirewallRequired' => 'Active firewall required',
                ],
            ],
            'Compliance Signals' => [
                'keys' => [
                    'configurationManagerComplianceRequired',
                    'deviceCompliancePolicyScript',
                ],
                'labels' => [
                    'configurationManagerComplianceRequired' => 'ConfigMgr compliance required',
                    'deviceCompliancePolicyScript' => 'Compliance policy script',
                ],
            ],
        ];
    }

    /**
     * @param  array<string, mixed>  $labels
     * @return array<int, array<string, mixed>>
     */
    private function buildRows(array $snapshot, array $keys, array $labels = []): array
    {
        $rows = [];

        foreach ($keys as $key) {
            if (! array_key_exists($key, $snapshot)) {
                continue;
            }

            $rows[] = [
                'label' => $labels[$key] ?? Str::headline($key),
                'value' => $this->formatValue($snapshot[$key]),
            ];
        }

        return $rows;
    }

    /**
     * @param  array<int, string>  $usedKeys
     * @return array<int, array<string, mixed>>
     */
    private function buildAdditionalRows(array $snapshot, array $usedKeys): array
    {
        $ignoredKeys = array_merge($this->ignoredKeys(), $usedKeys);
        $rows = [];

        foreach ($snapshot as $key => $value) {
            if (! is_string($key)) {
                continue;
            }

            if (in_array($key, $ignoredKeys, true)) {
                continue;
            }

            $rows[] = [
                'label' => Str::headline($key),
                'value' => $this->formatValue($value),
            ];
        }

        return $rows;
    }

    /**
     * @return array<int, string>
     */
    private function ignoredKeys(): array
    {
        return [
            '@odata.context',
            '@odata.type',
            'id',
            'version',
            'createdDateTime',
            'lastModifiedDateTime',
            'supportsScopeTags',
            'roleScopeTagIds',
            'assignments',
            'createdBy',
            'lastModifiedBy',
            'omaSettings',
            'settings',
            'settingsDelta',
            'displayName',
            'description',
            'name',
            'platform',
            'platforms',
            'technologies',
            'settingCount',
            'settingsCount',
            'templateReference',
            'scheduledActionsForRule@odata.context',
            'scheduledActionsForRule',
        ];
    }

    private function formatValue(mixed $value): mixed
    {
        if (is_array($value)) {
            return json_encode($value, JSON_PRETTY_PRINT);
        }

        return $value;
    }
}
