using System;
using System.Buffers.Text;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using StellaOps.Feedser.Normalization.Distro;

namespace StellaOps.Feedser.Source.Distro.Suse.Internal;

internal static class SuseCsafParser
{
    public static SuseAdvisoryDto Parse(string json)
    {
        ArgumentException.ThrowIfNullOrEmpty(json);

        using var document = JsonDocument.Parse(json);
        var root = document.RootElement;

        if (!root.TryGetProperty("document", out var documentElement))
        {
            throw new InvalidOperationException("CSAF payload missing 'document' element.");
        }

        var trackingElement = documentElement.GetProperty("tracking");
        var advisoryId = trackingElement.TryGetProperty("id", out var idElement)
            ? idElement.GetString()
            : null;
        if (string.IsNullOrWhiteSpace(advisoryId))
        {
            throw new InvalidOperationException("CSAF payload missing tracking.id.");
        }

        var title = documentElement.TryGetProperty("title", out var titleElement)
            ? titleElement.GetString()
            : advisoryId;

        var summary = ExtractSummary(documentElement);
        var published = ParseDate(trackingElement, "initial_release_date")
            ?? ParseDate(trackingElement, "current_release_date")
            ?? DateTimeOffset.UtcNow;

        var references = new List<SuseReferenceDto>();
        if (documentElement.TryGetProperty("references", out var referencesElement) &&
            referencesElement.ValueKind == JsonValueKind.Array)
        {
            foreach (var referenceElement in referencesElement.EnumerateArray())
            {
                var url = referenceElement.TryGetProperty("url", out var urlElement)
                    ? urlElement.GetString()
                    : null;

                if (string.IsNullOrWhiteSpace(url))
                {
                    continue;
                }

                references.Add(new SuseReferenceDto(
                    url.Trim(),
                    referenceElement.TryGetProperty("category", out var categoryElement) ? categoryElement.GetString() : null,
                    referenceElement.TryGetProperty("summary", out var summaryElement) ? summaryElement.GetString() : null));
            }
        }

        var productLookup = BuildProductLookup(root);
        var packageBuilders = new Dictionary<string, PackageStateBuilder>(StringComparer.OrdinalIgnoreCase);
        var cveIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        if (root.TryGetProperty("vulnerabilities", out var vulnerabilitiesElement) &&
            vulnerabilitiesElement.ValueKind == JsonValueKind.Array)
        {
            foreach (var vulnerability in vulnerabilitiesElement.EnumerateArray())
            {
                if (vulnerability.TryGetProperty("cve", out var cveElement))
                {
                    var cve = cveElement.GetString();
                    if (!string.IsNullOrWhiteSpace(cve))
                    {
                        cveIds.Add(cve.Trim());
                    }
                }

                if (vulnerability.TryGetProperty("references", out var vulnReferences) &&
                    vulnReferences.ValueKind == JsonValueKind.Array)
                {
                    foreach (var referenceElement in vulnReferences.EnumerateArray())
                    {
                        var url = referenceElement.TryGetProperty("url", out var urlElement)
                            ? urlElement.GetString()
                            : null;
                        if (string.IsNullOrWhiteSpace(url))
                        {
                            continue;
                        }

                        references.Add(new SuseReferenceDto(
                            url.Trim(),
                            referenceElement.TryGetProperty("category", out var categoryElement) ? categoryElement.GetString() : null,
                            referenceElement.TryGetProperty("summary", out var summaryElement) ? summaryElement.GetString() : null));
                    }
                }

                if (!vulnerability.TryGetProperty("product_status", out var statusElement) ||
                    statusElement.ValueKind != JsonValueKind.Object)
                {
                    continue;
                }

                foreach (var property in statusElement.EnumerateObject())
                {
                    var category = property.Name;
                    var idArray = property.Value;
                    if (idArray.ValueKind != JsonValueKind.Array)
                    {
                        continue;
                    }

                    foreach (var productIdElement in idArray.EnumerateArray())
                    {
                        var productId = productIdElement.GetString();
                        if (string.IsNullOrWhiteSpace(productId))
                        {
                            continue;
                        }

                        if (!productLookup.TryGetValue(productId, out var product))
                        {
                            continue;
                        }

                        if (!packageBuilders.TryGetValue(productId, out var builder))
                        {
                            builder = new PackageStateBuilder(product);
                            packageBuilders[productId] = builder;
                        }

                        builder.ApplyStatus(category, product);
                    }
                }
            }
        }

        var packages = new List<SusePackageStateDto>(packageBuilders.Count);
        foreach (var builder in packageBuilders.Values)
        {
            if (builder.ShouldEmit)
            {
                packages.Add(builder.ToDto());
            }
        }

        packages.Sort(static (left, right) =>
        {
            var compare = string.Compare(left.Platform, right.Platform, StringComparison.OrdinalIgnoreCase);
            if (compare != 0)
            {
                return compare;
            }

            compare = string.Compare(left.Package, right.Package, StringComparison.OrdinalIgnoreCase);
            if (compare != 0)
            {
                return compare;
            }

            return string.Compare(left.Architecture, right.Architecture, StringComparison.OrdinalIgnoreCase);
        });

        var cveList = cveIds.Count == 0
            ? Array.Empty<string>()
            : cveIds.OrderBy(static cve => cve, StringComparer.OrdinalIgnoreCase).ToArray();

        return new SuseAdvisoryDto(
            advisoryId.Trim(),
            string.IsNullOrWhiteSpace(title) ? advisoryId : title!,
            summary,
            published,
            cveList,
            packages,
            references);
    }

