using System.Globalization;
using System.Text.Json;

namespace StellaOps.Feedser.Source.Cve.Internal;

internal static class CveListParser
{
    public static CveListPage Parse(ReadOnlySpan<byte> content, int currentPage, int pageSize)
    {
        using var document = JsonDocument.Parse(content.ToArray());
        var root = document.RootElement;

        var items = new List<CveListItem>();
        DateTimeOffset? maxModified = null;

        foreach (var element in EnumerateItemElements(root))
        {
            var cveId = ExtractCveId(element);
            if (string.IsNullOrWhiteSpace(cveId))
            {
                continue;
            }

            var modified = ExtractModified(element);
            if (modified.HasValue && (!maxModified.HasValue || modified > maxModified))
            {
                maxModified = modified;
            }

            items.Add(new CveListItem(cveId, modified));
        }

        var hasMore = TryDetermineHasMore(root, currentPage, pageSize, items.Count, out var nextPage);

        return new CveListPage(items, maxModified, hasMore, nextPage ?? currentPage + 1);
    }

    private static IEnumerable<JsonElement> EnumerateItemElements(JsonElement root)
    {
        if (root.TryGetProperty("data", out var dataElement) && dataElement.ValueKind == JsonValueKind.Array)
        {
            foreach (var item in dataElement.EnumerateArray())
            {
                yield return item;
            }
            yield break;
        }

        if (root.TryGetProperty("vulnerabilities", out var vulnerabilities) && vulnerabilities.ValueKind == JsonValueKind.Array)
        {
            foreach (var item in vulnerabilities.EnumerateArray())
            {
                yield return item;
            }
            yield break;
        }

        if (root.ValueKind == JsonValueKind.Array)
        {
            foreach (var item in root.EnumerateArray())
            {
                yield return item;
            }
        }
    }

    private static string? ExtractCveId(JsonElement element)
    {
        if (element.TryGetProperty("cveId", out var cveId) && cveId.ValueKind == JsonValueKind.String)
        {
            return cveId.GetString();
        }

        if (element.TryGetProperty("cveMetadata", out var metadata))
        {
            if (metadata.TryGetProperty("cveId", out var metadataId) && metadataId.ValueKind == JsonValueKind.String)
            {
                return metadataId.GetString();
            }
        }

        if (element.TryGetProperty("cve", out var cve) && cve.ValueKind == JsonValueKind.Object)
        {
            if (cve.TryGetProperty("cveMetadata", out var nestedMeta) && nestedMeta.ValueKind == JsonValueKind.Object)
            {
                if (nestedMeta.TryGetProperty("cveId", out var nestedId) && nestedId.ValueKind == JsonValueKind.String)
                {
                    return nestedId.GetString();
                }
            }

            if (cve.TryGetProperty("id", out var cveIdElement) && cveIdElement.ValueKind == JsonValueKind.String)
            {
                return cveIdElement.GetString();
            }
        }

        return null;
    }

    private static DateTimeOffset? ExtractModified(JsonElement element)
    {
        static DateTimeOffset? Parse(JsonElement candidate)
        {
            return candidate.ValueKind switch
            {
                JsonValueKind.String when DateTimeOffset.TryParse(candidate.GetString(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed)
                    => parsed.ToUniversalTime(),
                _ => null,
            };
        }

        if (element.TryGetProperty("dateUpdated", out var dateUpdated))
        {
            var parsed = Parse(dateUpdated);
            if (parsed.HasValue)
            {
                return parsed;
            }
        }

        if (element.TryGetProperty("cveMetadata", out var metadata))
        {
            if (metadata.TryGetProperty("dateUpdated", out var metadataUpdated))
            {
                var parsed = Parse(metadataUpdated);
                if (parsed.HasValue)
                {
                    return parsed;
                }
            }
        }

        if (element.TryGetProperty("cve", out var cve) && cve.ValueKind == JsonValueKind.Object)
        {
            if (cve.TryGetProperty("cveMetadata", out var nestedMeta))
            {
                if (nestedMeta.TryGetProperty("dateUpdated", out var nestedUpdated))
                {
                    var parsed = Parse(nestedUpdated);
                    if (parsed.HasValue)
                    {
                        return parsed;
                    }
                }
            }

            if (cve.TryGetProperty("lastModified", out var lastModified))
            {
                var parsed = Parse(lastModified);
                if (parsed.HasValue)
                {
                    return parsed;
                }
            }
        }

        return null;
    }

    private static bool TryDetermineHasMore(JsonElement root, int currentPage, int pageSize, int itemCount, out int? nextPage)
    {
        nextPage = null;

        if (root.TryGetProperty("pagination", out var pagination) && pagination.ValueKind == JsonValueKind.Object)
        {
            var totalPages = TryGetInt(pagination, "totalPages")
                ?? TryGetInt(pagination, "pageCount")
                ?? TryGetInt(pagination, "totalPagesCount");
            if (totalPages.HasValue)
            {
                if (currentPage < totalPages.Value)
                {
                    nextPage = currentPage + 1;
                    return true;
                }

                return false;
            }

            var totalCount = TryGetInt(pagination, "totalCount")
                ?? TryGetInt(pagination, "totalResults");
            var limit = TryGetInt(pagination, "limit")
                ?? TryGetInt(pagination, "itemsPerPage")
                ?? TryGetInt(pagination, "pageSize")
                ?? pageSize;

            if (totalCount.HasValue)
            {
                var processed = (currentPage - 1) * limit + itemCount;
                if (processed < totalCount.Value)
                {
                    nextPage = currentPage + 1;
                    return true;
                }

                return false;
            }

            if (pagination.TryGetProperty("nextPage", out var nextPageElement))
            {
                switch (nextPageElement.ValueKind)
                {
                    case JsonValueKind.Number when nextPageElement.TryGetInt32(out var value):
                        nextPage = value;
                        return true;
                    case JsonValueKind.String when int.TryParse(nextPageElement.GetString(), out var parsed):
                        nextPage = parsed;
                        return true;
                    case JsonValueKind.String when !string.IsNullOrWhiteSpace(nextPageElement.GetString()):
                        nextPage = currentPage + 1;
                        return true;
                }
            }
        }

        if (root.TryGetProperty("nextPage", out var nextPageValue))
        {
            switch (nextPageValue.ValueKind)
            {
                case JsonValueKind.Number when nextPageValue.TryGetInt32(out var value):
                    nextPage = value;
                    return true;
                case JsonValueKind.String when int.TryParse(nextPageValue.GetString(), out var parsed):
                    nextPage = parsed;
                    return true;
                case JsonValueKind.String when !string.IsNullOrWhiteSpace(nextPageValue.GetString()):
                    nextPage = currentPage + 1;
                    return true;
            }
        }

        if (itemCount >= pageSize)
        {
            nextPage = currentPage + 1;
            return true;
        }

        return false;
    }

    private static int? TryGetInt(JsonElement element, string propertyName)
    {
        if (!element.TryGetProperty(propertyName, out var value))
        {
            return null;
        }

        return value.ValueKind switch
        {
            JsonValueKind.Number when value.TryGetInt32(out var number) => number,
            JsonValueKind.String when int.TryParse(value.GetString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed) => parsed,
            _ => null,
        };
    }
}

internal sealed record CveListPage(
    IReadOnlyList<CveListItem> Items,
    DateTimeOffset? MaxModified,
    bool HasMorePages,
    int NextPageCandidate);

internal sealed record CveListItem(string CveId, DateTimeOffset? DateUpdated);
