namespace StellaOps.Feedser.Merge.Comparers;

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

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

    private DebianEvrComparer()
    {
    }

    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 = DebianEvr.TryParse(x, out var xEvr);
        var yParsed = DebianEvr.TryParse(y, out var yEvr);

        if (xParsed && yParsed)
        {
            return Compare(xEvr, yEvr);
        }

        if (xParsed)
        {
            return 1;
        }

        if (yParsed)
        {
            return -1;
        }

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

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

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

        if (y is null)
        {
            return 1;
        }

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

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

        compare = CompareSegment(x.Revision, y.Revision);
        if (compare != 0)
        {
            return compare;
        }

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

    private static int CompareSegment(string left, string right)
    {
        var i = 0;
        var j = 0;

        while (i < left.Length || j < right.Length)
        {
            while (i < left.Length && !IsAlphaNumeric(left[i]) && left[i] != '~')
            {
                i++;
            }

            while (j < right.Length && !IsAlphaNumeric(right[j]) && right[j] != '~')
            {
                j++;
            }

            var leftChar = i < left.Length ? left[i] : '\0';
            var rightChar = j < right.Length ? right[j] : '\0';

            if (leftChar == '~' || rightChar == '~')
            {
                if (leftChar != rightChar)
                {
                    return leftChar == '~' ? -1 : 1;
                }

                i += leftChar == '~' ? 1 : 0;
                j += rightChar == '~' ? 1 : 0;
                continue;
            }

            var leftIsDigit = char.IsDigit(leftChar);
            var rightIsDigit = char.IsDigit(rightChar);

            if (leftIsDigit && rightIsDigit)
            {
                var leftStart = i;
                while (i < left.Length && char.IsDigit(left[i]))
                {
                    i++;
                }

                var rightStart = j;
                while (j < right.Length && char.IsDigit(right[j]))
                {
                    j++;
                }

                var leftTrimmed = leftStart;
                while (leftTrimmed < i && left[leftTrimmed] == '0')
                {
                    leftTrimmed++;
                }

                var rightTrimmed = rightStart;
                while (rightTrimmed < j && right[rightTrimmed] == '0')
                {
                    rightTrimmed++;
                }

                var leftLength = i - leftTrimmed;
                var rightLength = j - rightTrimmed;

                if (leftLength != rightLength)
                {
                    return leftLength.CompareTo(rightLength);
                }

                var comparison = left.AsSpan(leftTrimmed, leftLength)
                    .CompareTo(right.AsSpan(rightTrimmed, rightLength), StringComparison.Ordinal);
                if (comparison != 0)
                {
                    return comparison;
                }

                continue;
            }

            if (leftIsDigit)
            {
                return 1;
            }

            if (rightIsDigit)
            {
                return -1;
            }

            var leftOrder = CharOrder(leftChar);
            var rightOrder = CharOrder(rightChar);

            var orderComparison = leftOrder.CompareTo(rightOrder);
            if (orderComparison != 0)
            {
                return orderComparison;
            }

            if (leftChar != rightChar)
            {
                return leftChar.CompareTo(rightChar);
            }

            if (leftChar == '\0')
            {
                return 0;
            }

            i++;
            j++;
        }

        return 0;
    }

    private static bool IsAlphaNumeric(char value)
        => char.IsLetterOrDigit(value);

    private static int CharOrder(char value)
    {
        if (value == '\0')
        {
            return 0;
        }

        if (value == '~')
        {
            return -1;
        }

        if (char.IsDigit(value))
        {
            return 0;
        }

        if (char.IsLetter(value))
        {
            return value;
        }

        return value + 256;
    }
}
