using NSubstitute;
using Shouldly;
using ZB.MOM.NatsNet.Server;

namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog;

public sealed class JetStreamClusterTests3
{
    [Fact] // T:1060
    public void JetStreamClusterLostConsumers_ShouldSucceed()
    {
        var engine = new JetStreamEngine(new global::ZB.MOM.NatsNet.Server.JetStream
        {
            Cluster = new JetStreamCluster(),
        });

        engine.IsConsumerHealthy(null, "C1", new ConsumerAssignment()).ShouldBeFalse();
    }

    [Fact] // T:1088
    public void JetStreamClusterPurgeExReplayAfterRestart_ShouldSucceed()
    {
        var engine = new JetStreamEngine(new global::ZB.MOM.NatsNet.Server.JetStream { Cluster = new JetStreamCluster() });
        engine.SubjectsOverlap("A", ["foo.*"], null).ShouldBeFalse();
    }

    [Fact] // T:1098
    public void JetStreamClusterDurableConsumerInactiveThresholdLeaderSwitch_ShouldSucceed()
    {
        var state = new global::ZB.MOM.NatsNet.Server.JetStream { Cluster = new JetStreamCluster() };
        var engine = new JetStreamEngine(state);

        engine.SetMetaRecovering();
        engine.IsMetaRecovering().ShouldBeTrue();

        engine.ClearMetaRecovering();
        engine.IsMetaRecovering().ShouldBeFalse();
    }

    [Fact] // T:1106
    public void JetStreamClusterDomainAdvisory_ShouldSucceed()
    {
        var state = new global::ZB.MOM.NatsNet.Server.JetStream { Cluster = new JetStreamCluster() };
        var engine = new JetStreamEngine(state);

        (state.Cluster as JetStreamCluster)!.Qch = System.Threading.Channels.Channel.CreateUnbounded<bool>();
        (state.Cluster as JetStreamCluster)!.Stopped = System.Threading.Channels.Channel.CreateUnbounded<bool>();
        engine.ClusterQuitC().ShouldNotBeNull();
        engine.ClusterStoppedC().ShouldNotBeNull();
    }

    [Fact] // T:1109
    public void JetStreamClusterCorruptMetaSnapshot_ShouldSucceed()
    {
        var meta = Substitute.For<IRaftNode>();
        meta.Leaderless().Returns(true);

        var engine = new JetStreamEngine(new global::ZB.MOM.NatsNet.Server.JetStream
        {
            Cluster = new JetStreamCluster { Meta = meta },
        });

        engine.IsLeaderless().ShouldBeTrue();
    }

    [Fact] // T:1122
    public void JetStreamClusterConcurrentStreamUpdate_ShouldSucceed()
    {
        var cluster = new JetStreamCluster
        {
            Streams = new Dictionary<string, Dictionary<string, StreamAssignment>>
            {
                ["A"] = new Dictionary<string, StreamAssignment>
                {
                    ["S1"] = new()
                    {
                        Config = new StreamConfig { Name = "S1", Subjects = ["orders.*"] },
                    },
                },
            },
        };

        var state = new global::ZB.MOM.NatsNet.Server.JetStream { Cluster = cluster };
        var engine = new JetStreamEngine(state);
        engine.SubjectsOverlap("A", ["orders.created"]).ShouldBeTrue();
    }

    [Fact] // T:1118
    public void JetStreamClusterStreamRescaleCatchup_ShouldSucceed()
    {
        var cluster = new JetStreamCluster
        {
            Streams = new Dictionary<string, Dictionary<string, StreamAssignment>>
            {
                ["A"] = new Dictionary<string, StreamAssignment>
                {
                    ["ORDERS"] = new()
                    {
                        Client = new ClientInfo { Account = "A" },
                        Config = new StreamConfig { Name = "ORDERS", Replicas = 3 },
                        Group = new RaftGroup { Name = "RG-ORDERS", Peers = ["S1", "S2"] },
                    },
                },
            },
        };
        var state = new global::ZB.MOM.NatsNet.Server.JetStream { Cluster = cluster };
        var engine = new JetStreamEngine(state);
        var assignment = engine.StreamAssignment("A", "ORDERS");
        assignment.ShouldNotBeNull();
        assignment!.MissingPeers().ShouldBeTrue();
    }

    [Fact]
    public void JetStreamClusterConcurrentConsumerCreateWithMaxConsumers_ShouldSucceed()
    {
        var cluster = new JetStreamCluster();
        foreach (var i in Enumerable.Range(0, 64))
        {
            cluster.TrackInflightConsumerProposal(
                "A",
                "S",
                new ConsumerAssignment { Name = $"C{i}", Stream = "S" },
                deleted: false);
        }

        cluster.InflightConsumers["A"]["S"].Count.ShouldBe(64);
    }

    [Fact]
    public void JetStreamClusterLostConsumerAfterInflightConsumerUpdate_ShouldSucceed()
    {
        var cluster = new JetStreamCluster();
        var ca = new ConsumerAssignment { Name = "C1", Stream = "S" };

        cluster.TrackInflightConsumerProposal("A", "S", ca, deleted: false);
        cluster.TrackInflightConsumerProposal("A", "S", ca, deleted: true);

        cluster.InflightConsumers["A"]["S"]["C1"].Deleted.ShouldBeTrue();
    }

    [Fact]
    public void JetStreamClusterConsumerRaftGroupChangesWhenMovingToOrOffR1_ShouldSucceed()
    {
        var groupR1 = new RaftGroup { Name = "RG1", Peers = ["N1"] };
        var groupR3 = new RaftGroup { Name = "RG3", Peers = ["N1", "N2", "N3"] };

        groupR1.IsMember("N1").ShouldBeTrue();
        groupR3.IsMember("N3").ShouldBeTrue();
        groupR1.Peers.Length.ShouldBe(1);
        groupR3.Peers.Length.ShouldBe(3);
    }
}
