using System.Text;
using System.Text.Json;
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 NatsStreamDirectGetPipelineTests
{
    [Fact]
    public void GetMessageScheduleTTL_InvalidValue_ReturnsNotOk()
    {
        var hdr = NatsMessageHeaders.GenHeader(null, NatsHeaderConstants.JsScheduleTtl, "not-a-ttl");

        var (ttl, ok) = NatsStream.GetMessageScheduleTTL(hdr);

        ok.ShouldBeFalse();
        ttl.ShouldBe(string.Empty);
    }

    [Fact]
    public void GetBatchSequence_ValidHeader_ReturnsSequence()
    {
        var hdr = NatsMessageHeaders.GenHeader(null, NatsHeaderConstants.JsBatchSeq, "9");

        var (seq, ok) = NatsStream.GetBatchSequence(hdr);

        ok.ShouldBeTrue();
        seq.ShouldBe(9UL);
    }

    [Fact]
    public void IsClustered_WithAssignmentNode_ReturnsTrue()
    {
        var stream = CreateStream();
        var node = Substitute.For<IRaftNode>();
        var assignment = new StreamAssignment
        {
            Group = new RaftGroup
            {
                Node = node,
                Peers = ["N1"],
            },
        };

        stream.SetStreamAssignment(assignment);

        stream.IsClustered().ShouldBeTrue();
        stream.IsClusteredInternal().ShouldBeTrue();
    }

    [Fact]
    public void InMsgReturnToPool_WithValues_ClearsState()
    {
        var msg = new InMsg
        {
            Subject = "foo",
            Reply = "bar",
            Hdr = [1, 2],
            Msg = [3, 4],
            Client = new object(),
        };

        msg.ReturnToPool();

        msg.Subject.ShouldBe(string.Empty);
        msg.Reply.ShouldBeNull();
        msg.Hdr.ShouldBeNull();
        msg.Msg.ShouldBeNull();
    }

    [Fact]
    public void QueueInbound_WithQueue_PushesMessage()
    {
        var stream = CreateStream();
        var queue = new IpQueue<InMsg>("inbound");

        stream.QueueInbound(queue, "orders.created", null, null, [1], null, null);
        var popped = queue.Pop();

        popped.ShouldNotBeNull();
        popped!.Length.ShouldBe(1);
        popped[0].Subject.ShouldBe("orders.created");
    }

    [Fact]
    public void ProcessDirectGetRequest_SeqRequest_ReturnsStoredMessage()
    {
        var stream = CreateStream();
        stream.SetupStore(null).ShouldBeNull();
        stream.Store!.StoreMsg("orders", null, [7, 8], ttl: 0);

        var request = JsonSerializer.SerializeToUtf8Bytes(new JsApiMsgGetRequest { Seq = 1 });
        var response = stream.ProcessDirectGetRequest("reply.inbox", null, request);

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

    [Fact]
    public void ProcessDirectGetLastBySubjectRequest_InvalidSubject_ReturnsBadRequest()
    {
        var stream = CreateStream();

        var response = stream.ProcessDirectGetLastBySubjectRequest("$JS.API.DIRECT.GET.STREAM", "reply", null, null);

        response.Error.ShouldNotBeNull();
        response.Error!.ErrCode.ShouldBe(JsApiErrors.BadRequest.ErrCode);
    }

    [Fact]
    public void ProcessJetStreamMsg_WithMemoryStore_StoresMessage()
    {
        var stream = CreateStream();
        stream.SetupStore(null).ShouldBeNull();

        var error = stream.ProcessJetStreamMsg("events", string.Empty, null, Encoding.ASCII.GetBytes("m1"), 0, 0, null, false, true);
        var stored = stream.Store!.LoadMsg(1, new StoreMsg());

        error.ShouldBeNull();
        stored.ShouldNotBeNull();
        stored!.Subject.ShouldBe("events");
    }

    [Fact]
    public void ProcessJetStreamBatchMsg_MissingSequence_ReturnsApiError()
    {
        var stream = CreateStream();
        stream.SetupStore(null).ShouldBeNull();

        var error = stream.ProcessJetStreamBatchMsg("batch-1", "events", string.Empty, null, [1], null);

        error.ShouldNotBeNull();
        error.ShouldBeOfType<InvalidOperationException>();
    }

    private static NatsStream CreateStream()
    {
        var account = new Account { Name = "A" };
        var config = new StreamConfig
        {
            Name = "S",
            Storage = StorageType.MemoryStorage,
            Subjects = ["events.>"],
        };
        return new NatsStream(account, config, DateTime.UtcNow);
    }
}
