using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace StellaOps.Feedser.Source.CertCc.Internal;

internal static class CertCcVendorStatementParser
{
    private static readonly string[] PairSeparators =
    {
        "\t",
        " - ",
        " – ",
        " — ",
        " : ",
        ": ",
        " :",
        ":",
    };

    private static readonly char[] BulletPrefixes = { '-', '*', '•', '+', '\t' };
    private static readonly char[] ProductDelimiters = { '/', ',', ';', '&' };

    // Matches dotted numeric versions and simple alphanumeric suffixes (e.g., 4.4.3.6, 3.9.9.12, 10.2a)
    private static readonly Regex VersionTokenRegex = new(@"(?<![A-Za-z0-9])(\d+(?:\.\d+){1,3}(?:[A-Za-z0-9\-]+)?)", RegexOptions.Compiled);

    public static IReadOnlyList<CertCcVendorPatch> Parse(string? statement)
    {
        if (string.IsNullOrWhiteSpace(statement))
        {
            return Array.Empty<CertCcVendorPatch>();
        }

        var patches = new List<CertCcVendorPatch>();
        var lines = statement
            .Replace("\r\n", "\n", StringComparison.Ordinal)
            .Replace('\r', '\n')
            .Split('\n', StringSplitOptions.RemoveEmptyEntries);

        foreach (var rawLine in lines)
        {
            var line = rawLine.Trim();
            if (line.Length == 0)
            {
                continue;
            }

            line = TrimBulletPrefix(line);
            if (line.Length == 0)
            {
                continue;
            }

            if (!TrySplitLine(line, out var productSegment, out var versionSegment))
            {
                continue;
            }

            var versions = ExtractVersions(versionSegment);
            if (versions.Count == 0)
            {
                continue;
            }

            var products = ExtractProducts(productSegment);
            if (products.Count == 0)
            {
                products.Add(string.Empty);
            }

            if (versions.Count == products.Count)
            {
                for (var index = 0; index < versions.Count; index++)
                {
                    patches.Add(new CertCcVendorPatch(products[index], versions[index], line));
                }

                continue;
            }

            if (versions.Count > 1 && products.Count > versions.Count && products.Count % versions.Count == 0)
            {
                var groupSize = products.Count / versions.Count;
                for (var versionIndex = 0; versionIndex < versions.Count; versionIndex++)
                {
                    var start = versionIndex * groupSize;
                    var end = start + groupSize;
                    var version = versions[versionIndex];
                    for (var productIndex = start; productIndex < end && productIndex < products.Count; productIndex++)
                    {
                        patches.Add(new CertCcVendorPatch(products[productIndex], version, line));
                    }
                }

                continue;
            }

            var primaryVersion = versions[0];
            foreach (var product in products)
            {
                patches.Add(new CertCcVendorPatch(product, primaryVersion, line));
            }
        }

        if (patches.Count == 0)
        {
            return Array.Empty<CertCcVendorPatch>();
        }

        return patches
            .Where(static patch => !string.IsNullOrWhiteSpace(patch.Version))
            .Distinct(CertCcVendorPatch.Comparer)
            .OrderBy(static patch => patch.Product, StringComparer.OrdinalIgnoreCase)
            .ThenBy(static patch => patch.Version, StringComparer.Ordinal)
            .ToArray();
    }

    private static string TrimBulletPrefix(string value)
    {
        var trimmed = value.TrimStart(BulletPrefixes).Trim();
        return trimmed.Length == 0 ? value.Trim() : trimmed;
    }

    private static bool TrySplitLine(string line, out string productSegment, out string versionSegment)
    {
        foreach (var separator in PairSeparators)
        {
            var parts = line.Split(separator, 2, StringSplitOptions.TrimEntries);
            if (parts.Length == 2)
            {
                productSegment = parts[0];
                versionSegment = parts[1];
                return true;
            }
        }

        var whitespaceSplit = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
        if (whitespaceSplit.Length >= 2)
        {
            productSegment = string.Join(' ', whitespaceSplit[..^1]);
            versionSegment = whitespaceSplit[^1];
            return true;
        }

        productSegment = string.Empty;
        versionSegment = string.Empty;
        return false;
    }

    private static List<string> ExtractProducts(string segment)
    {
        if (string.IsNullOrWhiteSpace(segment))
        {
            return new List<string>();
        }

        var normalized = segment.Replace('\t', ' ').Trim();
        var tokens = normalized
            .Split(ProductDelimiters, StringSplitOptions.RemoveEmptyEntries)
            .Select(static token => token.Trim())
            .Where(static token => token.Length > 0)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();

        return tokens;
    }

    private static List<string> ExtractVersions(string segment)
    {
        if (string.IsNullOrWhiteSpace(segment))
        {
            return new List<string>();
        }

        var matches = VersionTokenRegex.Matches(segment);
        if (matches.Count == 0)
        {
            return new List<string>();
        }

        var versions = new List<string>(matches.Count);
        foreach (Match match in matches)
        {
            if (match.Groups.Count == 0)
            {
                continue;
            }

            var value = match.Groups[1].Value.Trim();
            if (value.Length == 0)
            {
                continue;
            }

            versions.Add(value);
        }

        return versions
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .Take(32)
            .ToList();
    }
}

internal sealed record CertCcVendorPatch(string Product, string Version, string? RawLine)
{
    public static IEqualityComparer<CertCcVendorPatch> Comparer { get; } = new CertCcVendorPatchComparer();

    private sealed class CertCcVendorPatchComparer : IEqualityComparer<CertCcVendorPatch>
    {
        public bool Equals(CertCcVendorPatch? x, CertCcVendorPatch? y)
        {
            if (ReferenceEquals(x, y))
            {
                return true;
            }

            if (x is null || y is null)
            {
                return false;
            }

            return string.Equals(x.Product, y.Product, StringComparison.OrdinalIgnoreCase)
                && string.Equals(x.Version, y.Version, StringComparison.OrdinalIgnoreCase);
        }

        public int GetHashCode(CertCcVendorPatch obj)
        {
            var product = obj.Product?.ToLowerInvariant() ?? string.Empty;
            var version = obj.Version?.ToLowerInvariant() ?? string.Empty;
            return HashCode.Combine(product, version);
        }
    }
}
