<?php

namespace App\Support\OpsUx;

final class AssignmentJobFingerprint
{
    public static function forFetch(
        int $backupItemId,
        string $tenantExternalId,
        string $policyExternalId,
    ): string {
        return self::hash('assignments.fetch', [
            'backup_item_id' => $backupItemId,
            'tenant_external_id' => trim($tenantExternalId),
            'policy_external_id' => trim($policyExternalId),
        ]);
    }

    /**
     * @param  array<int, array<string, mixed>>  $assignments
     * @param  array<string, mixed>  $groupMapping
     * @param  array<string, mixed>  $foundationMapping
     */
    public static function forRestore(
        int $restoreRunId,
        int $tenantId,
        string $policyType,
        string $policyId,
        array $assignments,
        array $groupMapping,
        array $foundationMapping = [],
    ): string {
        return self::hash('assignments.restore', [
            'restore_run_id' => $restoreRunId,
            'managed_environment_id' => $tenantId,
            'policy_type' => trim($policyType),
            'policy_id' => trim($policyId),
            'assignments' => self::normalizeAssignments($assignments),
            'group_mapping' => $groupMapping,
            'foundation_mapping' => $foundationMapping,
        ]);
    }

    public static function executionIdentityKey(
        string $jobType,
        int $tenantId,
        string $fingerprint,
        ?int $operationRunId = null,
    ): string {
        if (is_int($operationRunId) && $operationRunId > 0) {
            return 'operation_run:'.$operationRunId;
        }

        return 'tenant:'.$tenantId
            .'|job_type:'.trim($jobType)
            .'|fingerprint:'.trim($fingerprint);
    }

    /**
     * @param  array<string, mixed>  $identityInputs
     */
    private static function hash(string $jobType, array $identityInputs): string
    {
        $payload = [
            'job_type' => trim($jobType),
            'identity' => self::normalize($identityInputs),
        ];

        $json = json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

        return hash('sha256', (string) $json);
    }

    /**
     * @param  array<int, array<string, mixed>>  $assignments
     * @return array<int, array<string, mixed>>
     */
    private static function normalizeAssignments(array $assignments): array
    {
        $normalized = [];

        foreach ($assignments as $assignment) {
            if (! is_array($assignment)) {
                continue;
            }

            $target = is_array($assignment['target'] ?? null) ? $assignment['target'] : [];

            $normalized[] = [
                'id' => is_scalar($assignment['id'] ?? null) ? (string) $assignment['id'] : '',
                'target_type' => is_scalar($target['@odata.type'] ?? null) ? (string) $target['@odata.type'] : '',
                'group_id' => is_scalar($target['groupId'] ?? null) ? (string) $target['groupId'] : '',
                'assignment_filter_id' => is_scalar($assignment['deviceAndAppManagementAssignmentFilterId'] ?? null)
                    ? (string) $assignment['deviceAndAppManagementAssignmentFilterId']
                    : (is_scalar($target['deviceAndAppManagementAssignmentFilterId'] ?? null) ? (string) $target['deviceAndAppManagementAssignmentFilterId'] : ''),
                'assignment_filter_type' => is_scalar($assignment['deviceAndAppManagementAssignmentFilterType'] ?? null)
                    ? (string) $assignment['deviceAndAppManagementAssignmentFilterType']
                    : (is_scalar($target['deviceAndAppManagementAssignmentFilterType'] ?? null) ? (string) $target['deviceAndAppManagementAssignmentFilterType'] : ''),
            ];
        }

        usort($normalized, static function (array $left, array $right): int {
            $leftJson = json_encode($left, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            $rightJson = json_encode($right, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

            return strcmp((string) $leftJson, (string) $rightJson);
        });

        return $normalized;
    }

    private static function normalize(mixed $value): mixed
    {
        if (! is_array($value)) {
            return $value;
        }

        if (array_is_list($value)) {
            $items = array_map(static fn (mixed $item): mixed => self::normalize($item), $value);

            usort($items, static function (mixed $left, mixed $right): int {
                $leftJson = json_encode($left, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
                $rightJson = json_encode($right, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

                return strcmp((string) $leftJson, (string) $rightJson);
            });

            return array_values($items);
        }

        ksort($value);

        foreach ($value as $key => $item) {
            $value[$key] = self::normalize($item);
        }

        return $value;
    }
}
