using StellaOps.Feedser.Models;
using StellaOps.Feedser.Normalization.SemVer;
using Xunit;

namespace StellaOps.Feedser.Normalization.Tests;

public sealed class SemVerRangeRuleBuilderTests
{
    private const string Note = "spec:test";

    [Theory]
    [InlineData("< 1.5.0", null, NormalizedVersionRuleTypes.LessThan, null, true, "1.5.0", false, null, false)]
    [InlineData(">= 1.0.0, < 2.0.0", null, NormalizedVersionRuleTypes.Range, "1.0.0", true, "2.0.0", false, null, false)]
    [InlineData(">1.2.3, <=1.3.0", null, NormalizedVersionRuleTypes.Range, "1.2.3", false, null, false, "1.3.0", true)]
    public void Build_ParsesCommonRanges(
        string range,
        string? patched,
        string expectedNormalizedType,
        string? expectedIntroduced,
        bool expectedIntroducedInclusive,
        string? expectedFixed,
        bool expectedFixedInclusive,
        string? expectedLastAffected,
        bool expectedLastInclusive)
    {
        var results = SemVerRangeRuleBuilder.Build(range, patched, Note);
        var result = Assert.Single(results);

        var primitive = result.Primitive;
        Assert.Equal(expectedIntroduced, primitive.Introduced);
        Assert.Equal(expectedIntroducedInclusive, primitive.IntroducedInclusive);
        Assert.Equal(expectedFixed, primitive.Fixed);
        Assert.Equal(expectedFixedInclusive, primitive.FixedInclusive);
        Assert.Equal(expectedLastAffected, primitive.LastAffected);
        Assert.Equal(expectedLastInclusive, primitive.LastAffectedInclusive);

        var normalized = result.NormalizedRule;
        Assert.Equal(NormalizedVersionSchemes.SemVer, normalized.Scheme);
        Assert.Equal(expectedNormalizedType, normalized.Type);
        Assert.Equal(expectedIntroduced, normalized.Min);
        Assert.Equal(expectedIntroduced is null ? (bool?)null : expectedIntroducedInclusive, normalized.MinInclusive);
        Assert.Equal(expectedFixed ?? expectedLastAffected, normalized.Max);
        Assert.Equal(
            expectedFixed is not null ? expectedFixedInclusive : expectedLastInclusive,
            normalized.MaxInclusive);
        Assert.Equal(patched is null && expectedIntroduced is null && expectedFixed is null && expectedLastAffected is null ? null : Note, normalized.Notes);
    }

    [Fact]
    public void Build_UsesPatchedVersionWhenUpperBoundMissing()
    {
        var results = SemVerRangeRuleBuilder.Build(">= 4.0.0", "4.3.6", Note);
        var result = Assert.Single(results);

        Assert.Equal("4.0.0", result.Primitive.Introduced);
        Assert.Equal("4.3.6", result.Primitive.Fixed);
        Assert.False(result.Primitive.FixedInclusive);

        var normalized = result.NormalizedRule;
        Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
        Assert.Equal("4.0.0", normalized.Min);
        Assert.True(normalized.MinInclusive);
        Assert.Equal("4.3.6", normalized.Max);
        Assert.False(normalized.MaxInclusive);
        Assert.Equal(Note, normalized.Notes);
    }

    [Theory]
    [InlineData("^1.2.3", "1.2.3", "2.0.0")]
    [InlineData("~1.2.3", "1.2.3", "1.3.0")]
    [InlineData("~> 1.2", "1.2.0", "1.3.0")]
    public void Build_HandlesCaretAndTilde(string range, string expectedMin, string expectedMax)
    {
        var results = SemVerRangeRuleBuilder.Build(range, null, Note);
        var result = Assert.Single(results);
        var normalized = result.NormalizedRule;
        Assert.Equal(expectedMin, normalized.Min);
        Assert.True(normalized.MinInclusive);
        Assert.Equal(expectedMax, normalized.Max);
        Assert.False(normalized.MaxInclusive);
        Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
    }

