using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NuGet.Versioning;
using StellaOps.Feedser.Models;

namespace StellaOps.Feedser.Normalization.SemVer;

/// <summary>
/// Parses SemVer-style range expressions into deterministic primitives and normalized rules.
/// Supports caret, tilde, wildcard, hyphen, and comparator syntaxes with optional provenance notes.
/// </summary>
public static class SemVerRangeRuleBuilder
{
    private static readonly Regex ComparatorRegex = new(@"^(?<op>>=|<=|>|<|==|=)\s*(?<value>.+)$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
    private static readonly Regex ComparatorTokenRegex = new(@"(?<token>(?<op>>=|<=|>|<|==|=)\s*(?<value>[^,\s\|]+))", RegexOptions.Compiled | RegexOptions.CultureInvariant);
    private static readonly Regex HyphenRegex = new(@"^\s*(?<start>.+?)\s+-\s+(?<end>.+)\s*$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
    private static readonly char[] SegmentTrimCharacters = { '(', ')', '[', ']', '{', '}', ';' };
    private static readonly char[] FragmentSplitCharacters = { ',', ' ' };

    public static IReadOnlyList<SemVerRangeBuildResult> Build(string? rawRange, string? patchedVersion = null, string? provenanceNote = null)
    {
        var results = new List<SemVerRangeBuildResult>();

        if (!string.IsNullOrWhiteSpace(rawRange))
        {
            foreach (var segment in SplitSegments(rawRange))
            {
                if (TryBuildFromSegment(segment, patchedVersion, provenanceNote, out var result))
                {
                    results.Add(result);
                }
            }
        }

        if (results.Count == 0 && TryNormalizeVersion(patchedVersion, out var normalizedPatched))
        {
            var constraint = $"< {normalizedPatched}";
            var fallbackPrimitive = new SemVerPrimitive(
                Introduced: null,
                IntroducedInclusive: false,
                Fixed: normalizedPatched,
                FixedInclusive: false,
                LastAffected: null,
                LastAffectedInclusive: false,
                ConstraintExpression: constraint);

            var fallbackRule = fallbackPrimitive.ToNormalizedVersionRule(provenanceNote);
            if (fallbackRule is not null)
            {
                results.Add(new SemVerRangeBuildResult(fallbackPrimitive, fallbackRule, constraint));
            }
        }

        return results.Count == 0 ? Array.Empty<SemVerRangeBuildResult>() : results;
    }

    public static IReadOnlyList<NormalizedVersionRule> BuildNormalizedRules(string? rawRange, string? patchedVersion = null, string? provenanceNote = null)
    {
        var results = Build(rawRange, patchedVersion, provenanceNote);
        if (results.Count == 0)
        {
            return Array.Empty<NormalizedVersionRule>();
        }

        var rules = new NormalizedVersionRule[results.Count];
        for (var i = 0; i < results.Count; i++)
        {
            rules[i] = results[i].NormalizedRule;
        }

        return rules;
    }

    private static IEnumerable<string> SplitSegments(string rawRange)
        => rawRange.Split("||", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

    private static bool TryBuildFromSegment(string segment, string? patchedVersion, string? provenanceNote, out SemVerRangeBuildResult result)
    {
        result = default!;

        if (!TryParsePrimitive(segment, patchedVersion, out var primitive))
        {
            return false;
        }

        var normalized = primitive.ToNormalizedVersionRule(provenanceNote);
        if (normalized is null)
        {
            return false;
        }

        result = new SemVerRangeBuildResult(primitive, normalized, primitive.ConstraintExpression ?? segment.Trim());
        return true;
    }

    private static bool TryParsePrimitive(string segment, string? patchedVersion, out SemVerPrimitive primitive)
    {
        primitive = default!;

        var trimmed = NormalizeWhitespace(segment);
        if (string.IsNullOrEmpty(trimmed) || IsWildcardToken(trimmed))
        {
            return false;
        }

        if (TryParseCaret(trimmed, out primitive))
        {
            return true;
        }

        if (TryParseTilde(trimmed, out primitive))
        {
            return true;
        }

        if (TryParseHyphen(trimmed, out primitive))
        {
            return true;
        }

        if (TryParseWildcard(trimmed, out primitive))
        {
            return true;
        }

        if (TryParseComparators(trimmed, patchedVersion, out primitive))
        {
            return true;
        }

        if (TryParseExact(trimmed, out primitive))
        {
            return true;
        }

        return false;
    }

    private static bool TryParseCaret(string expression, out SemVerPrimitive primitive)
    {
        primitive = default!;

        if (!expression.StartsWith("^", StringComparison.Ordinal))
        {
            return false;
        }

        var value = expression[1..].Trim();
        if (!TryParseSemanticVersion(value, out var version, out var normalizedBase))
        {
            return false;
        }

        var upper = CalculateCaretUpperBound(version);
        var upperNormalized = FormatVersion(upper);
        var constraint = $">= {normalizedBase} < {upperNormalized}";

        primitive = new SemVerPrimitive(
            Introduced: normalizedBase,
            IntroducedInclusive: true,
            Fixed: upperNormalized,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: constraint);
        return true;
    }

    private static bool TryParseTilde(string expression, out SemVerPrimitive primitive)
    {
        primitive = default!;

        if (!expression.StartsWith("~", StringComparison.Ordinal))
        {
            return false;
        }

        var remainder = expression.TrimStart('~');
        if (remainder.StartsWith(">", StringComparison.Ordinal))
        {
            remainder = remainder[1..];
        }

        remainder = remainder.Trim();
        if (!TryParseSemanticVersion(remainder, out var version, out var normalizedBase))
        {
            return false;
        }

        var componentCount = CountExplicitComponents(remainder);
        var upper = CalculateTildeUpperBound(version, componentCount);
        var upperNormalized = FormatVersion(upper);
        var constraint = $">= {normalizedBase} < {upperNormalized}";

        primitive = new SemVerPrimitive(
            Introduced: normalizedBase,
            IntroducedInclusive: true,
            Fixed: upperNormalized,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: constraint);
        return true;
    }

    private static bool TryParseHyphen(string expression, out SemVerPrimitive primitive)
    {
        primitive = default!;

        var match = HyphenRegex.Match(expression);
        if (!match.Success)
        {
            return false;
        }

        var startRaw = match.Groups["start"].Value.Trim();
        var endRaw = match.Groups["end"].Value.Trim();

        if (!TryNormalizeVersion(startRaw, out var start) || !TryNormalizeVersion(endRaw, out var end))
        {
            return false;
        }

        var constraint = $"{start} - {end}";
        primitive = new SemVerPrimitive(
            Introduced: start,
            IntroducedInclusive: true,
            Fixed: null,
            FixedInclusive: false,
            LastAffected: end,
            LastAffectedInclusive: true,
            ConstraintExpression: constraint);
        return true;
    }

    private static bool TryParseWildcard(string expression, out SemVerPrimitive primitive)
    {
        primitive = default!;

        var sanitized = expression.Trim(SegmentTrimCharacters).Trim();
        if (string.IsNullOrEmpty(sanitized))
        {
            return false;
        }

        var parts = sanitized.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (parts.Length == 0 || parts.All(static part => !IsWildcardToken(part)))
        {
            return false;
        }

        if (!int.TryParse(RemoveLeadingV(parts[0]), NumberStyles.Integer, CultureInfo.InvariantCulture, out var major))
        {
            return false;
        }

        var hasMinor = parts.Length > 1 && !IsWildcardToken(parts[1]);
        var hasPatch = parts.Length > 2 && !IsWildcardToken(parts[2]);
        var minor = hasMinor && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedMinor) ? parsedMinor : 0;
        var patch = hasPatch && int.TryParse(parts[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedPatch) ? parsedPatch : 0;

        var minVersion = new SemanticVersion(major, hasMinor ? minor : 0, hasPatch ? patch : 0);
        var minNormalized = FormatVersion(minVersion);

        SemanticVersion upperVersion;
        if (!hasMinor)
        {
            upperVersion = new SemanticVersion(major + 1, 0, 0);
        }
        else if (!hasPatch)
        {
            upperVersion = new SemanticVersion(major, minor + 1, 0);
        }
        else
        {
            upperVersion = new SemanticVersion(major, minor + 1, 0);
        }

        var upperNormalized = FormatVersion(upperVersion);
        var constraint = $">= {minNormalized} < {upperNormalized}";

        primitive = new SemVerPrimitive(
            Introduced: minNormalized,
            IntroducedInclusive: true,
            Fixed: upperNormalized,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: constraint);
        return true;
    }

    private static bool TryParseComparators(string expression, string? patchedVersion, out SemVerPrimitive primitive)
    {
        primitive = default!;

        var fragments = expression.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (fragments.Length == 0)
        {
            fragments = new[] { expression };
        }

        var constraintTokens = new List<string>();

        string? introduced = null;
        bool introducedInclusive = true;
        var hasIntroduced = false;

        string? fixedVersion = null;
        bool fixedInclusive = false;
        var hasFixed = false;

        string? lastAffected = null;
        bool lastInclusive = true;
        var hasLast = false;

        string? exactValue = null;

        foreach (var fragment in fragments)
        {
            var handled = false;

            foreach (Match match in ComparatorTokenRegex.Matches(fragment))
            {
                if (match.Groups["token"].Success
                    && TryParseComparatorFragment(match.Groups["token"].Value, constraintTokens, ref introduced, ref introducedInclusive, ref hasIntroduced, ref fixedVersion, ref fixedInclusive, ref hasFixed, ref lastAffected, ref lastInclusive, ref hasLast, ref exactValue))
                {
                    handled = true;
                }
            }

            if (!handled)
            {
                var parts = fragment.Split(FragmentSplitCharacters, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                foreach (var part in parts)
                {
                    if (TryParseComparatorFragment(part, constraintTokens, ref introduced, ref introducedInclusive, ref hasIntroduced, ref fixedVersion, ref fixedInclusive, ref hasFixed, ref lastAffected, ref lastInclusive, ref hasLast, ref exactValue))
                    {
                        handled = true;
                    }
                }
            }

            if (handled)
            {
                continue;
            }

            if (TryNormalizeVersion(fragment, out var normalizedExact))
            {
                exactValue = normalizedExact;
                constraintTokens.Add(normalizedExact);
            }
        }

        if (exactValue is not null)
        {
            primitive = new SemVerPrimitive(
                Introduced: exactValue,
                IntroducedInclusive: true,
                Fixed: exactValue,
                FixedInclusive: true,
                LastAffected: null,
                LastAffectedInclusive: false,
                ConstraintExpression: constraintTokens.Count > 0 ? string.Join(", ", constraintTokens) : expression.Trim(),
                ExactValue: exactValue);
            return true;
        }

        if (hasIntroduced && !hasFixed && !hasLast && TryNormalizeVersion(patchedVersion, out var patchedNormalized))
        {
            fixedVersion = patchedNormalized;
            fixedInclusive = false;
            hasFixed = true;
        }

        if (!hasIntroduced && !hasFixed && !hasLast)
        {
            primitive = default!;
            return false;
        }

        var constraint = constraintTokens.Count > 0 ? string.Join(", ", constraintTokens) : expression.Trim();
        primitive = new SemVerPrimitive(
            Introduced: hasIntroduced ? introduced : null,
            IntroducedInclusive: hasIntroduced ? introducedInclusive : true,
            Fixed: hasFixed ? fixedVersion : null,
            FixedInclusive: hasFixed ? fixedInclusive : false,
            LastAffected: hasLast ? lastAffected : null,
            LastAffectedInclusive: hasLast ? lastInclusive : false,
            ConstraintExpression: constraint);
        return true;
    }

    private static bool TryParseExact(string expression, out SemVerPrimitive primitive)
    {
        primitive = default!;

        if (!TryNormalizeVersion(expression, out var normalized))
        {
            return false;
        }

        primitive = new SemVerPrimitive(
            Introduced: normalized,
            IntroducedInclusive: true,
            Fixed: normalized,
            FixedInclusive: true,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: normalized);
        return true;
    }

    private static bool TryParseComparatorFragment(
        string fragment,
        ICollection<string> constraintTokens,
        ref string? introduced,
        ref bool introducedInclusive,
        ref bool hasIntroduced,
        ref string? fixedVersion,
        ref bool fixedInclusive,
        ref bool hasFixed,
        ref string? lastAffected,
        ref bool lastInclusive,
        ref bool hasLast,
        ref string? exactValue)
    {
        var trimmed = fragment.Trim();
        trimmed = trimmed.Trim(SegmentTrimCharacters);

        if (string.IsNullOrEmpty(trimmed))
        {
            return false;
        }

        var match = ComparatorRegex.Match(trimmed);
        if (!match.Success)
        {
            return false;
        }

        var op = match.Groups["op"].Value;
        var rawValue = match.Groups["value"].Value;
        if (!TryNormalizeVersion(rawValue, out var value))
        {
            return true;
        }

        switch (op)
        {
            case ">=":
                introduced = value!;
                introducedInclusive = true;
                hasIntroduced = true;
                constraintTokens.Add($">= {value}");
                break;
            case ">":
                introduced = value!;
                introducedInclusive = false;
                hasIntroduced = true;
                constraintTokens.Add($"> {value}");
                break;
            case "<=":
                lastAffected = value!;
                lastInclusive = true;
                hasLast = true;
                constraintTokens.Add($"<= {value}");
                break;
            case "<":
                fixedVersion = value!;
                fixedInclusive = false;
                hasFixed = true;
                constraintTokens.Add($"< {value}");
                break;
            case "=":
            case "==":
                exactValue = value!;
                constraintTokens.Add($"= {value}");
                break;
        }

        return true;
    }

    private static bool TryNormalizeVersion(string? value, [NotNullWhen(true)] out string normalized)
    {
        normalized = string.Empty;
        if (string.IsNullOrWhiteSpace(value))
        {
            return false;
        }

        var trimmed = value.Trim();
        trimmed = trimmed.Trim(SegmentTrimCharacters);
        trimmed = trimmed.Trim('\'', '"', '`');

        if (string.IsNullOrWhiteSpace(trimmed) || IsWildcardToken(trimmed))
        {
            return false;
        }

        var candidate = RemoveLeadingV(trimmed);
        if (SemanticVersion.TryParse(candidate, out var semanticVersion))
        {
            normalized = FormatVersion(semanticVersion);
            return true;
        }

        if (trimmed.IndexOfAny(new[] { '*', 'x', 'X' }) >= 0)
        {
            return false;
        }

        normalized = candidate;
        return true;
    }

    private static bool TryParseSemanticVersion(string value, [NotNullWhen(true)] out SemanticVersion version, out string normalized)
    {
        normalized = string.Empty;

        var candidate = RemoveLeadingV(value);
        if (!SemanticVersion.TryParse(candidate, out var parsed))
        {
            candidate = ExpandSemanticVersion(candidate);
            if (!SemanticVersion.TryParse(candidate, out parsed))
            {
                version = null!;
                return false;
            }
        }

        version = parsed!;
        normalized = FormatVersion(parsed);
        return true;
    }

    private static string ExpandSemanticVersion(string value)
    {
        var partCount = value.Count(static ch => ch == '.');
        return partCount switch
        {
            0 => value + ".0.0",
            1 => value + ".0",
            _ => value,
        };
    }

    private static SemanticVersion CalculateCaretUpperBound(SemanticVersion baseVersion)
    {
        if (baseVersion.Major > 0)
        {
            return new SemanticVersion(baseVersion.Major + 1, 0, 0);
    }

        if (baseVersion.Minor > 0)
        {
            return new SemanticVersion(0, baseVersion.Minor + 1, 0);
        }

        return new SemanticVersion(0, 0, baseVersion.Patch + 1);
    }

    private static SemanticVersion CalculateTildeUpperBound(SemanticVersion baseVersion, int componentCount)
    {
        if (componentCount <= 1)
        {
            return new SemanticVersion(baseVersion.Major + 1, 0, 0);
        }

        return new SemanticVersion(baseVersion.Major, baseVersion.Minor + 1, 0);
    }

    private static int CountExplicitComponents(string value)
    {
        var head = value.Split(new[] { '-', '+' }, 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        return head.Length == 0
            ? 0
            : head[0].Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Length;
    }

    private static string NormalizeWhitespace(string value)
    {
        var builder = new StringBuilder(value.Length);
        var previousWhitespace = false;

        foreach (var ch in value)
        {
            if (char.IsWhiteSpace(ch))
            {
                if (!previousWhitespace)
                {
                    builder.Append(' ');
                    previousWhitespace = true;
                }
            }
            else
            {
                builder.Append(ch);
                previousWhitespace = false;
            }
        }

        return builder.ToString().Trim();
    }

    private static string RemoveLeadingV(string value)
    {
        if (value.Length > 1 && (value[0] == 'v' || value[0] == 'V') && char.IsDigit(value[1]))
        {
            return value[1..];
        }

        return value;
    }

    private static string FormatVersion(SemanticVersion version)
    {
        var normalized = version.ToNormalizedString();
        if (!string.IsNullOrEmpty(version.Metadata))
        {
            normalized += "+" + version.Metadata;
        }

        return normalized;
    }

    private static bool IsWildcardToken(string value)
        => string.Equals(value, "*", StringComparison.OrdinalIgnoreCase)
            || string.Equals(value, "x", StringComparison.OrdinalIgnoreCase);
}

public sealed record SemVerRangeBuildResult(
    SemVerPrimitive Primitive,
    NormalizedVersionRule NormalizedRule,
    string Expression)
{
    public string? ConstraintExpression => Primitive.ConstraintExpression;
}