    private static string? ExtractSummary(JsonElement documentElement)
    {
        if (!documentElement.TryGetProperty("notes", out var notesElement) || notesElement.ValueKind != JsonValueKind.Array)
        {
            return null;
        }

        foreach (var note in notesElement.EnumerateArray())
        {
            var category = note.TryGetProperty("category", out var categoryElement)
                ? categoryElement.GetString()
                : null;

            if (string.Equals(category, "summary", StringComparison.OrdinalIgnoreCase)
                || string.Equals(category, "description", StringComparison.OrdinalIgnoreCase))
            {
                return note.TryGetProperty("text", out var textElement) ? textElement.GetString() : null;
            }
        }

        return null;
    }

    private static DateTimeOffset? ParseDate(JsonElement element, string propertyName)
    {
        if (!element.TryGetProperty(propertyName, out var dateElement))
        {
            return null;
        }

        if (dateElement.ValueKind == JsonValueKind.String &&
            DateTimeOffset.TryParse(dateElement.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed))
        {
            return parsed.ToUniversalTime();
        }

        return null;
    }

    private static Dictionary<string, SuseProduct> BuildProductLookup(JsonElement root)
    {
        var lookup = new Dictionary<string, SuseProduct>(StringComparer.OrdinalIgnoreCase);

        if (!root.TryGetProperty("product_tree", out var productTree))
        {
            return lookup;
        }

        if (productTree.TryGetProperty("branches", out var branches) && branches.ValueKind == JsonValueKind.Array)
        {
            TraverseBranches(branches, null, null, lookup);
        }

        return lookup;
    }

    private static void TraverseBranches(JsonElement branches, string? platform, string? architecture, IDictionary<string, SuseProduct> lookup)
    {
        foreach (var branch in branches.EnumerateArray())
        {
            var category = branch.TryGetProperty("category", out var categoryElement)
                ? categoryElement.GetString()
                : null;

            var name = branch.TryGetProperty("name", out var nameElement)
                ? nameElement.GetString()
                : null;

            var nextPlatform = platform;
            var nextArchitecture = architecture;

            if (string.Equals(category, "product_family", StringComparison.OrdinalIgnoreCase) ||
                string.Equals(category, "product_name", StringComparison.OrdinalIgnoreCase) ||
                string.Equals(category, "product_version", StringComparison.OrdinalIgnoreCase))
            {
                if (!string.IsNullOrWhiteSpace(name))
                {
                    nextPlatform = name;
                }
            }

            if (string.Equals(category, "architecture", StringComparison.OrdinalIgnoreCase))
            {
                nextArchitecture = string.IsNullOrWhiteSpace(name) ? null : name;
            }

            if (branch.TryGetProperty("product", out var productElement) && productElement.ValueKind == JsonValueKind.Object)
            {
                var productId = productElement.TryGetProperty("product_id", out var idElement)
                    ? idElement.GetString()
                    : null;

                if (!string.IsNullOrWhiteSpace(productId))
                {
                    var productName = productElement.TryGetProperty("name", out var productNameElement)
                        ? productNameElement.GetString()
                        : productId;

                    var (platformName, packageSegment) = SplitProductId(productId!, nextPlatform);
                    if (string.IsNullOrWhiteSpace(packageSegment))
                    {
                        packageSegment = productName;
                    }

                    if (string.IsNullOrWhiteSpace(packageSegment))
                    {
                        continue;
                    }

                    if (!Nevra.TryParse(packageSegment, out var nevra) && !Nevra.TryParse(productName ?? packageSegment, out nevra))
                    {
                        continue;
                    }

                    lookup[productId!] = new SuseProduct(
                        productId!,
                        platformName ?? "SUSE",
                        nevra!,
                        nextArchitecture ?? nevra!.Architecture);
                }
            }

            if (branch.TryGetProperty("branches", out var childBranches) && childBranches.ValueKind == JsonValueKind.Array)
            {
                TraverseBranches(childBranches, nextPlatform, nextArchitecture, lookup);
            }
        }
    }

