using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;

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

internal static class UbuntuNoticeParser
{
    public static UbuntuIndexResponse ParseIndex(string json)
    {
        ArgumentException.ThrowIfNullOrEmpty(json);

        using var document = JsonDocument.Parse(json);
        var root = document.RootElement;
        if (!root.TryGetProperty("notices", out var noticesElement) || noticesElement.ValueKind != JsonValueKind.Array)
        {
            return UbuntuIndexResponse.Empty;
        }

        var notices = new List<UbuntuNoticeDto>(noticesElement.GetArrayLength());
        foreach (var noticeElement in noticesElement.EnumerateArray())
        {
            if (!noticeElement.TryGetProperty("id", out var idElement))
            {
                continue;
            }

            var noticeId = idElement.GetString();
            if (string.IsNullOrWhiteSpace(noticeId))
            {
                continue;
            }

            var published = ParseDate(noticeElement, "published") ?? DateTimeOffset.UtcNow;
            var title = noticeElement.TryGetProperty("title", out var titleElement)
                ? titleElement.GetString() ?? noticeId
                : noticeId;

            var summary = noticeElement.TryGetProperty("summary", out var summaryElement)
                ? summaryElement.GetString() ?? string.Empty
                : string.Empty;

            var cves = ExtractCves(noticeElement);
            var references = ExtractReferences(noticeElement);
            var packages = ExtractPackages(noticeElement);

            if (packages.Count == 0)
            {
                continue;
            }

            notices.Add(new UbuntuNoticeDto(
                noticeId,
                published,
                title,
                summary,
                cves,
                packages,
                references));
        }

        var offset = root.TryGetProperty("offset", out var offsetElement) && offsetElement.ValueKind == JsonValueKind.Number
            ? offsetElement.GetInt32()
            : 0;

        var limit = root.TryGetProperty("limit", out var limitElement) && limitElement.ValueKind == JsonValueKind.Number
            ? limitElement.GetInt32()
            : noticesElement.GetArrayLength();

        var totalResults = root.TryGetProperty("total_results", out var totalElement) && totalElement.ValueKind == JsonValueKind.Number
            ? totalElement.GetInt32()
            : notices.Count;

        return new UbuntuIndexResponse(offset, limit, totalResults, notices);
    }

    private static IReadOnlyList<string> ExtractCves(JsonElement noticeElement)
    {
        if (!noticeElement.TryGetProperty("cves", out var cveArray) || cveArray.ValueKind != JsonValueKind.Array)
        {
            return Array.Empty<string>();
        }

        var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var cveElement in cveArray.EnumerateArray())
        {
            var cve = cveElement.TryGetProperty("id", out var idElement)
                ? idElement.GetString()
                : cveElement.GetString();

            if (!string.IsNullOrWhiteSpace(cve))
            {
                set.Add(cve.Trim());
            }
        }

        if (set.Count == 0)
        {
            return Array.Empty<string>();
        }

        var list = new List<string>(set);
        list.Sort(StringComparer.OrdinalIgnoreCase);
        return list;
    }

    private static IReadOnlyList<UbuntuReferenceDto> ExtractReferences(JsonElement noticeElement)
    {
        if (!noticeElement.TryGetProperty("references", out var referencesElement) || referencesElement.ValueKind != JsonValueKind.Array)
        {
            return Array.Empty<UbuntuReferenceDto>();
        }

        var list = new List<UbuntuReferenceDto>();
        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var referenceElement in referencesElement.EnumerateArray())
        {
            var url = referenceElement.TryGetProperty("url", out var urlElement)
                ? urlElement.GetString()
                : null;

            if (string.IsNullOrWhiteSpace(url) || !seen.Add(url))
            {
                continue;
            }

            var kind = referenceElement.TryGetProperty("category", out var categoryElement)
                ? categoryElement.GetString()
                : null;

            var title = referenceElement.TryGetProperty("summary", out var summaryElement)
                ? summaryElement.GetString()
                : null;

            list.Add(new UbuntuReferenceDto(url.Trim(), kind, title));
        }

        return list.Count == 0 ? Array.Empty<UbuntuReferenceDto>() : list;
    }

    private static IReadOnlyList<UbuntuReleasePackageDto> ExtractPackages(JsonElement noticeElement)
    {
        if (!noticeElement.TryGetProperty("release_packages", out var releasesElement) || releasesElement.ValueKind != JsonValueKind.Object)
        {
            return Array.Empty<UbuntuReleasePackageDto>();
        }

        var packages = new List<UbuntuReleasePackageDto>();
        foreach (var releaseProperty in releasesElement.EnumerateObject())
        {
            var release = releaseProperty.Name;
            var packageArray = releaseProperty.Value;
            if (packageArray.ValueKind != JsonValueKind.Array)
            {
                continue;
            }

            foreach (var packageElement in packageArray.EnumerateArray())
            {
                var name = packageElement.TryGetProperty("name", out var nameElement)
                    ? nameElement.GetString()
                    : null;

                var version = packageElement.TryGetProperty("version", out var versionElement)
                    ? versionElement.GetString()
                    : null;

                if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(version))
                {
                    continue;
                }

                var pocket = packageElement.TryGetProperty("pocket", out var pocketElement)
                    ? pocketElement.GetString() ?? string.Empty
                    : string.Empty;

                var isSource = packageElement.TryGetProperty("is_source", out var sourceElement)
                    && sourceElement.ValueKind == JsonValueKind.True;

                packages.Add(new UbuntuReleasePackageDto(
                    release,
                    name.Trim(),
                    version.Trim(),
                    pocket.Trim(),
                    isSource));
            }
        }

        return packages.Count == 0 ? Array.Empty<UbuntuReleasePackageDto>() : packages;
    }

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

        var value = dateElement.GetString();
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        return DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed)
            ? parsed.ToUniversalTime()
            : null;
    }
}

internal sealed record UbuntuIndexResponse(int Offset, int Limit, int TotalResults, IReadOnlyList<UbuntuNoticeDto> Notices)
{
    public static UbuntuIndexResponse Empty { get; } = new(0, 0, 0, Array.Empty<UbuntuNoticeDto>());
}
