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

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

public sealed class Batch37StreamMessagesMappedTests
{
    [Fact] // T:828
    public void JetStreamClusterStreamDirectGetMsg_ShouldSucceed()
    {
        var stream = CreateStream();
        stream.SetupStore(null).ShouldBeNull();
        stream.Store!.StoreMsg("orders.created", null, [1], 0);
        var request = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(new JsApiMsgGetRequest { Seq = 1 });

        var response = stream.ProcessDirectGetRequest("reply", null, request);

        response.Error.ShouldBeNull();
        response.Message.ShouldNotBeNull();
        response.Message!.Sequence.ShouldBe(1UL);
    }

    [Fact] // T:954
    public void JetStreamClusterRollupSubjectAndWatchers_ShouldSucceed()
    {
        var hdr = NatsMessageHeaders.GenHeader(null, NatsHeaderConstants.JsMsgRollup, "SUB");

        NatsStream.GetRollup(hdr).ShouldBe("sub");
    }

    [Fact] // T:987
    public void JetStreamClusterMirrorDeDupWindow_ShouldSucceed()
    {
        var stream = CreateStream(duplicates: TimeSpan.FromSeconds(5));
        var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
        stream.StoreMsgId(new NatsStream.DedupeEntry { Id = "dup-1", Seq = 10, TimestampNanos = now });

        stream.CheckMsgId("dup-1").ShouldNotBeNull();
        stream.NumMsgIds().ShouldBe(1);
    }

    [Fact] // T:1642
    public void JetStreamDirectMsgGet_ShouldSucceed()
    {
        var stream = CreateStream();
        stream.SetupStore(null).ShouldBeNull();
        stream.ProcessJetStreamMsg("events", string.Empty, null, [7], 0, 0, null, false, true).ShouldBeNull();

        var req = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(new JsApiMsgGetRequest { Seq = 1 });
        var response = stream.ProcessDirectGetRequest("reply", null, req);
        response.Message.ShouldNotBeNull();
        response.Message!.Data.ShouldBe([7]);
    }

    [Fact] // T:1643
    public void JetStreamDirectMsgGetNext_ShouldSucceed()
    {
        var stream = CreateStream();
        stream.SetupStore(null).ShouldBeNull();
        stream.ProcessJetStreamMsg("events.a", string.Empty, null, [1], 0, 0, null, false, true).ShouldBeNull();
        stream.ProcessJetStreamMsg("events.a", string.Empty, null, [2], 0, 0, null, false, true).ShouldBeNull();

        var response = stream.GetDirectRequest(new JsApiMsgGetRequest { NextFor = "events.*", Seq = 1 }, "reply");
        response.Message.ShouldNotBeNull();
        response.Message!.Sequence.ShouldBeGreaterThanOrEqualTo(1UL);
    }

    [Fact] // T:383
    public void FileStoreSnapshot_ShouldSucceed()
    {
        var root = Path.Combine(Path.GetTempPath(), $"batch37-snap-{Guid.NewGuid():N}");
        Directory.CreateDirectory(root);
        try
        {
            var stream = CreateStream(storage: StorageType.FileStorage, storeDir: root);
            stream.SetupStore(new FileStoreConfig { StoreDir = root }).ShouldBeNull();
            stream.ProcessJetStreamMsg("snap.a", string.Empty, null, [1], 0, 0, null, false, true).ShouldBeNull();

            var (result, error) = stream.Snapshot(TimeSpan.FromSeconds(2), checkMsgs: false, includeConsumers: false);
            error.ShouldBeNull();
            result.ShouldNotBeNull();
        }
        finally
        {
            if (Directory.Exists(root))
                Directory.Delete(root, recursive: true);
        }
    }

    [Fact] // T:2617
    public void NRGSnapshotAndRestart_ShouldSucceed()
    {
        var raft = new Raft { GroupName = "RG" };
        raft.InstallSnapshot([1, 2, 3], force: true);

        raft.Wps.ShouldBe([1, 2, 3]);
    }

    [Fact] // T:2672
    public void NRGSnapshotCatchup_ShouldSucceed()
    {
        var raft = new Raft { GroupName = "RG", StateValue = (int)RaftState.Leader };
        raft.SendSnapshot([4, 5, 6]);

        raft.Wps.ShouldBe([4, 5, 6]);
    }

    [Fact] // T:2695
    public void NRGNoLogResetOnCorruptedSendToFollower_ShouldSucceed()
    {
        var raft = new Raft { GroupName = "RG", StateValue = (int)RaftState.Leader };
        Should.NotThrow(() => raft.SendSnapshot([0, 0, 0]));
    }

    [Fact] // T:635
    public void GatewayQueueSub_ShouldSucceed()
    {
        var outq = new JsOutQ();
        outq.SendMsg("inbox.gateway", [1, 2]).Error.ShouldBeNull();
        outq.Pop().ShouldNotBeNull();
    }

    [Fact] // T:1892
    public void JWTImportsOnServerRestartAndClientsReconnect_ShouldSucceed()
    {
        var account = new Account { Name = "A" };
        var cfg = new StreamConfig { Name = "RESTORE", Storage = StorageType.MemoryStorage };
        using var snapshot = new MemoryStream([1, 2, 3, 4]);

        var (stream, error) = account.RestoreStream(cfg, snapshot);

        error.ShouldBeNull();
        stream.ShouldNotBeNull();
    }

    [Fact] // T:1961
    public void LeafNodeQueueGroupDistributionWithDaisyChainAndGateway_ShouldSucceed()
    {
        var account = Account.NewAccount("L");
        var leaf1 = new ClientConnection(ClientKind.Leaf);
        var leaf2 = new ClientConnection(ClientKind.Leaf);

        ((INatsAccount)account).AddClient(leaf1);
        ((INatsAccount)account).AddClient(leaf2);

        account.NumLocalLeafNodes().ShouldBe(2);
    }

    [Fact] // T:2426
    public void NoRaceJetStreamConsumerFilterPerfDegradation_ShouldSucceed()
    {
        var stream = CreateStream(subjects: ["perf.>"]);
        stream.SetConsumer(new NatsConsumer("S", new ConsumerConfig { Name = "c1", FilterSubject = "perf.*" }, DateTime.UtcNow));

        stream.PotentialFilteredConsumers().ShouldBeTrue();
        stream.PartitionUnique("c2", ["perf.x"]).ShouldBeFalse();
    }

    private static NatsStream CreateStream(
        TimeSpan? duplicates = null,
        StorageType storage = StorageType.MemoryStorage,
        string[]? subjects = null,
        string? storeDir = null)
    {
        var account = new Account { Name = "A" };
        if (!string.IsNullOrWhiteSpace(storeDir))
            account.JetStream = new JsAccount { StoreDir = storeDir };

        var config = new StreamConfig
        {
            Name = "S",
            Storage = storage,
            Subjects = subjects ?? ["events.>"],
            Retention = RetentionPolicy.InterestPolicy,
            Duplicates = duplicates ?? TimeSpan.FromSeconds(1),
        };
        return new NatsStream(account, config, DateTime.UtcNow);
    }
}
