using NATS.Client.Core;
using NATS.Server.TestUtilities;

namespace NATS.Server.LeafNodes.Tests.LeafNodes;

/// <summary>
/// Tests for subject filter propagation through leaf nodes.
/// Reference: golang/nats-server/server/leafnode_test.go
/// </summary>
public class LeafNodeSubjectFilterTests
{
    // Go: TestLeafNodeInterestPropagationDaisychain server/leafnode_test.go:3953
    [Fact]
    public async Task Wildcard_subscription_propagates_through_leaf_node()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();
        await using var hubConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Hub.Port}" });
        await hubConn.ConnectAsync();

        await using var sub = await leafConn.SubscribeCoreAsync<string>("wild.*");
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync("wild.test");

        await hubConn.PublishAsync("wild.test", "wildcard-match");

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        (await sub.Msgs.ReadAsync(cts.Token)).Data.ShouldBe("wildcard-match");
    }

    // Go: TestLeafNodeInterestPropagationDaisychain server/leafnode_test.go:3953
    [Fact]
    public async Task Full_wildcard_subscription_propagates_through_leaf_node()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();
        await using var hubConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Hub.Port}" });
        await hubConn.ConnectAsync();

        await using var sub = await leafConn.SubscribeCoreAsync<string>("fwc.>");
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync("fwc.a.b.c");

        await hubConn.PublishAsync("fwc.a.b.c", "full-wc");

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        (await sub.Msgs.ReadAsync(cts.Token)).Data.ShouldBe("full-wc");
    }

    // Go: TestLeafNodeStreamAndShadowSubs server/leafnode_test.go:6176
    [Fact]
    public async Task Catch_all_subscription_propagates_through_leaf_node()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();
        await using var hubConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Hub.Port}" });
        await hubConn.ConnectAsync();

        await using var sub = await leafConn.SubscribeCoreAsync<string>(">");
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync("anything.at.all");

        await hubConn.PublishAsync("anything.at.all", "catch-all");

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        (await sub.Msgs.ReadAsync(cts.Token)).Data.ShouldBe("catch-all");
    }

    // Go: TestLeafNodePermissions server/leafnode_test.go:1267
    [Fact]
    public async Task Subscription_interest_propagates_from_hub_to_leaf()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var hubConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Hub.Port}" });
        await hubConn.ConnectAsync();

        await using var sub = await hubConn.SubscribeCoreAsync<string>("interest.prop");
        await hubConn.PingAsync();
        await fixture.WaitForRemoteInterestOnSpokeAsync("interest.prop");

        fixture.Spoke.HasRemoteInterest("interest.prop").ShouldBeTrue();
    }

    // Go: TestLeafNodePermissions server/leafnode_test.go:1267
    [Fact]
    public async Task Unsubscribe_removes_interest_on_remote()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();

        var sub = await leafConn.SubscribeCoreAsync<string>("unsub.test");
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync("unsub.test");
        fixture.Hub.HasRemoteInterest("unsub.test").ShouldBeTrue();

        await sub.DisposeAsync();
        await leafConn.PingAsync();

        await PollHelper.WaitUntilAsync(() => !(fixture.Hub.HasRemoteInterest("unsub.test")), timeoutMs: 5000);

        fixture.Hub.HasRemoteInterest("unsub.test").ShouldBeFalse();
    }

    // Go: TestLeafNodeInterestPropagationDaisychain server/leafnode_test.go:3953
    [Fact]
    public async Task Multiple_subscriptions_on_different_subjects_all_propagate()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();

        await using var sub1 = await leafConn.SubscribeCoreAsync<string>("multi.a");
        await using var sub2 = await leafConn.SubscribeCoreAsync<string>("multi.b");
        await using var sub3 = await leafConn.SubscribeCoreAsync<string>("multi.c");
        await leafConn.PingAsync();

        await fixture.WaitForRemoteInterestOnHubAsync("multi.a");
        await fixture.WaitForRemoteInterestOnHubAsync("multi.b");
        await fixture.WaitForRemoteInterestOnHubAsync("multi.c");

        fixture.Hub.HasRemoteInterest("multi.a").ShouldBeTrue();
        fixture.Hub.HasRemoteInterest("multi.b").ShouldBeTrue();
        fixture.Hub.HasRemoteInterest("multi.c").ShouldBeTrue();
    }

    // Go: TestLeafNodeDuplicateMsg server/leafnode_test.go:6513
    [Fact]
    public async Task No_interest_for_unsubscribed_subject()
    {
        await using var fixture = await LeafFixture.StartAsync();
        fixture.Hub.HasRemoteInterest("nonexistent.subject").ShouldBeFalse();
    }

    // Go: TestLeafNodeInterestPropagationDaisychain server/leafnode_test.go:3953
    [Fact]
    public async Task Wildcard_interest_matches_multiple_concrete_subjects()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();
        await using var hubConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Hub.Port}" });
        await hubConn.ConnectAsync();

        await using var sub = await leafConn.SubscribeCoreAsync<string>("events.*");
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync("events.created");

        await hubConn.PublishAsync("events.created", "ev1");
        await hubConn.PublishAsync("events.updated", "ev2");
        await hubConn.PublishAsync("events.deleted", "ev3");

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var received = new List<string>();
        for (var i = 0; i < 3; i++)
            received.Add((await sub.Msgs.ReadAsync(cts.Token)).Data!);

        received.ShouldContain("ev1");
        received.ShouldContain("ev2");
        received.ShouldContain("ev3");
    }

    // Go: TestLeafNodePermissions server/leafnode_test.go:1267
    [Fact]
    public async Task Non_matching_wildcard_does_not_receive_message()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();
        await using var hubConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Hub.Port}" });
        await hubConn.ConnectAsync();

        await using var sub = await leafConn.SubscribeCoreAsync<string>("orders.*");
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync("orders.test");

        await hubConn.PublishAsync("users.test", "should-not-arrive");

        using var leakCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
        await Should.ThrowAsync<OperationCanceledException>(async () =>
            await sub.Msgs.ReadAsync(leakCts.Token));
    }

    // Go: TestLeafNodeQueueGroupDistribution server/leafnode_test.go:4021
    [Fact]
    public async Task Queue_subscription_interest_propagates_through_leaf_node()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();
        await using var hubConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Hub.Port}" });
        await hubConn.ConnectAsync();

        await using var sub = await leafConn.SubscribeCoreAsync<string>("queue.test", queueGroup: "workers");
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync("queue.test");

        await hubConn.PublishAsync("queue.test", "queued-msg");

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        (await sub.Msgs.ReadAsync(cts.Token)).Data.ShouldBe("queued-msg");
    }

    // Go: TestLeafNodeIsolatedLeafSubjectPropagationGlobal server/leafnode_test.go:10280
    [Fact]
    public async Task Interest_on_hub_side_includes_remote_interest_from_leaf()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();

        await using var sub = await leafConn.SubscribeCoreAsync<string>("remote.interest.check");
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync("remote.interest.check");

        fixture.Hub.HasRemoteInterest("remote.interest.check").ShouldBeTrue();
        fixture.Hub.HasRemoteInterest("some.other.subject").ShouldBeFalse();
    }

    // Go: TestLeafNodeInterestPropagationDaisychain server/leafnode_test.go:3953
    [Fact]
    public async Task Deep_subject_hierarchy_forwarded_correctly()
    {
        await using var fixture = await LeafFixture.StartAsync();

        await using var leafConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Spoke.Port}" });
        await leafConn.ConnectAsync();
        await using var hubConn = new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{fixture.Hub.Port}" });
        await hubConn.ConnectAsync();

        const string deepSubject = "a.b.c.d.e.f.g.h";
        await using var sub = await leafConn.SubscribeCoreAsync<string>(deepSubject);
        await leafConn.PingAsync();
        await fixture.WaitForRemoteInterestOnHubAsync(deepSubject);

        await hubConn.PublishAsync(deepSubject, "deep");

        using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
        (await sub.Msgs.ReadAsync(cts.Token)).Data.ShouldBe("deep");
    }
}
