<?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->flattenNoncomplianceActionsForDiff($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 flattenNoncomplianceActionsForDiff(array $snapshot): array
    {
        $actions = $this->canonicalNoncomplianceActions($snapshot['scheduledActionsForRule'] ?? null);

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

        $countsByType = [];

        foreach ($actions as $action) {
            $actionType = (string) $action['action_type'];
            $countsByType[$actionType] = ($countsByType[$actionType] ?? 0) + 1;
        }

        $occurrencesByType = [];
        $flat = [];

        foreach ($actions as $action) {
            $actionType = (string) $action['action_type'];
            $occurrencesByType[$actionType] = ($occurrencesByType[$actionType] ?? 0) + 1;

            $label = 'Actions for noncompliance > '.$this->actionTypeLabel($actionType);

            if (($countsByType[$actionType] ?? 0) > 1) {
                $label .= ' #'.$occurrencesByType[$actionType];
            }

            $ruleName = $action['rule_name'] ?? null;
            if (is_string($ruleName) && $ruleName !== '') {
                $flat[$label.' > Rule name'] = $ruleName;
            }

            $flat[$label.' > Grace period'] = $this->formatGracePeriod($action['grace_period_hours'] ?? null);
            $flat[$label.' > Notification template ID'] = $action['notification_template_id'] ?? null;
        }

        return $flat;
    }

    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<int, array{
     *   action_type: string,
     *   grace_period_hours: ?int,
     *   notification_template_id: ?string,
     *   rule_name: ?string
     * }>
     */
    private function canonicalNoncomplianceActions(mixed $scheduled): array
    {
        if (! is_array($scheduled)) {
            return [];
        }

        $actions = [];

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

            $ruleName = $rule['ruleName'] ?? null;
            $ruleName = is_string($ruleName) ? trim($ruleName) : null;
            $ruleName = $ruleName !== '' ? $ruleName : null;

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

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

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

                $actionType = $config['actionType'] ?? null;
                $actionType = is_string($actionType) ? strtolower(trim($actionType)) : null;

                if ($actionType === null || $actionType === '') {
                    continue;
                }

                $gracePeriodHours = $config['gracePeriodHours'] ?? null;
                $gracePeriodHours = is_numeric($gracePeriodHours) ? (int) $gracePeriodHours : null;

                $actions[] = [
                    'action_type' => $actionType,
                    'grace_period_hours' => $gracePeriodHours,
                    'notification_template_id' => $this->normalizeNotificationTemplateId($config),
                    'rule_name' => $ruleName,
                ];
            }
        }

        usort($actions, function (array $left, array $right): int {
            $actionTypeComparison = $left['action_type'] <=> $right['action_type'];

            if ($actionTypeComparison !== 0) {
                return $actionTypeComparison;
            }

            $gracePeriodComparison = ($left['grace_period_hours'] ?? PHP_INT_MAX) <=> ($right['grace_period_hours'] ?? PHP_INT_MAX);

            if ($gracePeriodComparison !== 0) {
                return $gracePeriodComparison;
            }

            $templateComparison = ($left['notification_template_id'] ?? "\u{10FFFF}") <=> ($right['notification_template_id'] ?? "\u{10FFFF}");

            if ($templateComparison !== 0) {
                return $templateComparison;
            }

            return ($left['rule_name'] ?? "\u{10FFFF}") <=> ($right['rule_name'] ?? "\u{10FFFF}");
        });

        return $actions;
    }

    private function normalizeNotificationTemplateId(array $config): ?string
    {
        $templateKey = $this->resolveNotificationTemplateKey($config);

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

        $templateId = $config[$templateKey] ?? null;
        $templateId = is_string($templateId) ? trim($templateId) : null;

        if ($templateId === null || $templateId === '' || $this->isEmptyGuid($templateId)) {
            return null;
        }

        return $templateId;
    }

    private function actionTypeLabel(string $actionType): string
    {
        return match ($actionType) {
            'block' => 'Mark device noncompliant',
            'notification' => 'Send notification',
            'retire' => 'Add device to retire list',
            'wipe' => 'Wipe device',
            default => Str::headline($actionType),
        };
    }

    private function formatGracePeriod(?int $hours): ?string
    {
        if ($hours === null) {
            return null;
        }

        if ($hours === 0) {
            return '0 hours';
        }

        $days = intdiv($hours, 24);
        $remainingHours = $hours % 24;
        $parts = [];

        if ($days > 0) {
            $parts[] = $days === 1 ? '1 day' : $days.' days';
        }

        if ($remainingHours > 0) {
            $parts[] = $remainingHours === 1 ? '1 hour' : $remainingHours.' hours';
        }

        $label = implode(' ', $parts);

        if ($label === '') {
            return $hours === 1 ? '1 hour' : $hours.' hours';
        }

        return sprintf('%s (%d hours)', $label, $hours);
    }

    /**
     * @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;
    }
}
