<?php

namespace App\Services\Intune;

use Illuminate\Support\Arr;

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

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

    /**
     * @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.'],
            ];
        }

        $warnings = [];
        $settings = [];
        $roleDefinition = Arr::get($snapshot, 'roleDefinition');
        $roleDefinition = is_array($roleDefinition) ? $roleDefinition : [];

        $roleDefinitionDisplay = $this->formatRoleDefinition($roleDefinition);
        if ($roleDefinitionDisplay === null) {
            $warnings[] = 'Role definition details were not expanded; using identifier fallback where possible.';
        } elseif (($roleDefinition['displayName'] ?? null) === null && ($roleDefinition['id'] ?? null) !== null) {
            $warnings[] = 'Role definition display name unavailable; using identifier fallback.';
        }

        $members = $this->normalizeSubjects(Arr::get($snapshot, 'members', []));
        $scopeMembers = $this->normalizeSubjects(Arr::get($snapshot, 'scopeMembers', []));
        $resourceScopes = $this->normalizedStringList(Arr::get($snapshot, 'resourceScopes', []));

        $summaryEntries = [];
        $this->pushEntry($summaryEntries, 'Assignment name', Arr::get($snapshot, 'displayName'));
        $this->pushEntry($summaryEntries, 'Description', Arr::get($snapshot, 'description'));
        $this->pushEntry($summaryEntries, 'Role definition', $roleDefinitionDisplay);
        $this->pushEntry($summaryEntries, 'Scope type', Arr::get($snapshot, 'scopeType'));
        $this->pushEntry($summaryEntries, 'Members count', count($members));
        $this->pushEntry($summaryEntries, 'Scope members count', count($scopeMembers));
        $this->pushEntry($summaryEntries, 'Resource scopes count', count($resourceScopes));

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

        if ($members !== []) {
            $settings[] = [
                'type' => 'keyValue',
                'title' => 'Members',
                'entries' => [
                    [
                        'key' => 'Resolved members',
                        'value' => $members,
                    ],
                ],
            ];
        }

        if ($scopeMembers !== []) {
            $settings[] = [
                'type' => 'keyValue',
                'title' => 'Scope members',
                'entries' => [
                    [
                        'key' => 'Resolved scope members',
                        'value' => $scopeMembers,
                    ],
                ],
            ];
        }

        if ($resourceScopes !== []) {
            $settings[] = [
                'type' => 'keyValue',
                'title' => 'Resource scopes',
                'entries' => [
                    [
                        'key' => 'Scopes',
                        'value' => $resourceScopes,
                    ],
                ],
            ];
        }

        if ($settings === []) {
            return [
                'status' => 'warning',
                'settings' => [],
                'warnings' => array_values(array_unique(array_merge($warnings, ['Role assignment 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),
        );
    }

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

    private function formatRoleDefinition(array $roleDefinition): ?string
    {
        $displayName = Arr::get($roleDefinition, 'displayName');
        $id = Arr::get($roleDefinition, 'id');

        if (is_string($displayName) && $displayName !== '' && is_string($id) && $id !== '') {
            return sprintf('%s (%s)', $displayName, $id);
        }

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

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

        return null;
    }

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

        $normalized = [];

        foreach ($subjects as $subject) {
            if (is_string($subject) && $subject !== '') {
                $normalized[] = $subject;

                continue;
            }

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

            $display = Arr::get($subject, 'displayName')
                ?? Arr::get($subject, 'userPrincipalName')
                ?? Arr::get($subject, 'mail')
                ?? Arr::get($subject, 'name');
            $identifier = Arr::get($subject, 'id')
                ?? Arr::get($subject, 'principalId')
                ?? Arr::get($subject, 'groupId');

            if (is_string($display) && $display !== '' && is_string($identifier) && $identifier !== '' && $display !== $identifier) {
                $normalized[] = sprintf('%s (%s)', $display, $identifier);

                continue;
            }

            if (is_string($display) && $display !== '') {
                $normalized[] = $display;

                continue;
            }

            if (is_string($identifier) && $identifier !== '') {
                $normalized[] = $identifier;
            }
        }

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

        return $normalized;
    }

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

        $normalized = 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,
        ));

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

        return $normalized;
    }
}
