using NATS.Server;
using NATS.Server.Auth;
using NATS.Server.Protocol;
using NATS.Server.Subscriptions;

namespace NATS.Server.Core.Tests.Subscriptions;

public class SubListParityBatch2Tests
{
    [Fact]
    public void RegisterQueueNotification_tracks_first_and_last_exact_queue_interest()
    {
        var subList = new SubList();
        var notifications = new List<bool>();
        Action<bool> callback = hasInterest => notifications.Add(hasInterest);

        subList.RegisterQueueNotification("foo.bar", "q", callback).ShouldBeTrue();
        notifications.ShouldBe([false]);

        var sub1 = new Subscription { Subject = "foo.bar", Queue = "q", Sid = "1" };
        var sub2 = new Subscription { Subject = "foo.bar", Queue = "q", Sid = "2" };

        subList.Insert(sub1);
        subList.Insert(sub2);
        notifications.ShouldBe([false, true]);

        subList.Remove(sub1);
        notifications.ShouldBe([false, true]);

        subList.Remove(sub2);
        notifications.ShouldBe([false, true, false]);

        subList.ClearQueueNotification("foo.bar", "q", callback).ShouldBeTrue();
    }

    [Fact]
    public void UpdateRemoteQSub_updates_queue_weight_for_match_remote()
    {
        var subList = new SubList();
        var original = new RemoteSubscription("foo.bar", "q", "R1", Account: "A", QueueWeight: 1);
        subList.ApplyRemoteSub(original);
        subList.MatchRemote("A", "foo.bar").Count.ShouldBe(1);

        subList.UpdateRemoteQSub(original with { QueueWeight = 3 });
        subList.MatchRemote("A", "foo.bar").Count.ShouldBe(3);
    }

    [Fact]
    public void SubListStats_Add_aggregates_stats_like_go()
    {
        var stats = new SubListStats
        {
            NumSubs = 1,
            NumCache = 2,
            NumInserts = 3,
            NumRemoves = 4,
            NumMatches = 10,
            MaxFanout = 5,
            TotalFanout = 8,
            CacheEntries = 2,
            CacheHits = 6,
        };

        stats.Add(new SubListStats
        {
            NumSubs = 2,
            NumCache = 3,
            NumInserts = 4,
            NumRemoves = 5,
            NumMatches = 30,
            MaxFanout = 9,
            TotalFanout = 12,
            CacheEntries = 3,
            CacheHits = 15,
        });

        stats.NumSubs.ShouldBe((uint)3);
        stats.NumCache.ShouldBe((uint)5);
        stats.NumInserts.ShouldBe((ulong)7);
        stats.NumRemoves.ShouldBe((ulong)9);
        stats.NumMatches.ShouldBe((ulong)40);
        stats.MaxFanout.ShouldBe((uint)9);
        stats.AvgFanout.ShouldBe(4.0);
        stats.CacheHitRate.ShouldBe(0.525);
    }

    [Fact]
    public void NumLevels_returns_max_trie_depth()
    {
        var subList = new SubList();
        subList.Insert(new Subscription { Subject = "foo.bar.baz", Sid = "1" });
        subList.Insert(new Subscription { Subject = "foo.bar", Sid = "2" });

        subList.NumLevels().ShouldBe(3);
    }

    [Fact]
    public void LocalSubs_filters_non_local_kinds_and_optionally_includes_leaf()
    {
        var subList = new SubList();
        subList.Insert(new Subscription { Subject = "foo.a", Sid = "1", Client = new TestClient(ClientKind.Client) });
        subList.Insert(new Subscription { Subject = "foo.b", Sid = "2", Client = new TestClient(ClientKind.Router) });
        subList.Insert(new Subscription { Subject = "foo.c", Sid = "3", Client = new TestClient(ClientKind.System) });
        subList.Insert(new Subscription { Subject = "foo.d", Sid = "4", Client = new TestClient(ClientKind.Leaf) });

        var local = subList.LocalSubs(includeLeafHubs: false).Select(s => s.Sid).OrderBy(x => x).ToArray();
        local.ShouldBe(["1", "3"]);

        var withLeaf = subList.LocalSubs(includeLeafHubs: true).Select(s => s.Sid).OrderBy(x => x).ToArray();
        withLeaf.ShouldBe(["1", "3", "4"]);
    }

    private sealed class TestClient(ClientKind kind) : INatsClient
    {
        public ulong Id => 1;
        public ClientKind Kind => kind;
        public Account? Account => null;
        public ClientOptions? ClientOpts => null;
        public ClientPermissions? Permissions => null;
        public void SendMessage(string subject, string sid, string? replyTo, ReadOnlyMemory<byte> headers, ReadOnlyMemory<byte> payload)
        {
        }

        public void SendMessageNoFlush(string subject, string sid, string? replyTo, ReadOnlyMemory<byte> headers, ReadOnlyMemory<byte> payload)
        {
        }

        public void SignalFlush() { }

        public bool QueueOutbound(ReadOnlyMemory<byte> data) => true;

        public void RemoveSubscription(string sid)
        {
        }
    }
}
