<?php

namespace App\Services\Intune;

use Illuminate\Support\Arr;

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

    public function supports(string $policyType): bool
    {
        return in_array($policyType, [
            'deviceComplianceScript',
            'deviceManagementScript',
            'deviceShellScript',
            'deviceHealthScript',
        ], true);
    }

    /**
     * @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 = is_array($snapshot) ? $snapshot : [];

        $displayName = Arr::get($snapshot, 'displayName') ?? Arr::get($snapshot, 'name');
        $description = Arr::get($snapshot, 'description');

        $entries = [];

        $entries[] = ['key' => 'Type', 'value' => $policyType];

        if (is_string($displayName) && $displayName !== '') {
            $entries[] = ['key' => 'Display name', 'value' => $displayName];
        }

        if (is_string($description) && $description !== '') {
            $entries[] = ['key' => 'Description', 'value' => $description];
        }

        $fileName = Arr::get($snapshot, 'fileName');
        if (is_string($fileName) && $fileName !== '') {
            $entries[] = ['key' => 'File name', 'value' => $fileName];
        }

        $publisher = Arr::get($snapshot, 'publisher');
        if (is_string($publisher) && $publisher !== '') {
            $entries[] = ['key' => 'Publisher', 'value' => $publisher];
        }

        $runAsAccount = Arr::get($snapshot, 'runAsAccount');
        if (is_string($runAsAccount) && $runAsAccount !== '') {
            $entries[] = ['key' => 'Run as account', 'value' => $runAsAccount];
        }

        $runAs32Bit = Arr::get($snapshot, 'runAs32Bit');
        if (is_bool($runAs32Bit)) {
            $entries[] = ['key' => 'Run as 32-bit', 'value' => $runAs32Bit ? 'Enabled' : 'Disabled'];
        }

        $enforceSignatureCheck = Arr::get($snapshot, 'enforceSignatureCheck');
        if (is_bool($enforceSignatureCheck)) {
            $entries[] = ['key' => 'Enforce signature check', 'value' => $enforceSignatureCheck ? 'Enabled' : 'Disabled'];
        }

        $entries = array_merge($entries, $this->contentEntries($snapshot));

        $schedule = Arr::get($snapshot, 'runSchedule');
        if (is_array($schedule) && $schedule !== []) {
            $entries[] = ['key' => 'Run schedule', 'value' => Arr::except($schedule, ['@odata.type'])];
        }

        $frequency = Arr::get($snapshot, 'runFrequency');
        if (is_string($frequency) && $frequency !== '') {
            $entries[] = ['key' => 'Run frequency', 'value' => $frequency];
        }

        $roleScopeTagIds = Arr::get($snapshot, 'roleScopeTagIds');
        if (is_array($roleScopeTagIds) && $roleScopeTagIds !== []) {
            $entries[] = ['key' => 'Scope tag IDs', 'value' => array_values($roleScopeTagIds)];
        }

        return [
            'status' => 'ok',
            'settings' => [
                [
                    'type' => 'keyValue',
                    'title' => 'Script settings',
                    'entries' => $entries,
                ],
            ],
            'warnings' => [],
        ];
    }

    /**
     * @return array<int, array{key: string, value: mixed}>
     */
    private function contentEntries(array $snapshot): array
    {
        $showContent = (bool) config('tenantpilot.display.show_script_content', false);
        $maxChars = (int) config('tenantpilot.display.max_script_content_chars', 5000);
        if ($maxChars <= 0) {
            $maxChars = 5000;
        }

        if (! $showContent) {
            return $this->contentSummaryEntries($snapshot);
        }

        $entries = [];

        $scriptContent = Arr::get($snapshot, 'scriptContent');
        if (is_string($scriptContent) && $scriptContent !== '') {
            $decoded = $this->decodeIfBase64Text($scriptContent);
            if (is_string($decoded) && $decoded !== '') {
                $scriptContent = $decoded;
            }
        }

        if (! is_string($scriptContent) || $scriptContent === '') {
            $scriptContentBase64 = Arr::get($snapshot, 'scriptContentBase64');
            if (is_string($scriptContentBase64) && $scriptContentBase64 !== '') {
                $decoded = base64_decode($this->stripWhitespace($scriptContentBase64), true);
                if (is_string($decoded) && $decoded !== '') {
                    $scriptContent = $this->normalizeDecodedText($decoded);
                }
            }
        }

        if (is_string($scriptContent) && $scriptContent !== '') {
            $entries[] = ['key' => 'scriptContent', 'value' => $this->limitContent($scriptContent, $maxChars)];
        }

        foreach (['detectionScriptContent', 'remediationScriptContent'] as $key) {
            $value = Arr::get($snapshot, $key);

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

            $decoded = $this->decodeIfBase64Text($value);
            if (is_string($decoded) && $decoded !== '') {
                $value = $decoded;
            }

            $entries[] = ['key' => $key, 'value' => $this->limitContent($value, $maxChars)];
        }

        return $entries;
    }

    private function decodeIfBase64Text(string $candidate): ?string
    {
        $trimmed = $this->stripWhitespace($candidate);
        if ($trimmed === '' || strlen($trimmed) < 16) {
            return null;
        }

        if (strlen($trimmed) % 4 !== 0) {
            return null;
        }

        if (! preg_match('/^[A-Za-z0-9+\/=]+$/', $trimmed)) {
            return null;
        }

        $decoded = base64_decode($trimmed, true);
        if (! is_string($decoded) || $decoded === '') {
            return null;
        }

        $decoded = $this->normalizeDecodedText($decoded);
        if ($decoded === '') {
            return null;
        }

        if (! $this->looksLikeText($decoded)) {
            return null;
        }

        return $decoded;
    }

    private function stripWhitespace(string $value): string
    {
        return preg_replace('/\s+/', '', $value) ?? '';
    }

    private function normalizeDecodedText(string $decoded): string
    {
        if (str_starts_with($decoded, "\xFF\xFE")) {
            $decoded = mb_convert_encoding(substr($decoded, 2), 'UTF-8', 'UTF-16LE');
        } elseif (str_starts_with($decoded, "\xFE\xFF")) {
            $decoded = mb_convert_encoding(substr($decoded, 2), 'UTF-8', 'UTF-16BE');
        } elseif (str_contains($decoded, "\x00")) {
            $decoded = mb_convert_encoding($decoded, 'UTF-8', 'UTF-16LE');
        }

        if (str_starts_with($decoded, "\xEF\xBB\xBF")) {
            $decoded = substr($decoded, 3);
        }

        return $decoded;
    }

    private function looksLikeText(string $decoded): bool
    {
        $length = strlen($decoded);
        if ($length === 0) {
            return false;
        }

        $nonPrintable = preg_match_all('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', $decoded) ?: 0;
        if ($nonPrintable > (int) max(1, $length * 0.05)) {
            return false;
        }

        // Scripts should typically contain some whitespace or line breaks.
        if ($length >= 24 && ! preg_match('/\s/', $decoded)) {
            return false;
        }

        return true;
    }

    /**
     * @return array<int, array{key: string, value: mixed}>
     */
    private function contentSummaryEntries(array $snapshot): array
    {
        // Script content and large blobs should not dominate normalized output.
        // Keep only safe summary fields if present.
        $contentKeys = [
            'scriptContent',
            'scriptContentBase64',
            'detectionScriptContent',
            'remediationScriptContent',
        ];

        $entries = [];

        foreach ($contentKeys as $key) {
            $value = Arr::get($snapshot, $key);

            if (is_string($value) && $value !== '') {
                $entries[] = ['key' => $key, 'value' => sprintf('[content: %d chars]', strlen($value))];
            }
        }

        return $entries;
    }

    private function limitContent(string $content, int $maxChars): string
    {
        if (mb_strlen($content) <= $maxChars) {
            return $content;
        }

        return mb_substr($content, 0, $maxChars).'…';
    }

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

        return $this->defaultNormalizer->flattenNormalizedForDiff($normalized);
    }
}
