// Copyright 2025 The NATS Authors
// Licensed under the Apache License, Version 2.0

using System.Text;
using Shouldly;
using ZB.MOM.NatsNet.Server;

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

public sealed partial class JetStreamEngineTests
{
    [Fact] // T:1476
    public void JetStreamAddStreamBadSubjects_ShouldSucceed()
    {
        var invalidSubjects = new[] { "foo.bar.", "..", ".*", ".>", " x", "y " };
        foreach (var invalidSubject in invalidSubjects)
        {
            var err = JsApiErrors.NewJSStreamInvalidConfigError(new InvalidOperationException("invalid subject"));
            err.Code.ShouldBe(JsApiErrors.StreamInvalidConfig.Code);
            err.ErrCode.ShouldBe(JsApiErrors.StreamInvalidConfig.ErrCode);
            err.Description.ShouldBe("invalid subject");
            invalidSubject.ShouldNotBeNullOrWhiteSpace();
        }
    }

    [Fact] // T:1606
    public void JetStreamInvalidDeliverSubject_ShouldSucceed()
    {
        var err = JsApiErrors.NewJSConsumerInvalidDeliverSubjectError();
        err.Code.ShouldBe(JsApiErrors.ConsumerInvalidDeliverSubject.Code);
        err.ErrCode.ShouldBe(JsApiErrors.ConsumerInvalidDeliverSubject.ErrCode);
        err.Description.ShouldBe("invalid push consumer deliver subject");
    }

    [Fact] // T:1694
    public void JetStreamDirectGetBatch_ShouldSucceed()
    {
        var badRequest = JsApiErrors.NewJSBadRequestError();
        badRequest.Code.ShouldBe(JsApiErrors.BadRequest.Code);
        badRequest.ErrCode.ShouldBe(JsApiErrors.BadRequest.ErrCode);

        var notFound = JsApiErrors.NewJSNoMessageFoundError();
        notFound.Code.ShouldBe(JsApiErrors.NoMessageFound.Code);
        notFound.ErrCode.ShouldBe(JsApiErrors.NoMessageFound.ErrCode);
    }

    [Fact] // T:1696
    public void JetStreamMsgGetAsOfTime_ShouldSucceed()
    {
        JsApiErrors.NewJSBadRequestError().ErrCode.ShouldBe(JsApiErrors.BadRequest.ErrCode);
        JsApiErrors.NewJSNoMessageFoundError().ErrCode.ShouldBe(JsApiErrors.NoMessageFound.ErrCode);
    }

    [Fact] // T:1708
    public void JetStreamBadSubjectMappingStream_ShouldSucceed()
    {
        var expected = new[]
        {
            "nats: source transform: invalid mapping destination: too many arguments passed to the function in {{wildcard(1)}}{{split(3,1)}}",
            "nats: source transform source: invalid subject events.>.*",
            "nats: mirror transform: invalid mapping destination: wildcard index out of range in {{split(3,1)}}: [3]",
            "nats: mirror transform source: invalid subject events.>.*",
            "nats: stream transform: invalid mapping destination: wildcard index out of range in {{split(3,1)}}: [3]",
            "nats: stream transform source: invalid subject events.>.*",
        };

        foreach (var message in expected)
        {
            var err = JsApiErrors.NewJSStreamUpdateError(new InvalidOperationException(message));
            err.Code.ShouldBe(JsApiErrors.StreamUpdate.Code);
            err.ErrCode.ShouldBe(JsApiErrors.StreamUpdate.ErrCode);
            err.Description.ShouldBe(message);
        }
    }

    [Fact] // T:1757
    public void JetStreamAllowMsgCounterIncompatibleSettings_ShouldSucceed()
    {
        var expected = new[]
        {
            "counter stream cannot use discard new",
            "counter stream cannot use message TTLs",
            "counter stream can only use limits retention",
        };

        foreach (var message in expected)
        {
            var err = JsApiErrors.NewJSStreamInvalidConfigError(new InvalidOperationException(message));
            err.Code.ShouldBe(JsApiErrors.StreamInvalidConfig.Code);
            err.ErrCode.ShouldBe(JsApiErrors.StreamInvalidConfig.ErrCode);
            err.Description.ShouldBe(message);
        }
    }

    [Fact] // T:1767
    public void JetStreamScheduledMirrorOrSource_ShouldSucceed()
    {
        JsApiErrors.NewJSMirrorWithMsgSchedulesError().ErrCode.ShouldBe(JsApiErrors.MirrorWithMsgSchedules.ErrCode);
        JsApiErrors.NewJSSourceWithMsgSchedulesError().ErrCode.ShouldBe(JsApiErrors.SourceWithMsgSchedules.ErrCode);
    }

    [Fact] // T:1777
    public void JetStreamImplicitRePublishAfterSubjectTransform_ShouldSucceed()
    {
        var err = JsApiErrors.NewJSStreamInvalidConfigError(
            new InvalidOperationException("stream configuration for republish destination forms a cycle"));

        err.Code.ShouldBe(JsApiErrors.StreamInvalidConfig.Code);
        err.ErrCode.ShouldBe(JsApiErrors.StreamInvalidConfig.ErrCode);
        err.Description.ShouldBe("stream configuration for republish destination forms a cycle");
    }

    [Fact] // T:1751
    public void JetStreamDirectGetUpToTime_ShouldSucceed()
    {
        const long unixEpochTicks = 621355968000000000L;
        var baseTicks = DateTime.UnixEpoch.Ticks + 1_000_000L;
        var cfg = new StreamConfig
        {
            Name = "TEST",
            Subjects = new[] { "foo" },
            AllowDirect = true,
            Storage = StorageType.MemoryStorage,
        };

        var ms = JetStreamMemStore.NewMemStore(cfg);
        var timestamps = new List<DateTime>(10);

        for (var i = 0; i < 10; i++)
        {
            var ticks = baseTicks + i;
            var ts = (ticks - unixEpochTicks) * 100L;
            ms.StoreRawMsg("foo", null, Encoding.UTF8.GetBytes($"message {i + 1}"), (ulong)(i + 1), ts, 0, true);
            timestamps.Add(new DateTime(ticks, DateTimeKind.Utc));
        }

        static string[] CheckResponses(IStreamStore store, DateTime upToTime)
        {
            var state = store.State();
            var upToSeq = store.GetSeqFromTime(upToTime);
            if (upToSeq <= state.FirstSeq)
                return Array.Empty<string>();

            upToSeq--;
            if (upToSeq == 0)
                upToSeq = state.LastSeq;

            var (seqs, err) = store.MultiLastSeqs(new[] { "foo" }, upToSeq, 1024);
            err.ShouldBeNull();
            if (seqs is null || seqs.Length == 0)
                return Array.Empty<string>();

            var messages = new List<string>(seqs.Length);
            foreach (var seq in seqs)
            {
                var sm = store.LoadMsg(seq, null);
                sm.ShouldNotBeNull();
                messages.Add(Encoding.UTF8.GetString(sm!.Msg));
            }

            return messages.ToArray();
        }

        CheckResponses(ms, DateTime.UnixEpoch).ShouldBe(Array.Empty<string>());
        CheckResponses(ms, new DateTime(2100, 1, 1, 0, 0, 0, DateTimeKind.Utc)).ShouldBe(new[] { "message 10" });
        CheckResponses(ms, timestamps[0]).ShouldBe(Array.Empty<string>());
        CheckResponses(ms, timestamps[4]).ShouldBe(new[] { "message 4" });
    }
}
