using NATS.Server.LeafNodes;

namespace NATS.Server.LeafNodes.Tests.LeafNodes;

/// <summary>
/// Tests for leaf node loop detection via $LDS. prefix.
/// Reference: golang/nats-server/server/leafnode_test.go
/// </summary>
public class LeafNodeLoopDetectionTests
{
    // Go: TestLeafNodeLoop server/leafnode_test.go:837
    [Fact]
    public void HasLoopMarker_returns_true_for_marked_subject()
    {
        var marked = LeafLoopDetector.Mark("orders.created", "SERVER1");
        LeafLoopDetector.HasLoopMarker(marked).ShouldBeTrue();
    }

    [Fact]
    public void HasLoopMarker_returns_false_for_plain_subject()
    {
        LeafLoopDetector.HasLoopMarker("orders.created").ShouldBeFalse();
    }

    [Fact]
    public void Mark_prepends_LDS_prefix_with_server_id()
    {
        LeafLoopDetector.Mark("foo.bar", "ABC123").ShouldBe("$LDS.ABC123.foo.bar");
    }

    [Fact]
    public void IsLooped_returns_true_when_subject_contains_own_server_id()
    {
        var marked = LeafLoopDetector.Mark("foo.bar", "MYSERVER");
        LeafLoopDetector.IsLooped(marked, "MYSERVER").ShouldBeTrue();
    }

    [Fact]
    public void IsLooped_returns_false_when_subject_contains_different_server_id()
    {
        var marked = LeafLoopDetector.Mark("foo.bar", "OTHER");
        LeafLoopDetector.IsLooped(marked, "MYSERVER").ShouldBeFalse();
    }

    // Go: TestLeafNodeLoopDetectionOnActualLoop server/leafnode_test.go:9410
    [Fact]
    public void TryUnmark_extracts_original_subject_from_single_mark()
    {
        var marked = LeafLoopDetector.Mark("orders.created", "S1");
        LeafLoopDetector.TryUnmark(marked, out var unmarked).ShouldBeTrue();
        unmarked.ShouldBe("orders.created");
    }

    [Fact]
    public void TryUnmark_extracts_original_subject_from_nested_marks()
    {
        var nested = LeafLoopDetector.Mark(LeafLoopDetector.Mark("data.stream", "S1"), "S2");
        LeafLoopDetector.TryUnmark(nested, out var unmarked).ShouldBeTrue();
        unmarked.ShouldBe("data.stream");
    }

    [Fact]
    public void TryUnmark_extracts_original_from_triple_nested_marks()
    {
        var tripleNested = LeafLoopDetector.Mark(
            LeafLoopDetector.Mark(LeafLoopDetector.Mark("test.subject", "S1"), "S2"), "S3");
        LeafLoopDetector.TryUnmark(tripleNested, out var unmarked).ShouldBeTrue();
        unmarked.ShouldBe("test.subject");
    }

    [Fact]
    public void TryUnmark_returns_false_for_unmarked_subject()
    {
        LeafLoopDetector.TryUnmark("orders.created", out var unmarked).ShouldBeFalse();
        unmarked.ShouldBe("orders.created");
    }

    [Fact]
    public void Mark_preserves_dot_separated_structure()
    {
        var marked = LeafLoopDetector.Mark("a.b.c.d", "SRV");
        marked.ShouldStartWith("$LDS.SRV.");
        marked.ShouldEndWith("a.b.c.d");
    }

    // Go: TestLeafNodeLoopDetectionWithMultipleClusters server/leafnode_test.go:3546
    [Fact]
    public void IsLooped_detects_loop_in_nested_marks()
    {
        var marked = LeafLoopDetector.Mark(LeafLoopDetector.Mark("test", "REMOTE"), "LOCAL");
        LeafLoopDetector.IsLooped(marked, "LOCAL").ShouldBeTrue();
        LeafLoopDetector.IsLooped(marked, "REMOTE").ShouldBeFalse();
    }

    [Fact]
    public void HasLoopMarker_works_with_prefix_only()
    {
        LeafLoopDetector.HasLoopMarker("$LDS.").ShouldBeTrue();
    }

    [Fact]
    public void IsLooped_returns_false_for_plain_subject()
    {
        LeafLoopDetector.IsLooped("plain.subject", "MYSERVER").ShouldBeFalse();
    }

    [Fact]
    public void Mark_with_single_token_subject()
    {
        var marked = LeafLoopDetector.Mark("simple", "S1");
        marked.ShouldBe("$LDS.S1.simple");
        LeafLoopDetector.TryUnmark(marked, out var unmarked).ShouldBeTrue();
        unmarked.ShouldBe("simple");
    }

    // Go: TestLeafNodeLoopFromDAG server/leafnode_test.go:899
    [Fact]
    public void Multiple_servers_in_chain_each_add_their_mark()
    {
        var original = "data.stream";
        var fromS1 = LeafLoopDetector.Mark(original, "S1");
        fromS1.ShouldBe("$LDS.S1.data.stream");

        var fromS2 = LeafLoopDetector.Mark(fromS1, "S2");
        fromS2.ShouldBe("$LDS.S2.$LDS.S1.data.stream");

        LeafLoopDetector.IsLooped(fromS2, "S2").ShouldBeTrue();
        LeafLoopDetector.IsLooped(fromS2, "S1").ShouldBeFalse();

        LeafLoopDetector.TryUnmark(fromS2, out var unmarked).ShouldBeTrue();
        unmarked.ShouldBe("data.stream");
    }

    [Fact]
    public void Roundtrip_mark_unmark_preserves_original()
    {
        var subjects = new[] { "foo", "foo.bar", "foo.bar.baz", "a.b.c.d.e", "single", "with.*.wildcard", "with.>" };

        foreach (var subject in subjects)
        {
            var marked = LeafLoopDetector.Mark(subject, "TESTSRV");
            LeafLoopDetector.TryUnmark(marked, out var unmarked).ShouldBeTrue();
            unmarked.ShouldBe(subject, $"Failed roundtrip for: {subject}");
        }
    }

    [Fact]
    public void Four_server_chain_marks_and_unmarks_correctly()
    {
        var step1 = LeafLoopDetector.Mark("test", "A");
        var step2 = LeafLoopDetector.Mark(step1, "B");
        var step3 = LeafLoopDetector.Mark(step2, "C");
        var step4 = LeafLoopDetector.Mark(step3, "D");

        LeafLoopDetector.IsLooped(step4, "D").ShouldBeTrue();
        LeafLoopDetector.IsLooped(step4, "C").ShouldBeFalse();
        LeafLoopDetector.IsLooped(step4, "B").ShouldBeFalse();
        LeafLoopDetector.IsLooped(step4, "A").ShouldBeFalse();

        LeafLoopDetector.TryUnmark(step4, out var unmarked).ShouldBeTrue();
        unmarked.ShouldBe("test");
    }

    [Fact]
    public void HasLoopMarker_is_case_sensitive()
    {
        LeafLoopDetector.HasLoopMarker("$LDS.SRV.foo").ShouldBeTrue();
        LeafLoopDetector.HasLoopMarker("$lds.SRV.foo").ShouldBeFalse();
    }

    // Go: TestLeafNodeLoopDetectedOnAcceptSide server/leafnode_test.go:1522
    [Fact]
    public void IsLooped_is_case_sensitive_for_server_id()
    {
        var marked = LeafLoopDetector.Mark("foo", "MYSERVER");
        LeafLoopDetector.IsLooped(marked, "MYSERVER").ShouldBeTrue();
        LeafLoopDetector.IsLooped(marked, "myserver").ShouldBeFalse();
    }
}
