using NATS.Server.Subscriptions;

namespace NATS.Server.Tests;

public class SubjectTransformTests
{
    [Fact]
    public void WildcardReplacement_SingleToken()
    {
        // foo.* -> bar.{{wildcard(1)}}
        var transform = SubjectTransform.Create("foo.*", "bar.{{wildcard(1)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo.baz").ShouldBe("bar.baz");
    }

    [Fact]
    public void DollarSyntax_ReversesOrder()
    {
        // foo.*.* -> bar.$2.$1 reverses captured tokens
        var transform = SubjectTransform.Create("foo.*.*", "bar.$2.$1");
        transform.ShouldNotBeNull();
        transform.Apply("foo.A.B").ShouldBe("bar.B.A");
    }

    [Fact]
    public void DollarSyntax_MultipleWildcardPositions()
    {
        // foo.*.bar.*.baz -> req.$2.$1
        var transform = SubjectTransform.Create("foo.*.bar.*.baz", "req.$2.$1");
        transform.ShouldNotBeNull();
        transform.Apply("foo.A.bar.B.baz").ShouldBe("req.B.A");
    }

    [Fact]
    public void WildcardFunction_MultiplePositions()
    {
        // foo.*.bar.*.baz -> req.{{wildcard(2)}}.{{wildcard(1)}}
        var transform = SubjectTransform.Create("foo.*.bar.*.baz", "req.{{wildcard(2)}}.{{wildcard(1)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo.A.bar.B.baz").ShouldBe("req.B.A");
    }

    [Fact]
    public void FullWildcardCapture_MultiToken()
    {
        // baz.> -> my.pre.> captures multi-token remainder
        var transform = SubjectTransform.Create("baz.>", "my.pre.>");
        transform.ShouldNotBeNull();
        transform.Apply("baz.1.2.3").ShouldBe("my.pre.1.2.3");
    }

    [Fact]
    public void FullWildcardCapture_FooBar()
    {
        // baz.> -> foo.bar.>
        var transform = SubjectTransform.Create("baz.>", "foo.bar.>");
        transform.ShouldNotBeNull();
        transform.Apply("baz.1.2.3").ShouldBe("foo.bar.1.2.3");
    }

    [Fact]
    public void NoMatch_ReturnsNull()
    {
        var transform = SubjectTransform.Create("foo.*", "bar.$1");
        transform.ShouldNotBeNull();
        transform.Apply("baz.qux").ShouldBeNull();
    }

    [Fact]
    public void NoMatch_WrongTokenCount()
    {
        var transform = SubjectTransform.Create("foo.*", "bar.$1");
        transform.ShouldNotBeNull();
        transform.Apply("foo.a.b").ShouldBeNull();
    }

    [Fact]
    public void PartitionFunction_DeterministicResult()
    {
        // Partition should produce deterministic 0..N-1 results
        var transform = SubjectTransform.Create("*", "bar.{{partition(10)}}");
        transform.ShouldNotBeNull();

        // FNV-1a of "foo" mod 10 = 3
        transform.Apply("foo").ShouldBe("bar.3");
        // FNV-1a of "baz" mod 10 = 0
        transform.Apply("baz").ShouldBe("bar.0");
        // FNV-1a of "qux" mod 10 = 9
        transform.Apply("qux").ShouldBe("bar.9");
    }

    [Fact]
    public void PartitionFunction_ZeroBuckets()
    {
        var transform = SubjectTransform.Create("*", "bar.{{partition(0)}}");
        transform.ShouldNotBeNull();
        transform.Apply("baz").ShouldBe("bar.0");
    }

    [Fact]
    public void PartitionFunction_WithTokenIndexes()
    {
        // partition(10, 1, 2) hashes concatenation of wildcard 1 and wildcard 2
        // For source *.*: wildcard 1 -> pos 0 ("foo"), wildcard 2 -> pos 1 ("bar")
        // Key = "foobar" (no separator), FNV-1a("foobar") % 10 = 0
        var transform = SubjectTransform.Create("*.*", "bar.{{partition(10,1,2)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo.bar").ShouldBe("bar.0");
    }

    [Fact]
    public void PartitionFunction_WithSpecificToken()
    {
        // partition(10, 0) with wildcard source: in Go, wildcard index 0 silently
        // maps to source position 0 (Go map zero-value behavior). We match this.
        var transform = SubjectTransform.Create("*", "bar.{{partition(10, 0)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo").ShouldBe("bar.3");
    }

    [Fact]
    public void PartitionFunction_ShorthandNoWildcardsInSource()
    {
        // When source has no wildcards, partition(n) hashes the full subject
        var transform = SubjectTransform.Create("foo.bar", "baz.{{partition(10)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo.bar").ShouldBe("baz.6");
    }

