<?php

namespace App\Services\Intune;

use Illuminate\Support\Arr;

class IntuneRoleDefinitionNormalizer implements PolicyTypeNormalizer
{
    private const string MetadataOnly = 'metadata_only';

    private const string None = 'none';

    private const string PermissionChange = 'permission_change';

    public function __construct(
        private readonly DefaultPolicyNormalizer $defaultNormalizer,
    ) {}

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

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

        if ($snapshot === []) {
            return [
                'status' => 'warning',
                'settings' => [],
                'warnings' => ['No snapshot available.'],
            ];
        }

        $settings = [];
        $warnings = [];
        $summaryEntries = [];

        $this->pushEntry($summaryEntries, 'Display name', Arr::get($snapshot, 'displayName'));
        $this->pushEntry($summaryEntries, 'Description', Arr::get($snapshot, 'description'));

        $isBuiltIn = Arr::get($snapshot, 'isBuiltIn');
        if (is_bool($isBuiltIn)) {
            $this->pushEntry($summaryEntries, 'Role source', $isBuiltIn ? 'Built-in' : 'Custom');
        }

        $permissionBlocks = $this->normalizePermissionBlocks(Arr::get($snapshot, 'rolePermissions', []));
        $this->pushEntry($summaryEntries, 'Permission blocks', count($permissionBlocks));

        $scopeTagIds = $this->normalizedStringList(Arr::get($snapshot, 'roleScopeTagIds', []));
        $this->pushEntry($summaryEntries, 'Scope tag IDs', $scopeTagIds);

        if ($summaryEntries !== []) {
            $settings[] = [
                'type' => 'keyValue',
                'title' => 'Role definition',
                'entries' => $summaryEntries,
            ];
        }

        foreach ($permissionBlocks as $index => $block) {
            $entries = [];
            $this->pushEntry($entries, 'Allowed actions', $block['allowed']);
            $this->pushEntry($entries, 'Denied actions', $block['denied']);
            $this->pushEntry($entries, 'Conditions', $block['conditions']);

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

            $settings[] = [
                'type' => 'keyValue',
                'title' => sprintf('Permission block %d', $index + 1),
                'entries' => $entries,
            ];
        }

        if ($permissionBlocks === []) {
            $warnings[] = 'Role definition contains no expanded permission blocks.';
        }

        if ($settings === []) {
            return [
                'status' => 'warning',
                'settings' => [],
                'warnings' => array_values(array_unique(array_merge($warnings, ['Role definition snapshot contains no readable fields.']))),
            ];
        }

        return [
            'status' => $warnings === [] ? 'ok' : 'warning',
            'settings' => $settings,
            'warnings' => array_values(array_unique($warnings)),
        ];
    }

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

    /**
     * @return array<string, mixed>
     */
    public function buildEvidenceMap(?array $snapshot, ?string $platform = null): array
    {
        return $this->flattenForDiff($snapshot, 'intuneRoleDefinition', $platform);
    }

    /**
     * @return array{
     *   baseline: array<string, mixed>,
     *   current: array<string, mixed>,
     *   changed_keys: list<string>,
     *   metadata_keys: list<string>,
     *   permission_keys: list<string>,
     *   diff_kind: string,
     *   diff_fingerprint: string
     * }
     */
    public function classifyDiff(?array $baselineSnapshot, ?array $currentSnapshot, ?string $platform = null): array
    {
        $baseline = $this->buildEvidenceMap($baselineSnapshot, $platform);
        $current = $this->buildEvidenceMap($currentSnapshot, $platform);

        $keys = array_values(array_unique(array_merge(array_keys($baseline), array_keys($current))));
        sort($keys, SORT_STRING);

        $changedKeys = [];
        $metadataKeys = [];
        $permissionKeys = [];

        foreach ($keys as $key) {
            if (($baseline[$key] ?? null) === ($current[$key] ?? null)) {
                continue;
            }

            $changedKeys[] = $key;

            if ($this->isPermissionDiffKey($key)) {
                $permissionKeys[] = $key;

                continue;
            }

            $metadataKeys[] = $key;
        }

        $diffKind = match (true) {
            $permissionKeys !== [] => self::PermissionChange,
            $metadataKeys !== [] => self::MetadataOnly,
            default => self::None,
        };

        return [
            'baseline' => $baseline,
            'current' => $current,
            'changed_keys' => $changedKeys,
            'metadata_keys' => $metadataKeys,
            'permission_keys' => $permissionKeys,
            'diff_kind' => $diffKind,
            'diff_fingerprint' => hash(
                'sha256',
                json_encode([
                    'diff_kind' => $diffKind,
                    'changed_keys' => $changedKeys,
                    'baseline' => $baseline,
                    'current' => $current,
                ], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            ),
        ];
    }

    /**
     * @return array<int, array{allowed: array<int, string>, denied: array<int, string>, conditions: array<int, string>, fingerprint: string}>
     */
    private function normalizePermissionBlocks(mixed $rolePermissions): array
    {
        if (! is_array($rolePermissions)) {
            return [];
        }

        $normalized = [];

        foreach ($rolePermissions as $permissionBlock) {
            if (! is_array($permissionBlock)) {
                continue;
            }

            $resourceActions = $permissionBlock['resourceActions'] ?? null;
            $resourceActions = is_array($resourceActions) ? $resourceActions : [];

            $allowed = [];
            $denied = [];
            $conditions = [];

            foreach ($resourceActions as $resourceAction) {
                if (! is_array($resourceAction)) {
                    continue;
                }

                $allowed = array_merge($allowed, $this->normalizedStringList($resourceAction['allowedResourceActions'] ?? []));
                $denied = array_merge($denied, $this->normalizedStringList($resourceAction['notAllowedResourceActions'] ?? []));
                $conditions = array_merge($conditions, $this->normalizedStringList([$resourceAction['condition'] ?? null]));
            }

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

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

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

            $block = [
                'allowed' => $allowed,
                'denied' => $denied,
                'conditions' => $conditions,
            ];

            $block['fingerprint'] = hash(
                'sha256',
                json_encode($block, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
            );

            $normalized[] = $block;
        }

        usort($normalized, fn (array $left, array $right): int => strcmp($left['fingerprint'], $right['fingerprint']));

        return $normalized;
    }

    /**
     * @param  array<int, array{key: string, value: mixed}>  $entries
     */
    private function pushEntry(array &$entries, string $key, mixed $value): void
    {
        if ($value === null) {
            return;
        }

        if (is_string($value) && $value === '') {
            return;
        }

        if (is_array($value) && $value === []) {
            return;
        }

        $entries[] = [
            'key' => $key,
            'value' => $value,
        ];
    }

    /**
     * @return array<int, string>
     */
    private function normalizedStringList(mixed $values): array
    {
        if (! is_array($values)) {
            return [];
        }

        return array_values(array_filter(
            array_map(
                static fn (mixed $value): ?string => is_string($value) && $value !== '' ? $value : null,
                $values,
            ),
            static fn (?string $value): bool => $value !== null,
        ));
    }

    private function isPermissionDiffKey(string $key): bool
    {
        return $key === 'Role definition > Permission blocks'
            || str_starts_with($key, 'Permission block ');
    }
}