    [Theory]
    [InlineData("1.2.x", "1.2.0", "1.3.0")]
    [InlineData("1.x", "1.0.0", "2.0.0")]
    public void Build_HandlesWildcardNotation(string range, string expectedMin, string expectedMax)
    {
        var results = SemVerRangeRuleBuilder.Build(range, null, Note);
        var result = Assert.Single(results);
        Assert.Equal(expectedMin, result.Primitive.Introduced);
        Assert.Equal(expectedMax, result.Primitive.Fixed);

        var normalized = result.NormalizedRule;
        Assert.Equal(expectedMin, normalized.Min);
        Assert.Equal(expectedMax, normalized.Max);
        Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
    }

    [Fact]
    public void Build_PreservesPreReleaseAndMetadataInExactRule()
    {
        var results = SemVerRangeRuleBuilder.Build("= 2.5.1-alpha.1+build.7", null, Note);
        var result = Assert.Single(results);

        Assert.Equal("2.5.1-alpha.1+build.7", result.Primitive.ExactValue);

        var normalized = result.NormalizedRule;
        Assert.Equal(NormalizedVersionRuleTypes.Exact, normalized.Type);
        Assert.Equal("2.5.1-alpha.1+build.7", normalized.Value);
        Assert.Equal(Note, normalized.Notes);
    }

    [Fact]
    public void Build_ParsesComparatorWithoutCommaSeparators()
    {
        var results = SemVerRangeRuleBuilder.Build(">=1.0.0 <1.2.0", null, Note);
        var result = Assert.Single(results);

        var primitive = result.Primitive;
        Assert.Equal("1.0.0", primitive.Introduced);
        Assert.True(primitive.IntroducedInclusive);
        Assert.Equal("1.2.0", primitive.Fixed);
        Assert.False(primitive.FixedInclusive);
        Assert.Equal(">= 1.0.0, < 1.2.0", primitive.ConstraintExpression);

        var normalized = result.NormalizedRule;
        Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
        Assert.Equal("1.0.0", normalized.Min);
        Assert.True(normalized.MinInclusive);
        Assert.Equal("1.2.0", normalized.Max);
        Assert.False(normalized.MaxInclusive);
        Assert.Equal(Note, normalized.Notes);
    }

    [Fact]
    public void Build_HandlesMultipleSegmentsSeparatedByOr()
    {
        var results = SemVerRangeRuleBuilder.Build(">=1.0.0 <1.2.0 || >=2.0.0 <2.2.0", null, Note);
        Assert.Equal(2, results.Count);

        var first = results[0];
        Assert.Equal("1.0.0", first.Primitive.Introduced);
        Assert.Equal("1.2.0", first.Primitive.Fixed);
        Assert.Equal(NormalizedVersionRuleTypes.Range, first.NormalizedRule.Type);
        Assert.Equal("1.0.0", first.NormalizedRule.Min);
        Assert.Equal("1.2.0", first.NormalizedRule.Max);

        var second = results[1];
        Assert.Equal("2.0.0", second.Primitive.Introduced);
        Assert.Equal("2.2.0", second.Primitive.Fixed);
        Assert.Equal(NormalizedVersionRuleTypes.Range, second.NormalizedRule.Type);
        Assert.Equal("2.0.0", second.NormalizedRule.Min);
        Assert.Equal("2.2.0", second.NormalizedRule.Max);

        foreach (var result in results)
        {
            Assert.Equal(Note, result.NormalizedRule.Notes);
        }
    }

    [Fact]
    public void BuildNormalizedRules_ProjectsNormalizedRules()
    {
        var rules = SemVerRangeRuleBuilder.BuildNormalizedRules(">=1.0.0 <1.2.0", null, Note);
        var rule = Assert.Single(rules);

        Assert.Equal(NormalizedVersionSchemes.SemVer, rule.Scheme);
        Assert.Equal(NormalizedVersionRuleTypes.Range, rule.Type);
        Assert.Equal("1.0.0", rule.Min);
        Assert.True(rule.MinInclusive);
        Assert.Equal("1.2.0", rule.Max);
        Assert.False(rule.MaxInclusive);
        Assert.Equal(Note, rule.Notes);
    }

    [Fact]
    public void BuildNormalizedRules_ReturnsEmptyWhenNoRules()
    {
        var rules = SemVerRangeRuleBuilder.BuildNormalizedRules("   ", null, Note);
        Assert.Empty(rules);
    }
}
