using System.Collections.Generic;
using System.Linq;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;

namespace StellaOps.Feedser.Models;

/// <summary>
/// Deterministic JSON serializer tuned for canonical advisory output.
/// </summary>
public static class CanonicalJsonSerializer
{
    private static readonly JsonSerializerOptions CompactOptions = CreateOptions(writeIndented: false);
    private static readonly JsonSerializerOptions PrettyOptions = CreateOptions(writeIndented: true);

    private static readonly IReadOnlyDictionary<Type, string[]> PropertyOrderOverrides = new Dictionary<Type, string[]>
    {
        {
            typeof(AdvisoryProvenance),
            new[]
            {
                "source",
                "kind",
                "value",
                "decisionReason",
                "recordedAt",
                "fieldMask",
            }
        },
        {
            typeof(AffectedPackage),
            new[]
            {
                "type",
                "identifier",
                "platform",
                "versionRanges",
                "normalizedVersions",
                "statuses",
                "provenance",
            }
        },
        {
            typeof(AdvisoryCredit),
            new[]
            {
                "displayName",
                "role",
                "contacts",
                "provenance",
            }
        },
        {
            typeof(NormalizedVersionRule),
            new[]
            {
                "scheme",
                "type",
                "min",
                "minInclusive",
                "max",
                "maxInclusive",
                "value",
                "notes",
            }
        },
        {
            typeof(AdvisoryWeakness),
            new[]
            {
                "taxonomy",
                "identifier",
                "name",
                "uri",
                "provenance",
            }
        },
    };

    public static string Serialize<T>(T value)
        => JsonSerializer.Serialize(value, CompactOptions);

    public static string SerializeIndented<T>(T value)
        => JsonSerializer.Serialize(value, PrettyOptions);

    public static Advisory Normalize(Advisory advisory)
        => new(
            advisory.AdvisoryKey,
            advisory.Title,
            advisory.Summary,
            advisory.Language,
            advisory.Published,
            advisory.Modified,
            advisory.Severity,
            advisory.ExploitKnown,
            advisory.Aliases,
            advisory.Credits,
            advisory.References,
            advisory.AffectedPackages,
            advisory.CvssMetrics,
            advisory.Provenance,
            advisory.Description,
            advisory.Cwes,
            advisory.CanonicalMetricId);

    public static T Deserialize<T>(string json)
        => JsonSerializer.Deserialize<T>(json, PrettyOptions)!
            ?? throw new InvalidOperationException($"Unable to deserialize type {typeof(T).Name}.");

    private static JsonSerializerOptions CreateOptions(bool writeIndented)
    {
        var options = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
            DefaultIgnoreCondition = JsonIgnoreCondition.Never,
            WriteIndented = writeIndented,
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        };

        var baselineResolver = options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver();
        options.TypeInfoResolver = new DeterministicTypeInfoResolver(baselineResolver);
        options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, allowIntegerValues: false));
        return options;
    }

    private sealed class DeterministicTypeInfoResolver : IJsonTypeInfoResolver
    {
        private readonly IJsonTypeInfoResolver _inner;

        public DeterministicTypeInfoResolver(IJsonTypeInfoResolver inner)
        {
            _inner = inner ?? throw new ArgumentNullException(nameof(inner));
        }

        public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
        {
            var info = _inner.GetTypeInfo(type, options);
            if (info is null)
            {
                throw new InvalidOperationException($"Unable to resolve JsonTypeInfo for '{type}'.");
            }

            if (info.Kind is JsonTypeInfoKind.Object && info.Properties is { Count: > 1 })
            {
                var ordered = info.Properties
                    .OrderBy(property => GetPropertyOrder(type, property.Name))
                    .ThenBy(property => property.Name, StringComparer.Ordinal)
                    .ToArray();

                info.Properties.Clear();
                foreach (var property in ordered)
                {
                    info.Properties.Add(property);
                }
            }

            return info;
        }

        private static int GetPropertyOrder(Type type, string propertyName)
        {
            if (PropertyOrderOverrides.TryGetValue(type, out var order) &&
                Array.IndexOf(order, propertyName) is var index &&
                index >= 0)
            {
                return index;
            }

            return int.MaxValue;
        }
    }
}