    [Fact]
    public void PartitionFunction_ShorthandWithWildcards()
    {
        // partition(10) with wildcards hashes all subject tokens joined
        var transform = SubjectTransform.Create("*.*", "bar.{{partition(10)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo.bar").ShouldBe("bar.6");
    }

    [Fact]
    public void SplitFunction_BasicDelimiter()
    {
        // events.a-b-c with split(1,-) -> split.a.b.c
        var transform = SubjectTransform.Create("*", "{{split(1,-)}}");
        transform.ShouldNotBeNull();
        transform.Apply("abc-def--ghi-").ShouldBe("abc.def.ghi");
    }

    [Fact]
    public void SplitFunction_LeadingDelimiter()
    {
        var transform = SubjectTransform.Create("*", "{{split(1,-)}}");
        transform.ShouldNotBeNull();
        transform.Apply("-abc-def--ghi-").ShouldBe("abc.def.ghi");
    }

    [Fact]
    public void LeftFunction_BasicTrim()
    {
        // data.abcdef with left(1,3) -> prefix.abc
        var transform = SubjectTransform.Create("*", "prefix.{{left(1,3)}}");
        transform.ShouldNotBeNull();
        transform.Apply("abcdef").ShouldBe("prefix.abc");
    }

    [Fact]
    public void LeftFunction_LenExceedsToken()
    {
        var transform = SubjectTransform.Create("*", "{{left(1,6)}}");
        transform.ShouldNotBeNull();
        // When len exceeds token length, return full token
        transform.Apply("1234").ShouldBe("1234");
    }

    [Fact]
    public void LeftFunction_SingleChar()
    {
        var transform = SubjectTransform.Create("*", "{{left(1,1)}}");
        transform.ShouldNotBeNull();
        transform.Apply("1234").ShouldBe("1");
    }

    [Fact]
    public void RightFunction_BasicTrim()
    {
        // data.abcdef with right(1,3) -> suffix.def
        var transform = SubjectTransform.Create("*", "suffix.{{right(1,3)}}");
        transform.ShouldNotBeNull();
        transform.Apply("abcdef").ShouldBe("suffix.def");
    }

    [Fact]
    public void RightFunction_LenExceedsToken()
    {
        var transform = SubjectTransform.Create("*", "{{right(1,6)}}");
        transform.ShouldNotBeNull();
        transform.Apply("1234").ShouldBe("1234");
    }

    [Fact]
    public void RightFunction_SingleChar()
    {
        var transform = SubjectTransform.Create("*", "{{right(1,1)}}");
        transform.ShouldNotBeNull();
        transform.Apply("1234").ShouldBe("4");
    }

    [Fact]
    public void RightFunction_ThreeChars()
    {
        var transform = SubjectTransform.Create("*", "{{right(1,3)}}");
        transform.ShouldNotBeNull();
        transform.Apply("1234").ShouldBe("234");
    }

    [Fact]
    public void SplitFromLeft_BasicSplit()
    {
        // data.abcdef with splitFromLeft(1,3) -> parts.abc.def
        var transform = SubjectTransform.Create("*", "{{splitFromLeft(1,3)}}");
        transform.ShouldNotBeNull();
        transform.Apply("12345").ShouldBe("123.45");
    }

    [Fact]
    public void SplitFromRight_BasicSplit()
    {
        // data.abcdef with splitFromRight(1,3) -> parts.abc.def
        var transform = SubjectTransform.Create("*", "{{SplitFromRight(1,3)}}");
        transform.ShouldNotBeNull();
        transform.Apply("12345").ShouldBe("12.345");
    }

    [Fact]
    public void SliceFromLeft_BasicSlice()
    {
        // data.abcdef with sliceFromLeft(1,2) -> chunks.ab.cd.ef
        var transform = SubjectTransform.Create("*", "{{SliceFromLeft(1,3)}}");
        transform.ShouldNotBeNull();
        transform.Apply("1234567890").ShouldBe("123.456.789.0");
    }

    [Fact]
    public void SliceFromRight_BasicSlice()
    {
        // data.abcdef with sliceFromRight(1,2) -> chunks.ab.cd.ef
        var transform = SubjectTransform.Create("*", "{{SliceFromRight(1,3)}}");
        transform.ShouldNotBeNull();
        transform.Apply("1234567890").ShouldBe("1.234.567.890");
    }

    [Fact]
    public void LiteralPassthrough_NoWildcards()
    {
        // Literal source with no wildcards: exact match, returns dest
        var transform = SubjectTransform.Create("foo", "bar");
        transform.ShouldNotBeNull();
        transform.Apply("foo").ShouldBe("bar");
    }

