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

namespace ZB.MOM.NatsNet.Server.Tests;

public sealed class NatsServerJetStreamEventsTests
{
    [Fact]
    public void PublishAdvisory_NullAccount_UsesSystemAccount()
    {
        var server = NewServer(new ServerOptions());
        server.SetDefaultSystemAccount().ShouldBeNull();
        var systemAccount = server.SystemAccount();
        systemAccount.ShouldNotBeNull();

        const string subject = "$JS.EVENT.ADVISORY.API";
        AddInterest(systemAccount!, subject);

        Account? capturedAccount = null;

        var result = server.PublishAdvisory(
            acc: null,
            subject,
            new { Event = "ok" },
            sendInternalAccountMessage: (account, _, _) =>
            {
                capturedAccount = account;
                return null;
            });

        result.ShouldBeTrue();
        capturedAccount.ShouldBe(systemAccount);
    }

    [Fact]
    public void PublishAdvisory_NoInterest_ReturnsFalseWithoutSend()
    {
        var server = NewServer(new ServerOptions { NoSystemAccount = true });
        var account = new Account { Name = "A" };
        var sendCalls = 0;

        var result = server.PublishAdvisory(
            account,
            "$JS.EVENT.ADVISORY.API",
            new { Event = "no-interest" },
            sendInternalAccountMessage: (_, _, _) =>
            {
                sendCalls++;
                return null;
            },
            hasGatewayInterest: (_, _) => false);

        result.ShouldBeFalse();
        sendCalls.ShouldBe(0);
    }

    [Fact]
    public void PublishAdvisory_MarshalFailure_ReturnsFalse()
    {
        var server = NewServer(new ServerOptions { NoSystemAccount = true });
        var account = new Account { Name = "A" };
        AddInterest(account, "$JS.EVENT.ADVISORY.API");

        var sendCalls = 0;
        var advisory = new CyclicAdvisory();
        advisory.Self = advisory;

        var result = server.PublishAdvisory(
            account,
            "$JS.EVENT.ADVISORY.API",
            advisory,
            sendInternalAccountMessage: (_, _, _) =>
            {
                sendCalls++;
                return null;
            });

        result.ShouldBeFalse();
        sendCalls.ShouldBe(0);
    }

    [Fact]
    public void PublishAdvisory_SendFailure_ReturnsFalse()
    {
        var server = NewServer(new ServerOptions { NoSystemAccount = true });
        var account = new Account { Name = "A" };
        AddInterest(account, "$JS.EVENT.ADVISORY.API");

        var result = server.PublishAdvisory(
            account,
            "$JS.EVENT.ADVISORY.API",
            new { Event = "send-error" },
            sendInternalAccountMessage: (_, _, _) => new InvalidOperationException("send failed"));

        result.ShouldBeFalse();
    }

    [Fact]
    public void PublishAdvisory_SendSucceeds_ReturnsTrue()
    {
        var server = NewServer(new ServerOptions { NoSystemAccount = true });
        var account = new Account { Name = "A" };
        AddInterest(account, "$JS.EVENT.ADVISORY.API");

        byte[]? payload = null;

        var result = server.PublishAdvisory(
            account,
            "$JS.EVENT.ADVISORY.API",
            new { Event = "sent" },
            sendInternalAccountMessage: (_, _, bytes) =>
            {
                payload = bytes;
                return null;
            });

        result.ShouldBeTrue();
        payload.ShouldNotBeNull();
        Encoding.UTF8.GetString(payload!).ShouldContain("\"Event\":\"sent\"");
    }

    private static NatsServer NewServer(ServerOptions options)
    {
        var (server, err) = NatsServer.NewServer(options);
        err.ShouldBeNull();
        server.ShouldNotBeNull();
        return server!;
    }

    private static void AddInterest(Account account, string subject)
    {
        account.Sublist ??= SubscriptionIndex.NewSublistWithCache();
        var err = account.Sublist.Insert(new Subscription
        {
            Subject = Encoding.ASCII.GetBytes(subject),
        });
        err.ShouldBeNull();
    }

    private sealed class CyclicAdvisory
    {
        public CyclicAdvisory? Self { get; set; }
    }
}
