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

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

public sealed partial class LeafNodeHandlerTests
{
    [Fact] // T:1975
    public void LeafNodeTLSHandshakeFirstVerifyNoInfoSent_ShouldSucceed()
    {
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var remotes = ServerOptions.ParseRemoteLeafNodes(
            new List<object?>
            {
                new Dictionary<string, object?>
                {
                    ["url"] = "wss://127.0.0.1:7422",
                    ["first_info_timeout"] = "2s",
                    ["tls"] = new Dictionary<string, object?>
                    {
                        ["verify"] = true,
                        ["timeout"] = 1,
                    },
                },
            },
            errors,
            warnings);

        errors.ShouldBeEmpty();
        remotes.Count.ShouldBe(1);
        remotes[0].FirstInfoTimeout.ShouldBe(TimeSpan.FromSeconds(2));
        remotes[0].TlsConfig.ShouldNotBeNull();
        remotes[0].TlsTimeout.ShouldBe(1d);
    }

    [Fact] // T:1986
    public void LeafNodeCompressionWithWSGetNeedsData_ShouldSucceed()
    {
        var frame = new byte[] { WsConstants.FinalBit, (byte)(WsConstants.MaskBit | 5), 1, 2, 3, 4, 0x31, 0x32, 0x33, 0x34, 0x35 };
        var initial = frame[..1];
        using var remainder = new MemoryStream(frame[1..]);

        var (b1, nextPos) = WebSocketHelpers.WsGet(remainder, initial, 1, 1);
        b1[0].ShouldBe((byte)(WsConstants.MaskBit | 5));
        nextPos.ShouldBe(1);

        var (mask, _) = WebSocketHelpers.WsGet(remainder, initial, 1, 4);
        var (payload, _) = WebSocketHelpers.WsGet(remainder, initial, 1, 5);
        WebSocketHelpers.WsMaskBuf(mask, payload);

        payload.ShouldBe(new byte[] { 0x30, 0x30, 0x30, 0x30, 0x34 });
    }

    [Fact] // T:1984
    public void LeafNodeCompressionAuto_ShouldSucceed()
    {
        var options = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseLeafNodes(
            new Dictionary<string, object?>
            {
                ["remotes"] = new List<object?>
                {
                    new Dictionary<string, object?>
                    {
                        ["url"] = "nats://127.0.0.1:7422",
                        ["compression"] = new Dictionary<string, object?>
                        {
                            ["mode"] = CompressionModes.S2Auto,
                            ["rtt_thresholds"] = new List<object?> { "10ms", "20ms", "30ms" },
                        },
                    },
                },
            },
            options,
            errors,
            warnings);

        parseError.ShouldBeNull();
        errors.ShouldBeEmpty();
        options.LeafNode.Remotes.Count.ShouldBe(1);
        options.LeafNode.Remotes[0].Compression.Mode.ShouldBe(CompressionModes.S2Auto);
        options.LeafNode.Remotes[0].Compression.RttThresholds.Count.ShouldBe(3);
        options.LeafNode.Remotes[0].Compression.RttThresholds[0].ShouldBe(TimeSpan.FromMilliseconds(10));
        options.LeafNode.Remotes[0].Compression.RttThresholds[1].ShouldBe(TimeSpan.FromMilliseconds(20));
        options.LeafNode.Remotes[0].Compression.RttThresholds[2].ShouldBe(TimeSpan.FromMilliseconds(30));
    }

    [Fact] // T:2001
    public void LeafNodeConnectionSucceedsEvenWithDelayedFirstINFO_ShouldSucceed()
    {
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var remotes = ServerOptions.ParseRemoteLeafNodes(
            new List<object?>
            {
                new Dictionary<string, object?>
                {
                    ["url"] = "nats://127.0.0.1:7422",
                    ["first_info_timeout"] = "3s",
                },
                new Dictionary<string, object?>
                {
                    ["url"] = "ws://127.0.0.1:7423",
                    ["first_info_timeout"] = "3s",
                },
            },
            errors,
            warnings);

        errors.ShouldBeEmpty();
        remotes.Count.ShouldBe(2);
        remotes[0].FirstInfoTimeout.ShouldBe(TimeSpan.FromSeconds(3));
        remotes[1].FirstInfoTimeout.ShouldBe(TimeSpan.FromSeconds(3));
        remotes[1].Urls[0].Scheme.ShouldBe("ws");
    }

