using StellaOps.Feedser.Models;
using Xunit;

namespace StellaOps.Feedser.Models.Tests;

public sealed class SemVerPrimitiveTests
{
    [Theory]
    [InlineData("1.0.0", true, "2.0.0", false, null, false, null, null, SemVerPrimitiveStyles.Range)]
    [InlineData("1.0.0", true, null, false, null, false, null, null, SemVerPrimitiveStyles.GreaterThanOrEqual)]
    [InlineData("1.0.0", false, null, false, null, false, null, null, SemVerPrimitiveStyles.GreaterThan)]
    [InlineData(null, true, "2.0.0", false, null, false, null, null, SemVerPrimitiveStyles.LessThan)]
    [InlineData(null, true, "2.0.0", true, null, false, null, null, SemVerPrimitiveStyles.LessThanOrEqual)]
    [InlineData(null, true, null, false, "2.0.0", true, null, null, SemVerPrimitiveStyles.LessThanOrEqual)]
    [InlineData(null, true, null, false, "2.0.0", false, null, null, SemVerPrimitiveStyles.LessThan)]
    [InlineData(null, true, null, false, null, false, null, "1.5.0", SemVerPrimitiveStyles.Exact)]
    public void StyleReflectsSemantics(
        string? introduced,
        bool introducedInclusive,
        string? fixedVersion,
        bool fixedInclusive,
        string? lastAffected,
        bool lastAffectedInclusive,
        string? constraintExpression,
        string? exactValue,
        string expectedStyle)
    {
        var primitive = new SemVerPrimitive(
            introduced,
            introducedInclusive,
            fixedVersion,
            fixedInclusive,
            lastAffected,
            lastAffectedInclusive,
            constraintExpression,
            exactValue);

        Assert.Equal(expectedStyle, primitive.Style);
    }

    [Fact]
    public void EqualityIncludesExactValue()
    {
        var baseline = new SemVerPrimitive(
            Introduced: null,
            IntroducedInclusive: true,
            Fixed: null,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: null);

        var variant = baseline with { ExactValue = "1.2.3" };

        Assert.NotEqual(baseline, variant);
        Assert.Equal(SemVerPrimitiveStyles.Exact, variant.Style);
        Assert.Equal(SemVerPrimitiveStyles.Range, baseline.Style);
    }

    [Fact]
    public void ToNormalizedVersionRule_MapsRangeBounds()
    {
        var primitive = new SemVerPrimitive(
            Introduced: "1.0.0",
            IntroducedInclusive: true,
            Fixed: "2.0.0",
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: ">=1.0.0 <2.0.0");

        var rule = primitive.ToNormalizedVersionRule();

        Assert.NotNull(rule);
        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("2.0.0", rule.Max);
        Assert.False(rule.MaxInclusive);
        Assert.Null(rule.Value);
        Assert.Equal(">=1.0.0 <2.0.0", rule.Notes);
    }

    [Fact]
    public void ToNormalizedVersionRule_ExactUsesExactValue()
    {
        var primitive = new SemVerPrimitive(
            Introduced: null,
            IntroducedInclusive: true,
            Fixed: null,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: null,
            ExactValue: "3.1.4");

        var rule = primitive.ToNormalizedVersionRule("from-ghsa");

        Assert.NotNull(rule);
        Assert.Equal(NormalizedVersionSchemes.SemVer, rule!.Scheme);
        Assert.Equal(NormalizedVersionRuleTypes.Exact, rule.Type);
        Assert.Null(rule.Min);
        Assert.Null(rule.Max);
        Assert.Equal("3.1.4", rule.Value);
        Assert.Equal("from-ghsa", rule.Notes);
    }

    [Fact]
    public void ToNormalizedVersionRule_GreaterThanMapsMinimum()
    {
        var primitive = new SemVerPrimitive(
            Introduced: "1.5.0",
            IntroducedInclusive: false,
            Fixed: null,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: null);

        var rule = primitive.ToNormalizedVersionRule();

        Assert.NotNull(rule);
        Assert.Equal(NormalizedVersionSchemes.SemVer, rule!.Scheme);
        Assert.Equal(NormalizedVersionRuleTypes.GreaterThan, rule.Type);
        Assert.Equal("1.5.0", rule.Min);
        Assert.False(rule.MinInclusive);
        Assert.Null(rule.Max);
        Assert.Null(rule.Value);
        Assert.Null(rule.Notes);
    }

    [Fact]
    public void ToNormalizedVersionRule_UsesConstraintExpressionAsFallbackNotes()
    {
        var primitive = new SemVerPrimitive(
            Introduced: "1.4.0",
            IntroducedInclusive: false,
            Fixed: null,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: "> 1.4.0");

        var rule = primitive.ToNormalizedVersionRule();

        Assert.NotNull(rule);
        Assert.Equal("> 1.4.0", rule!.Notes);
    }

    [Fact]
    public void ToNormalizedVersionRule_ExactCarriesConstraintExpressionWhenNotesMissing()
    {
        var primitive = new SemVerPrimitive(
            Introduced: null,
            IntroducedInclusive: true,
            Fixed: null,
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: "= 3.2.1",
            ExactValue: "3.2.1");

        var rule = primitive.ToNormalizedVersionRule();

        Assert.NotNull(rule);
        Assert.Equal(NormalizedVersionRuleTypes.Exact, rule!.Type);
        Assert.Equal("3.2.1", rule.Value);
        Assert.Equal("= 3.2.1", rule.Notes);
    }

    [Fact]
    public void ToNormalizedVersionRule_ExplicitNotesOverrideConstraintExpression()
    {
        var primitive = new SemVerPrimitive(
            Introduced: "1.0.0",
            IntroducedInclusive: true,
            Fixed: "1.1.0",
            FixedInclusive: false,
            LastAffected: null,
            LastAffectedInclusive: false,
            ConstraintExpression: ">=1.0.0 <1.1.0");

        var rule = primitive.ToNormalizedVersionRule("ghsa:range");

        Assert.NotNull(rule);
        Assert.Equal("ghsa:range", rule!.Notes);
    }
}