    private static (string? Platform, string? Package) SplitProductId(string productId, string? currentPlatform)
    {
        var separatorIndex = productId.IndexOf(':');
        if (separatorIndex < 0)
        {
            return (currentPlatform, productId);
        }

        var platform = productId[..separatorIndex];
        var package = separatorIndex < productId.Length - 1 ? productId[(separatorIndex + 1)..] : string.Empty;
        var platformNormalized = string.IsNullOrWhiteSpace(platform) ? currentPlatform : platform;
        var packageNormalized = string.IsNullOrWhiteSpace(package) ? null : package;
        return (platformNormalized, packageNormalized);
    }

    private static string FormatNevraVersion(Nevra nevra)
    {
        var epochSegment = nevra.HasExplicitEpoch || nevra.Epoch > 0 ? $"{nevra.Epoch}:" : string.Empty;
        return $"{epochSegment}{nevra.Version}-{nevra.Release}";
    }

    private sealed record SuseProduct(string ProductId, string Platform, Nevra Nevra, string? Architecture)
    {
        public string Package => Nevra.Name;

        public string Version => FormatNevraVersion(Nevra);

        public string CanonicalNevra => Nevra.ToCanonicalString();
    }

    private sealed class PackageStateBuilder
    {
        private readonly SuseProduct _product;

        public PackageStateBuilder(SuseProduct product)
        {
            _product = product;
            Status = null;
        }

        public string Package => _product.Package;
        public string Platform => _product.Platform;
        public string? Architecture => _product.Architecture;
        public string? IntroducedVersion { get; private set; }
        public string? FixedVersion { get; private set; }
        public string? LastAffectedVersion { get; private set; }
        public string? Status { get; private set; }

        public bool ShouldEmit => !string.IsNullOrWhiteSpace(Status) && !string.Equals(Status, "not_affected", StringComparison.OrdinalIgnoreCase);

        public void ApplyStatus(string category, SuseProduct product)
        {
            if (string.IsNullOrWhiteSpace(category))
            {
                return;
            }

            switch (category.ToLowerInvariant())
            {
                case "recommended":
                case "fixed":
                    FixedVersion = product.Version;
                    Status = "resolved";
                    break;

                case "known_affected":
                case "known_vulnerable":
                    LastAffectedVersion = product.Version;
                    Status ??= "open";
                    break;

                case "first_affected":
                    IntroducedVersion ??= product.Version;
                    Status ??= "open";
                    break;

                case "under_investigation":
                    Status ??= "investigating";
                    break;

                case "known_not_affected":
                    Status = "not_affected";
                    IntroducedVersion = null;
                    FixedVersion = null;
                    LastAffectedVersion = null;
                    break;
            }
        }

        public SusePackageStateDto ToDto()
        {
            var status = Status ?? "unknown";
            var introduced = IntroducedVersion;
            var lastAffected = LastAffectedVersion;

            if (string.Equals(status, "resolved", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(FixedVersion))
            {
                status = "open";
            }

            return new SusePackageStateDto(
                Package,
                Platform,
                Architecture,
                _product.CanonicalNevra,
                introduced,
                FixedVersion,
                lastAffected,
                status);
        }
    }
}
