namespace Argus.Infrastructure;

public static class SemanticVersionUtility
{
    public static int Compare(string? left, string? right)
    {
        var leftParsed = SemanticVersion.TryParse(left, out var leftVersion);
        var rightParsed = SemanticVersion.TryParse(right, out var rightVersion);

        if (leftParsed && rightParsed)
        {
            return leftVersion.CompareTo(rightVersion);
        }

        if (leftParsed)
        {
            return 1;
        }

        if (rightParsed)
        {
            return -1;
        }

        return string.Compare(left, right, StringComparison.OrdinalIgnoreCase);
    }

    private sealed class SemanticVersion : IComparable<SemanticVersion>
    {
        private readonly IReadOnlyList<Identifier> _preReleaseIdentifiers;

        private SemanticVersion(int major, int minor, int patch, IReadOnlyList<Identifier> preReleaseIdentifiers)
        {
            Major = major;
            Minor = minor;
            Patch = patch;
            _preReleaseIdentifiers = preReleaseIdentifiers;
        }

        public int Major { get; }

        public int Minor { get; }

        public int Patch { get; }

        public bool IsPreRelease => _preReleaseIdentifiers.Count > 0;

        public static bool TryParse(string? value, out SemanticVersion version)
        {
            version = null!;

            if (string.IsNullOrWhiteSpace(value))
            {
                return false;
            }

            var trimmed = value.Trim();
            if (trimmed.Length > 1 && (trimmed[0] == 'v' || trimmed[0] == 'V') && char.IsDigit(trimmed[1]))
            {
                trimmed = trimmed[1..];
            }

            var buildSeparatorIndex = trimmed.IndexOf('+');
            if (buildSeparatorIndex >= 0)
            {
                trimmed = trimmed[..buildSeparatorIndex];
            }

            string corePart;
            string? preReleasePart = null;
            var preReleaseSeparatorIndex = trimmed.IndexOf('-');
            if (preReleaseSeparatorIndex >= 0)
            {
                corePart = trimmed[..preReleaseSeparatorIndex];
                preReleasePart = trimmed[(preReleaseSeparatorIndex + 1)..];
            }
            else
            {
                corePart = trimmed;
            }

            var coreSegments = corePart.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
            if (coreSegments.Length is < 1 or > 3)
            {
                return false;
            }

            if (!TryParseNumeric(coreSegments[0], out var major))
            {
                return false;
            }

            var minor = 0;
            if (coreSegments.Length >= 2 && !TryParseNumeric(coreSegments[1], out minor))
            {
                return false;
            }

            var patch = 0;
            if (coreSegments.Length == 3 && !TryParseNumeric(coreSegments[2], out patch))
            {
                return false;
            }

            var preReleaseIdentifiers = new List<Identifier>();
            if (!string.IsNullOrWhiteSpace(preReleasePart))
            {
                foreach (var segment in preReleasePart.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
                {
                    if (!Identifier.TryParse(segment, out var identifier))
                    {
                        return false;
                    }

                    preReleaseIdentifiers.Add(identifier);
                }
            }

            version = new SemanticVersion(major, minor, patch, preReleaseIdentifiers);
            return true;
        }

        public int CompareTo(SemanticVersion? other)
        {
            if (other is null)
            {
                return 1;
            }

            var majorComparison = Major.CompareTo(other.Major);
            if (majorComparison != 0)
            {
                return majorComparison;
            }

            var minorComparison = Minor.CompareTo(other.Minor);
            if (minorComparison != 0)
            {
                return minorComparison;
            }

            var patchComparison = Patch.CompareTo(other.Patch);
            if (patchComparison != 0)
            {
                return patchComparison;
            }

            if (!IsPreRelease && !other.IsPreRelease)
            {
                return 0;
            }

            if (!IsPreRelease)
            {
                return 1;
            }

            if (!other.IsPreRelease)
            {
                return -1;
            }

            var count = Math.Max(_preReleaseIdentifiers.Count, other._preReleaseIdentifiers.Count);
            for (var index = 0; index < count; index++)
            {
                if (index >= _preReleaseIdentifiers.Count)
                {
                    return -1;
                }

                if (index >= other._preReleaseIdentifiers.Count)
                {
                    return 1;
                }

                var identifierComparison = _preReleaseIdentifiers[index].CompareTo(other._preReleaseIdentifiers[index]);
                if (identifierComparison != 0)
                {
                    return identifierComparison;
                }
            }

            return 0;
        }

        private static bool TryParseNumeric(string value, out int parsed) =>
            int.TryParse(value, out parsed) && parsed >= 0;

        private readonly record struct Identifier(bool IsNumeric, long NumericValue, string TextValue) : IComparable<Identifier>
        {
            public static bool TryParse(string value, out Identifier identifier)
            {
                identifier = default;

                if (string.IsNullOrWhiteSpace(value))
                {
                    return false;
                }

                var trimmed = value.Trim();
                foreach (var character in trimmed)
                {
                    if (!char.IsLetterOrDigit(character) && character != '-')
                    {
                        return false;
                    }
                }

                if (long.TryParse(trimmed, out var numericValue))
                {
                    identifier = new Identifier(true, numericValue, trimmed);
                    return true;
                }

                identifier = new Identifier(false, 0, trimmed);
                return true;
            }

            public int CompareTo(Identifier other)
            {
                if (IsNumeric && other.IsNumeric)
                {
                    return NumericValue.CompareTo(other.NumericValue);
                }

                if (IsNumeric)
                {
                    return -1;
                }

                if (other.IsNumeric)
                {
                    return 1;
                }

                return string.Compare(TextValue, other.TextValue, StringComparison.OrdinalIgnoreCase);
            }
        }
    }
}
