namespace StellaOps.Feedser.Merge.Comparers;

using System;
using StellaOps.Feedser.Normalization.Distro;

public sealed class NevraComparer : IComparer<Nevra>, IComparer<string>
{
    public static NevraComparer Instance { get; } = new();

    private NevraComparer()
    {
    }

    public int Compare(string? x, string? y)
    {
        if (ReferenceEquals(x, y))
        {
            return 0;
        }

        if (x is null)
        {
            return -1;
        }

        if (y is null)
        {
            return 1;
        }

        var xParsed = Nevra.TryParse(x, out var xNevra);
        var yParsed = Nevra.TryParse(y, out var yNevra);

        if (xParsed && yParsed)
        {
            return Compare(xNevra, yNevra);
        }

        if (xParsed)
        {
            return 1;
        }

        if (yParsed)
        {
            return -1;
        }

        return string.Compare(x, y, StringComparison.Ordinal);
    }

    public int Compare(Nevra? x, Nevra? y)
    {
        if (ReferenceEquals(x, y))
        {
            return 0;
        }

        if (x is null)
        {
            return -1;
        }

        if (y is null)
        {
            return 1;
        }

        var compare = string.Compare(x.Name, y.Name, StringComparison.Ordinal);
        if (compare != 0)
        {
            return compare;
        }

        compare = string.Compare(x.Architecture ?? string.Empty, y.Architecture ?? string.Empty, StringComparison.Ordinal);
        if (compare != 0)
        {
            return compare;
        }

        compare = x.Epoch.CompareTo(y.Epoch);
        if (compare != 0)
        {
            return compare;
        }

        compare = RpmVersionComparer.Compare(x.Version, y.Version);
        if (compare != 0)
        {
            return compare;
        }

        compare = RpmVersionComparer.Compare(x.Release, y.Release);
        if (compare != 0)
        {
            return compare;
        }

        return string.Compare(x.Original, y.Original, StringComparison.Ordinal);
    }
}

internal static class RpmVersionComparer
{
    public static int Compare(string? left, string? right)
    {
        left ??= string.Empty;
        right ??= string.Empty;

        var i = 0;
        var j = 0;

        while (true)
        {
            var leftHasTilde = SkipToNextSegment(left, ref i);
            var rightHasTilde = SkipToNextSegment(right, ref j);

            if (leftHasTilde || rightHasTilde)
            {
                if (leftHasTilde && rightHasTilde)
                {
                    continue;
                }

                return leftHasTilde ? -1 : 1;
            }

            var leftEnd = i >= left.Length;
            var rightEnd = j >= right.Length;
            if (leftEnd || rightEnd)
            {
                if (leftEnd && rightEnd)
                {
                    return 0;
                }

                return leftEnd ? -1 : 1;
            }

            var leftDigit = char.IsDigit(left[i]);
            var rightDigit = char.IsDigit(right[j]);

            if (leftDigit && !rightDigit)
            {
                return 1;
            }

            if (!leftDigit && rightDigit)
            {
                return -1;
            }

            int compare;
            if (leftDigit)
            {
                compare = CompareNumericSegment(left, ref i, right, ref j);
            }
            else
            {
                compare = CompareAlphaSegment(left, ref i, right, ref j);
            }

            if (compare != 0)
            {
                return compare;
            }
        }
    }

    private static bool SkipToNextSegment(string value, ref int index)
    {
        var sawTilde = false;
        while (index < value.Length)
        {
            var current = value[index];
            if (current == '~')
            {
                sawTilde = true;
                index++;
                break;
            }

            if (char.IsLetterOrDigit(current))
            {
                break;
            }

            index++;
        }

        return sawTilde;
    }

    private static int CompareNumericSegment(string value, ref int index, string other, ref int otherIndex)
    {
        var start = index;
        while (index < value.Length && char.IsDigit(value[index]))
        {
            index++;
        }

        var otherStart = otherIndex;
        while (otherIndex < other.Length && char.IsDigit(other[otherIndex]))
        {
            otherIndex++;
        }

        var trimmedStart = start;
        while (trimmedStart < index && value[trimmedStart] == '0')
        {
            trimmedStart++;
        }

        var otherTrimmedStart = otherStart;
        while (otherTrimmedStart < otherIndex && other[otherTrimmedStart] == '0')
        {
            otherTrimmedStart++;
        }

        var length = index - trimmedStart;
        var otherLength = otherIndex - otherTrimmedStart;

        if (length != otherLength)
        {
            return length.CompareTo(otherLength);
        }

        var comparison = value.AsSpan(trimmedStart, length)
            .CompareTo(other.AsSpan(otherTrimmedStart, otherLength), StringComparison.Ordinal);
        if (comparison != 0)
        {
            return comparison;
        }

        return 0;
    }

    private static int CompareAlphaSegment(string value, ref int index, string other, ref int otherIndex)
    {
        var start = index;
        while (index < value.Length && char.IsLetter(value[index]))
        {
            index++;
        }

        var otherStart = otherIndex;
        while (otherIndex < other.Length && char.IsLetter(other[otherIndex]))
        {
            otherIndex++;
        }

        var length = index - start;
        var otherLength = otherIndex - otherStart;

        var comparison = value.AsSpan(start, length)
            .CompareTo(other.AsSpan(otherStart, otherLength), StringComparison.Ordinal);
        if (comparison != 0)
        {
            return comparison;
        }

        return 0;
    }
}