    [Fact]
    public void LiteralPassthrough_NoMatchOnDifferentSubject()
    {
        var transform = SubjectTransform.Create("foo", "bar");
        transform.ShouldNotBeNull();
        transform.Apply("baz").ShouldBeNull();
    }

    [Fact]
    public void InvalidSource_ReturnsNull()
    {
        // foo.. is not a valid subject
        SubjectTransform.Create("foo..", "bar").ShouldBeNull();
    }

    [Fact]
    public void InvalidSource_EmptyToken()
    {
        SubjectTransform.Create(".foo", "bar").ShouldBeNull();
    }

    [Fact]
    public void WildcardIndexOutOfRange_ReturnsNull()
    {
        // Source has 1 wildcard but dest references $2
        SubjectTransform.Create("foo.*", "bar.$2").ShouldBeNull();
    }

    [Fact]
    public void DestinationWithWildcard_ReturnsNull()
    {
        // Wildcards not allowed in destination (pwc)
        SubjectTransform.Create("foo.*", "bar.*").ShouldBeNull();
    }

    [Fact]
    public void FwcMismatch_ReturnsNull()
    {
        // If source has >, dest must also have >
        SubjectTransform.Create("foo.*", "bar.$1.>").ShouldBeNull();
        SubjectTransform.Create("foo.>", "bar.baz").ShouldBeNull();
    }

    [Fact]
    public void UnknownFunction_ReturnsNull()
    {
        SubjectTransform.Create("foo.*", "foo.{{unimplemented(1)}}").ShouldBeNull();
    }

    [Fact]
    public void SingleWildcardCapture_ExpandedToBarPrefix()
    {
        var transform = SubjectTransform.Create("*", "foo.bar.$1");
        transform.ShouldNotBeNull();
        transform.Apply("foo").ShouldBe("foo.bar.foo");
    }

    [Fact]
    public void ComboTransform_SplitAndSplitFromLeft()
    {
        // Combo: split + splitFromLeft
        var transform = SubjectTransform.Create("*.*", "{{split(2,-)}}.{{splitfromleft(1,2)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo.-abc-def--ghij-").ShouldBe("abc.def.ghij.fo.o");
    }

    [Fact]
    public void PartitionFunction_NoWildcardSource_FullSubjectHash()
    {
        // foo.baz -> qux.{{partition(10)}}
        var transform = SubjectTransform.Create("foo.baz", "qux.{{partition(10)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo.baz").ShouldBe("qux.4");
    }

    [Fact]
    public void PartitionFunction_NoWildcardSource_TestSubject()
    {
        var transform = SubjectTransform.Create("test.subject", "result.{{partition(5)}}");
        transform.ShouldNotBeNull();
        transform.Apply("test.subject").ShouldBe("result.0");
    }

    [Fact]
    public void WildcardFunction_CaseInsensitive()
    {
        // Function names are case-insensitive (e.g. Wildcard, wildcard, WILDCARD)
        var transform = SubjectTransform.Create("foo.*", "bar.{{Wildcard(1)}}");
        transform.ShouldNotBeNull();
        transform.Apply("foo.test").ShouldBe("bar.test");
    }

    [Fact]
    public void SplitFromLeft_CaseInsensitive()
    {
        var transform = SubjectTransform.Create("*", "{{splitfromleft(1,1)}}");
        transform.ShouldNotBeNull();
        // Single char split from left pos 1: "ab" -> "a.b"
    }

    [Fact]
    public void NotEnoughTokensInDest_PartitionWithMissingArgs()
    {
        SubjectTransform.Create("foo.*", "foo.{{partition()}}").ShouldBeNull();
    }

    [Fact]
    public void WildcardFunctionBadArg_ReturnsNull()
    {
        SubjectTransform.Create("foo.*", "foo.{{wildcard(foo)}}").ShouldBeNull();
    }

    [Fact]
    public void WildcardFunctionNoArgs_ReturnsNull()
    {
        SubjectTransform.Create("foo.*", "foo.{{wildcard()}}").ShouldBeNull();
    }

    [Fact]
    public void WildcardFunctionTooManyArgs_ReturnsNull()
    {
        SubjectTransform.Create("foo.*", "foo.{{wildcard(1,2)}}").ShouldBeNull();
    }

    [Fact]
    public void BadMustacheFormat_ReturnsNull()
    {
        SubjectTransform.Create("foo.*", "foo.{{ wildcard5) }}").ShouldBeNull();
    }

    [Fact]
    public void NoWildcardSource_TransformFunctionNotAllowed()
    {
        // When source has no wildcards, only partition and random functions are allowed
        SubjectTransform.Create("foo", "bla.{{wildcard(1)}}").ShouldBeNull();
    }
}