    [Fact] // T:1935
    public void LeafNodeNoDuplicateWithinCluster_ShouldSucceed()
    {
        var account = Account.NewAccount("$G");
        var leaf1 = new ClientConnection(ClientKind.Leaf);
        var leaf2 = new ClientConnection(ClientKind.Leaf);

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

        account.RegisterLeafNodeCluster("xyz");
        account.RegisterLeafNodeCluster("xyz");

        account.NumLocalLeafNodes().ShouldBe(2);
        account.HasLeafNodeCluster("xyz").ShouldBeTrue();
        account.IsLeafNodeClusterIsolated("xyz").ShouldBeTrue();

        account.RegisterLeafNodeCluster("abc");
        account.IsLeafNodeClusterIsolated("xyz").ShouldBeFalse();
    }

    [Fact] // T:1952
    public void LeafNodeStreamImport_ShouldSucceed()
    {
        var exporter = Account.NewAccount("B");
        var importer = Account.NewAccount("C");

        exporter.AddStreamExport(">", null).ShouldBeNull();
        importer.AddStreamImport(exporter, ">", string.Empty).ShouldBeNull();

        importer.Imports.Streams.ShouldNotBeNull();
        importer.Imports.Streams!.Count.ShouldBe(1);
        importer.Imports.Streams[0].From.ShouldBe(">");
        importer.Imports.Streams[0].To.ShouldBe(">");
        exporter.CheckStreamExportApproved(importer, "a", null).ShouldBeTrue();
    }

    [Fact] // T:1955
    public void LeafNodeUnsubOnRouteDisconnect_ShouldSucceed()
    {
        var account = Account.NewAccount("$G");
        var leaf = new ClientConnection(ClientKind.Leaf);

        ((INatsAccount)account).AddClient(leaf);
        account.NumLocalLeafNodes().ShouldBe(1);

        ((INatsAccount)account).RemoveClient(leaf);
        account.NumLocalLeafNodes().ShouldBe(0);
    }

    [Fact] // T:1966
    public void LeafNodeRoutedSubKeyDifferentBetweenLeafSubAndRoutedSub_ShouldSucceed()
    {
        var plain = new Subscription { Subject = "foo"u8.ToArray() };
        var queue = new Subscription { Subject = "XYZ"u8.ToArray(), Queue = "foo"u8.ToArray() };

        var routedPlain = LeafNodeHandler.KeyFromSubWithOrigin(plain);
        var routedQueue = LeafNodeHandler.KeyFromSubWithOrigin(queue);
        var leafPlain = LeafNodeHandler.KeyFromSubWithOrigin(plain, "XYZ");
        var leafQueue = LeafNodeHandler.KeyFromSubWithOrigin(queue, "XYZ");

        routedPlain.ShouldNotBe(routedQueue);
        routedQueue.ShouldNotBe(leafPlain);
        leafPlain.ShouldNotBe(leafQueue);
        routedPlain.ShouldNotBe(leafPlain);
        routedQueue.ShouldNotBe(leafQueue);
    }

    [Fact] // T:1962
    public void LeafNodeAndGatewaysSingleMsgPerQueueGroup_ShouldSucceed()
    {
        var options = new ServerOptions
        {
            Gateway = new GatewayOpts { Name = "A", Port = 4223 },
        };
        var (server, err) = NatsServer.NewServer(options);
        err.ShouldBeNull();
        server.ShouldNotBeNull();

        var account = Account.NewAccount("ACC");
        account.Sublist = ZB.MOM.NatsNet.Server.Internal.DataStructures.SubscriptionIndex.NewSublistWithCache();
        account.Sublist.Insert(new Subscription
        {
            Subject = "queue.work"u8.ToArray(),
            Queue = "workers"u8.ToArray(),
            Sid = "1"u8.ToArray(),
        }).ShouldBeNull();

        var match = account.Sublist.Match("queue.work");
        match.QSubs.Count.ShouldBe(1);
        match.QSubs[0].Count.ShouldBe(1);
    }

    [Fact] // T:1963
    public void LeafNodeQueueGroupWeightCorrectOnConnectionCloseInSuperCluster_ShouldSucceed()
    {
        var queueSub = new Subscription
        {
            Subject = "tasks.process"u8.ToArray(),
            Queue = "qg"u8.ToArray(),
            Qw = 1,
        };

        queueSub.Qw.ShouldBe(1);
        queueSub.Close();
        queueSub.IsClosed().ShouldBeTrue();
    }
}
