using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Auth;
using System.Linq;

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

public sealed class ServerOptionsTests
{
    private static Dictionary<string, object?> Map(params (string Key, object? Value)[] entries)
    {
        var map = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
        foreach (var (key, value) in entries)
            map[key] = value;
        return map;
    }

    private static List<object?> Arr(params object?[] entries) => [.. entries];

    private static string CreateJsonConfig(string json)
    {
        var path = Path.GetTempFileName();
        File.WriteAllText(path, json);
        return path;
    }

    private static T ReadProperty<T>(object target, string propertyName)
    {
        var property = target.GetType().GetProperty(propertyName);
        property.ShouldNotBeNull();
        var value = property.GetValue(target);
        value.ShouldNotBeNull();
        return (T)value;
    }

    [Fact]
    public void DeepCopyURLs_WithEntries_ReturnsIndependentCopy()
    {
        var source = new List<Uri>
        {
            new("nats://127.0.0.1:4222"),
            new("nats://127.0.0.1:4223"),
        };

        var copy = ServerOptions.DeepCopyURLs(source);

        copy.ShouldNotBeNull();
        copy.Count.ShouldBe(2);
        ReferenceEquals(copy, source).ShouldBeFalse();
        copy[0].ToString().ShouldBe(source[0].ToString());
        copy[1].ToString().ShouldBe(source[1].ToString());
    }

    [Fact]
    public void ProcessConfigFile_WithValidFile_ReturnsParsedOptions()
    {
        var tempFile = Path.GetTempFileName();

        try
        {
            File.WriteAllText(tempFile, """
            {
              "host": "127.0.0.1",
              "port": 4444,
              "system_account": "$SYS"
            }
            """);

            var options = ServerOptions.ProcessConfigFile(tempFile);

            options.Host.ShouldBe("127.0.0.1");
            options.Port.ShouldBe(4444);
            options.SystemAccount.ShouldBe("$SYS");
        }
        finally
        {
            File.Delete(tempFile);
        }
    }

    [Fact]
    public void ConfigureSystemAccount_WithSystemAccountString_SetsValue()
    {
        var options = new ServerOptions();
        var config = new Dictionary<string, object?>
        {
            ["system_account"] = "$SYSX",
        };

        var error = ServerOptions.ConfigureSystemAccount(options, config);

        error.ShouldBeNull();
        options.SystemAccount.ShouldBe("$SYSX");
    }

    [Fact]
    public void ConfigureSystemAccount_WithNonString_ReturnsError()
    {
        var options = new ServerOptions();
        var config = new Dictionary<string, object?>
        {
            ["system"] = 123L,
        };

        var error = ServerOptions.ConfigureSystemAccount(options, config);

        error.ShouldNotBeNull();
        error.Message.ShouldContain("must be a string");
    }

    [Fact]
    public void SetupUsersAndNKeysDuplicateCheckMap_WithUsersAndNkeys_IncludesAllIdentities()
    {
        var options = new ServerOptions
        {
            Users =
            [
                new User { Username = "alice" },
                new User { Username = "bob" },
            ],
            Nkeys =
            [
                new NkeyUser { Nkey = "UAA" },
                new NkeyUser { Nkey = "UBB" },
            ],
        };

        var map = ServerOptions.SetupUsersAndNKeysDuplicateCheckMap(options);

        map.Count.ShouldBe(4);
        map.ShouldContain("alice");
        map.ShouldContain("bob");
        map.ShouldContain("UAA");
        map.ShouldContain("UBB");
    }

    [Fact]
    public void ParseDuration_WithDurationString_ReturnsExpectedDuration()
    {
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parsed = ServerOptions.ParseDuration("write_deadline", "5s", errors, warnings);

        parsed.ShouldBe(TimeSpan.FromSeconds(5));
        errors.ShouldBeEmpty();
        warnings.ShouldBeEmpty();
    }

    [Fact]
    public void ParseDuration_WithLegacySeconds_AddsWarning()
    {
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parsed = ServerOptions.ParseDuration("auth_timeout", 3L, errors, warnings);

        parsed.ShouldBe(TimeSpan.FromSeconds(3));
        errors.ShouldBeEmpty();
        warnings.Count.ShouldBe(1);
    }

    [Fact]
    public void ParseWriteDeadlinePolicy_WithInvalidValue_ReturnsDefaultAndError()
    {
        var errors = new List<Exception>();

        var policy = ServerOptions.ParseWriteDeadlinePolicy("invalid", errors);

        policy.ShouldBe(WriteTimeoutPolicy.Default);
        errors.Count.ShouldBe(1);
        errors[0].Message.ShouldContain("write_timeout");
    }

    [Theory]
    [InlineData(8222L, "", 8222)]
    [InlineData("127.0.0.1:6222", "127.0.0.1", 6222)]
    public void ParseListen_WithValidInput_ReturnsHostPort(object input, string expectedHost, int expectedPort)
    {
        var (host, port) = ServerOptions.ParseListen(input);

        host.ShouldBe(expectedHost);
        port.ShouldBe(expectedPort);
    }

    [Fact]
    public void ParseCompression_WithMapValue_ParsesModeAndThresholds()
    {
        var compression = new CompressionOpts();

        var error = ServerOptions.ParseCompression(
            compression,
            CompressionModes.S2Fast,
            "compression",
            new Dictionary<string, object?>
            {
                ["mode"] = CompressionModes.S2Best,
                ["rtt_thresholds"] = new List<object?> { "10ms", "25ms" },
            });

        error.ShouldBeNull();
        compression.Mode.ShouldBe(CompressionModes.S2Best);
        compression.RttThresholds.Count.ShouldBe(2);
        compression.RttThresholds[0].ShouldBe(TimeSpan.FromMilliseconds(10));
        compression.RttThresholds[1].ShouldBe(TimeSpan.FromMilliseconds(25));
    }

    [Fact]
    public void ParseURLs_WithDuplicateEntries_DeduplicatesAndWarns()
    {
        var warnings = new List<Exception>();
        var errors = new List<Exception>();

        var urls = ServerOptions.ParseURLs(
            ["nats://127.0.0.1:4222", "nats://127.0.0.1:4222", "nats://127.0.0.1:4223"],
            "route",
            warnings,
            errors);

        errors.ShouldBeEmpty();
        warnings.Count.ShouldBe(1);
        urls.Count.ShouldBe(2);
    }

    [Fact]
    public void ParseCluster_WithBasicConfig_PopulatesClusterAndRoutes()
    {
        var options = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseCluster(
            new Dictionary<string, object?>
            {
                ["name"] = "core",
                ["listen"] = "127.0.0.1:6222",
                ["connect_retries"] = 8L,
                ["connect_backoff"] = true,
                ["no_advertise"] = true,
                ["compression"] = "s2_fast",
                ["routes"] = new List<object?> { "nats://127.0.0.1:6223" },
            },
            options,
            errors,
            warnings);

        parseError.ShouldBeNull();
        errors.ShouldBeEmpty();
        options.Cluster.Name.ShouldBe("core");
        options.Cluster.Host.ShouldBe("127.0.0.1");
        options.Cluster.Port.ShouldBe(6222);
        options.Cluster.ConnectRetries.ShouldBe(8);
        options.Cluster.ConnectBackoff.ShouldBeTrue();
        options.Cluster.NoAdvertise.ShouldBeTrue();
        options.Cluster.Compression.Mode.ShouldBe("s2_fast");
        options.Routes.Count.ShouldBe(1);
        options.Routes[0].ToString().ShouldBe("nats://127.0.0.1:6223/");
    }

    [Fact]
    public void ParseGateway_WithBasicConfig_PopulatesGateway()
    {
        var options = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseGateway(
            new Dictionary<string, object?>
            {
                ["name"] = "edge",
                ["listen"] = "127.0.0.1:7222",
                ["connect_retries"] = 4L,
                ["connect_backoff"] = true,
                ["advertise"] = "gw.local:7222",
                ["reject_unknown"] = true,
                ["authorization"] = new Dictionary<string, object?>
                {
                    ["user"] = "gwu",
                    ["password"] = "gwp",
                    ["auth_timeout"] = 3L,
                },
            },
            options,
            errors,
            warnings);

        parseError.ShouldBeNull();
        errors.ShouldBeEmpty();
        options.Gateway.Name.ShouldBe("edge");
        options.Gateway.Host.ShouldBe("127.0.0.1");
        options.Gateway.Port.ShouldBe(7222);
        options.Gateway.ConnectRetries.ShouldBe(4);
        options.Gateway.ConnectBackoff.ShouldBeTrue();
        options.Gateway.Advertise.ShouldBe("gw.local:7222");
        options.Gateway.RejectUnknown.ShouldBeTrue();
        options.Gateway.Username.ShouldBe("gwu");
        options.Gateway.Password.ShouldBe("gwp");
        options.Gateway.AuthTimeout.ShouldBe(3);
    }

    [Fact] // T:2514
    public void ConfigFile_ShouldSucceed()
    {
        var path = CreateJsonConfig("""
        {
          "Host": "127.0.0.1",
          "Port": 4242,
          "SystemAccount": "$SYS"
        }
        """);

        try
        {
            var opts = ServerOptions.ProcessConfigFile(path);
            opts.Host.ShouldBe("127.0.0.1");
            opts.Port.ShouldBe(4242);
            opts.SystemAccount.ShouldBe("$SYS");
        }
        finally
        {
            File.Delete(path);
        }
    }

    [Fact] // T:2517
    public void RouteFlagOverride_ShouldSucceed()
    {
        var merged = ServerOptions.MergeOptions(
            new ServerOptions(),
            new ServerOptions { RoutesStr = "nats-route://ruser:top_secret@127.0.0.1:8246" });

        merged.RoutesStr.ShouldBe("nats-route://ruser:top_secret@127.0.0.1:8246");
        merged.Routes.Count.ShouldBe(1);
        merged.Routes[0].ToString().ShouldBe("nats-route://ruser:top_secret@127.0.0.1:8246/");
    }

    [Fact] // T:2519
    public void RouteFlagOverrideWithMultiple_ShouldSucceed()
    {
        var routes = "nats-route://ruser:top_secret@127.0.0.1:8246, nats-route://ruser:top_secret@127.0.0.1:8266";
        var merged = ServerOptions.MergeOptions(new ServerOptions(), new ServerOptions { RoutesStr = routes });

        merged.RoutesStr.ShouldBe(routes);
        merged.Routes.Count.ShouldBe(2);
    }

    [Fact] // T:2520
    public void DynamicPortOnListen_ShouldSucceed()
    {
        var (host, port) = ServerOptions.ParseListen("127.0.0.1:-1");
        host.ShouldBe("127.0.0.1");
        port.ShouldBe(-1);
    }

    [Fact] // T:2521
    public void ListenConfig_ShouldSucceed()
    {
        var opts = new ServerOptions();
        var error = opts.ProcessConfigString("""
        {
          "listen": "10.0.1.22:4422",
          "cluster": {
            "listen": "127.0.0.1:4244"
          }
        }
        """);

        error.ShouldBeNull();
        opts.SetBaselineOptions();
        opts.Host.ShouldBe("10.0.1.22");
        opts.Port.ShouldBe(4422);
        opts.Cluster.Host.ShouldBe("127.0.0.1");
        opts.Cluster.Port.ShouldBe(4244);
    }

    [Fact] // T:2522
    public void ListenPortOnlyConfig_ShouldSucceed()
    {
        var opts = new ServerOptions();
        var error = opts.ProcessConfigString("""
        {
          "listen": 8922
        }
        """);

        error.ShouldBeNull();
        opts.SetBaselineOptions();
        opts.Host.ShouldBe(ServerConstants.DefaultHost);
        opts.Port.ShouldBe(8922);
    }

    [Fact] // T:2523
    public void ListenPortWithColonConfig_ShouldSucceed()
    {
        var (host, port) = ServerOptions.ParseListen("127.0.0.1:8922");
        host.ShouldBe("127.0.0.1");
        port.ShouldBe(8922);
    }

    [Fact] // T:2525
    public void MultipleUsersConfig_ShouldSucceed()
    {
        var (nkeys, users, error) = ServerOptions.ParseUsers(
            Arr(
                Map(("user", "alice"), ("password", "foo")),
                Map(("user", "bob"), ("password", "bar"))));

        error.ShouldBeNull();
        users.Count.ShouldBe(2);
        nkeys.ShouldBeEmpty();
    }

    [Fact] // T:2526
    public void AuthorizationConfig_ShouldSucceed()
    {
        var (auth, error) = ServerOptions.ParseAuthorization(
            Map(("users", Arr(
                    Map(
                        ("user", "alice"),
                        ("password", "pwd"),
                        ("permissions", Map(("publish", "*"), ("subscribe", ">")))),
                    Map(("user", "bob"), ("password", "pwd"))))));

        error.ShouldBeNull();
        auth.ShouldNotBeNull();
        auth.Users.Count.ShouldBe(2);

        var alice = auth.Users.Single(u => u.Username == "alice");
        alice.Permissions.ShouldNotBeNull();
        alice.Permissions.Publish.ShouldNotBeNull();
        alice.Permissions.Publish.Allow.ShouldContain("*");
        alice.Permissions.Subscribe.ShouldNotBeNull();
        alice.Permissions.Subscribe.Allow.ShouldContain(">");
    }

    [Fact] // T:2527
    public void NewStyleAuthorizationConfig_ShouldSucceed()
    {
        var (auth, error) = ServerOptions.ParseAuthorization(
            Map(("users", Arr(
                Map(
                    ("user", "alice"),
                    ("password", "pwd"),
                    ("permissions", Map(
                        ("publish", Map(("allow", Arr("foo", "bar", "baz")))),
                        ("subscribe", Map(("deny", Arr("$SYS.>")))))))))));

        error.ShouldBeNull();
        auth.ShouldNotBeNull();
        var alice = auth.Users.Single();
        alice.Permissions.ShouldNotBeNull();
        alice.Permissions.Publish.Allow.Count.ShouldBe(3);
        alice.Permissions.Subscribe.Deny.ShouldContain("$SYS.>");
    }

    [Fact] // T:2528
    public void NkeyUsersConfig_ShouldSucceed()
    {
        var (nkeys, users, error) = ServerOptions.ParseUsers(
            Arr(
                Map(("nkey", "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV")),
                Map(("nkey", "UA3C5TBZYK5GJQJRWPMU6NFY5JNAEVQB2V2TUZFZDHFJFUYVKTTUOFKZ"))));

        error.ShouldBeNull();
        nkeys.Count.ShouldBe(2);
        users.ShouldBeEmpty();
    }

    [Fact] // T:2529
    public void TlsPinnedCertificates_ShouldSucceed()
    {
        var (tlsOptions, error) = ServerOptions.ParseTLS(
            Map(("pinned_certs", Arr(
                "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069",
                "a8f407340dcc719864214b85ed96f98d16cbffa8f509d9fa4ca237b7bb3f9c32"))),
            isClientCtx: false);

        error.ShouldBeNull();
        tlsOptions.ShouldNotBeNull();
        tlsOptions.PinnedCerts.ShouldNotBeNull();
        tlsOptions.PinnedCerts.Count.ShouldBe(2);
    }

    [Fact] // T:2530
    public void NkeyUsersDefaultPermissionsConfig_ShouldSucceed()
    {
        var (auth, error) = ServerOptions.ParseAuthorization(
            Map(
                ("default_permissions", Map(("publish", "foo"))),
                ("users", Arr(
                    Map(("user", "user"), ("password", "pwd")),
                    Map(("user", "other"), ("password", "pwd"), ("permissions", Map(("subscribe", "bar")))),
                    Map(("nkey", "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV")),
                    Map(("nkey", "UA3C5TBZYK5GJQJRWPMU6NFY5JNAEVQB2V2TUZFZDHFJFUYVKTTUOFKZ"), ("permissions", Map(("subscribe", "bar"))))))));

        error.ShouldBeNull();
        auth.ShouldNotBeNull();

        var defaultUser = auth.Users.Single(u => u.Username == "user");
        defaultUser.Permissions.ShouldNotBeNull();
        defaultUser.Permissions.Publish.ShouldNotBeNull();
        defaultUser.Permissions.Publish.Allow.ShouldContain("foo");

        var defaultNkey = auth.Nkeys.Single(n => n.Nkey.StartsWith("UDK", StringComparison.Ordinal));
        defaultNkey.Permissions.ShouldNotBeNull();
        defaultNkey.Permissions.Publish.ShouldNotBeNull();
        defaultNkey.Permissions.Publish.Allow.ShouldContain("foo");
    }

    [Fact] // T:2531
    public void NkeyUsersWithPermsConfig_ShouldSucceed()
    {
        var (nkeys, users, error) = ServerOptions.ParseUsers(
            Arr(Map(
                ("nkey", "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV"),
                ("permissions", Map(
                    ("publish", "$SYS.>"),
                    ("subscribe", Map(("deny", Arr("foo", "bar", "baz")))))))));

        error.ShouldBeNull();
        users.ShouldBeEmpty();
        nkeys.Count.ShouldBe(1);
        nkeys[0].Permissions.ShouldNotBeNull();
        nkeys[0].Permissions.Publish.Allow.ShouldContain("$SYS.>");
        nkeys[0].Permissions.Subscribe.Deny.Count.ShouldBe(3);
    }

    [Fact] // T:2532
    public void BadNkeyConfig_ShouldSucceed()
    {
        var (_, _, error) = ServerOptions.ParseUsers(Arr(Map(("nkey", "Ufoo"))));
        error.ShouldNotBeNull();
        error.Message.ShouldContain("Not a valid public nkey");
    }

    [Fact] // T:2533
    public void NkeyWithPassConfig_ShouldSucceed()
    {
        var (_, _, error) = ServerOptions.ParseUsers(
            Arr(Map(("nkey", "UDKTV7HZVYJFJN64LLMYQBUR6MTNNYCDC3LAZH4VHURW3GZLL3FULBXV"), ("pass", "foo"))));

        error.ShouldNotBeNull();
        error.Message.ShouldContain("Nkey users do not take usernames or passwords");
    }

    [Fact] // T:2540
    public void EmptyConfig_ShouldSucceed()
    {
        var opts = new ServerOptions();
        var error = opts.ProcessConfigFileOverload2510(string.Empty);
        error.ShouldBeNull();
        opts.ConfigFile.ShouldBe(string.Empty);
    }

    [Fact] // T:2541
    public void MalformedListenAddress_ShouldSucceed()
    {
        Should.Throw<InvalidOperationException>(() => ServerOptions.ParseListen("bad::address"));
    }

    [Fact] // T:2542
    public void MalformedClusterAddress_ShouldSucceed()
    {
        var opts = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseCluster(Map(("listen", "bad::address")), opts, errors, warnings);

        parseError.ShouldBeNull();
        errors.Count.ShouldBeGreaterThan(0);
    }

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

        var parsed = ServerOptions.ParseDuration("ping_interval", 5L, errors, warnings);

        parsed.ShouldBe(TimeSpan.FromSeconds(5));
        errors.ShouldBeEmpty();
        warnings.Count.ShouldBe(1);
    }

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

        var parsed = ServerOptions.ParseDuration("ping_interval", "5m", errors, warnings);

        parsed.ShouldBe(TimeSpan.FromMinutes(5));
        errors.ShouldBeEmpty();
        warnings.ShouldBeEmpty();
    }

    [Fact] // T:2547
    public void OptionsProcessConfigFile_ShouldSucceed()
    {
        var path = CreateJsonConfig("""
        {
          "debug": false,
          "trace": true
        }
        """);

        try
        {
            var opts = new ServerOptions
            {
                Debug = true,
                Trace = false,
                LogFile = "test.log",
            };

            var error = opts.ProcessConfigFileOverload2510(path);

            error.ShouldBeNull();
            opts.ConfigFile.ShouldBe(path);
            opts.Debug.ShouldBeFalse();
            opts.Trace.ShouldBeTrue();
            opts.LogFile.ShouldBe("test.log");
        }
        finally
        {
            File.Delete(path);
        }
    }

    [Fact] // T:2549
    public void ClusterPermissionsConfig_ShouldSucceed()
    {
        var cluster = new ClusterOpts();
        var permissions = new Permissions
        {
            Publish = new SubjectPermission { Allow = ["foo"] },
            Subscribe = new SubjectPermission { Allow = ["bar"] },
        };

        ServerOptions.SetClusterPermissions(cluster, permissions);

        cluster.Permissions.ShouldNotBeNull();
        cluster.Permissions.Import.ShouldNotBeNull();
        cluster.Permissions.Import.Allow.ShouldContain("foo");
        cluster.Permissions.Export.ShouldNotBeNull();
        cluster.Permissions.Export.Allow.ShouldContain("bar");
    }

    [Fact] // T:2550
    public void ParseServiceLatency_ShouldSucceed()
    {
        var (latency, error) = ServerOptions.ParseServiceLatency(
            "latency",
            Map(("sampling", "33%"), ("subject", "latency.tracking.add")));

        error.ShouldBeNull();
        latency.ShouldNotBeNull();
        ReadProperty<int>(latency, "Sampling").ShouldBe(33);
        ReadProperty<string>(latency, "Subject").ShouldBe("latency.tracking.add");
    }

    [Fact] // T:2553
    public void ParsingGateways_ShouldSucceed()
    {
        var opts = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseGateway(
            Map(
                ("name", "A"),
                ("listen", "127.0.0.1:4444"),
                ("authorization", Map(("user", "ivan"), ("password", "pwd"), ("timeout", 2L))),
                ("advertise", "me:1"),
                ("connect_retries", 10L),
                ("connect_backoff", true),
                ("reject_unknown_cluster", true)),
            opts,
            errors,
            warnings);

        parseError.ShouldBeNull();
        errors.ShouldBeEmpty();
        opts.Gateway.Name.ShouldBe("A");
        opts.Gateway.Host.ShouldBe("127.0.0.1");
        opts.Gateway.Port.ShouldBe(4444);
        opts.Gateway.Username.ShouldBe("ivan");
        opts.Gateway.Password.ShouldBe("pwd");
        opts.Gateway.AuthTimeout.ShouldBe(2);
        opts.Gateway.ConnectRetries.ShouldBe(10);
        opts.Gateway.ConnectBackoff.ShouldBeTrue();
        opts.Gateway.RejectUnknown.ShouldBeTrue();
    }

    [Fact] // T:2555
    public void ParsingLeafNodesListener_ShouldSucceed()
    {
        var opts = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseLeafNodes(
            Map(
                ("listen", "127.0.0.1:3333"),
                ("authorization", Map(("user", "derek"), ("password", "s3cr3t!"), ("timeout", 2.2))),
                ("advertise", "me:22")),
            opts,
            errors,
            warnings);

        parseError.ShouldBeNull();
        errors.ShouldBeEmpty();
        opts.LeafNode.Host.ShouldBe("127.0.0.1");
        opts.LeafNode.Port.ShouldBe(3333);
        opts.LeafNode.Username.ShouldBe("derek");
        opts.LeafNode.Password.ShouldBe("s3cr3t!");
        opts.LeafNode.AuthTimeout.ShouldBe(2.2);
        opts.LeafNode.Advertise.ShouldBe("me:22");
    }

    [Fact] // T:2556
    public void ParsingLeafNodeRemotes_ShouldSucceed()
    {
        var remotes = ServerOptions.ParseRemoteLeafNodes(
            Arr(
                Map(
                    ("url", "nats-leaf://127.0.0.1:2222"),
                    ("account", "foobar"),
                    ("credentials", "./my.creds"))));

        remotes.Count.ShouldBe(1);
        remotes[0].Urls.Count.ShouldBe(1);
        remotes[0].Urls[0].ToString().ShouldBe("nats-leaf://127.0.0.1:2222/");
        remotes[0].LocalAccount.ShouldBe("foobar");
        remotes[0].Credentials.ShouldContain("my.creds");
    }

    [Fact] // T:2560
    public void SublistNoCacheConfig_ShouldSucceed()
    {
        var opts = new ServerOptions();
        var error = opts.ProcessConfigString("""
        {
          "disable_sublist_cache": true
        }
        """);

        error.ShouldBeNull();
        opts.NoSublistCache.ShouldBeTrue();
    }

    [Fact] // T:2583
    public void OptionsProxyRequired_ShouldSucceed()
    {
        var (authSingle, singleError) = ServerOptions.ParseAuthorization(
            Map(
                ("user", "user"),
                ("password", "pwd"),
                ("proxy_required", true)));

        singleError.ShouldBeNull();
        authSingle.ShouldNotBeNull();
        authSingle.ProxyRequired.ShouldBeTrue();

        var (authUsers, usersError) = ServerOptions.ParseAuthorization(
            Map(
                ("users", Arr(
                    Map(("user", "user1"), ("password", "pwd1")),
                    Map(("user", "user2"), ("password", "pwd2"), ("proxy_required", true)),
                    Map(("nkey", "UCARKS2E3KVB7YORL2DG34XLT7PUCOL2SVM7YXV6ETHLW6Z46UUJ2VZ3"), ("proxy_required", true)),
                    Map(("nkey", "UD6AYQSOIN2IN5OGC6VQZCR4H3UFMIOXSW6NNS6N53CLJA4PB56CEJJI"), ("proxy_required", false))))));

        usersError.ShouldBeNull();
        authUsers.ShouldNotBeNull();
        authUsers.Users.Single(u => u.Username == "user2").ProxyRequired.ShouldBeTrue();
        authUsers.Users.Single(u => u.Username == "user1").ProxyRequired.ShouldBeFalse();
        authUsers.Nkeys.Single(n => n.Nkey.StartsWith("UCAR", StringComparison.Ordinal)).ProxyRequired.ShouldBeTrue();
    }

    [Fact] // T:2588
    public void WebsocketPingIntervalConfig_ShouldSucceed()
    {
        var opts = new ServerOptions();
        var errors = new List<Exception>();
        var warnings = new List<Exception>();

        var parseError = ServerOptions.ParseWebsocket(
            Map(("port", 8080L), ("ping_interval", "30s")),
            opts,
            errors,
            warnings);

        parseError.ShouldBeNull();
        errors.ShouldBeEmpty();
        opts.Websocket.PingInterval.ShouldBe(TimeSpan.FromSeconds(30));

        parseError = ServerOptions.ParseWebsocket(
            Map(("port", 8080L), ("ping_interval", 45L)),
            opts,
            errors,
            warnings);

        parseError.ShouldBeNull();
        opts.Websocket.PingInterval.ShouldBe(TimeSpan.FromSeconds(45));
    }

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

        var leafError = ServerOptions.ParseLeafNodes(
            new Dictionary<string, object?>
            {
                ["write_deadline"] = "5s",
            },
            options,
            errors,
            warnings);

        var gatewayError = ServerOptions.ParseGateway(
            new Dictionary<string, object?>
            {
                ["write_deadline"] = "6s",
            },
            options,
            errors,
            warnings);

        var clusterError = ServerOptions.ParseCluster(
            new Dictionary<string, object?>
            {
                ["write_deadline"] = "7s",
            },
            options,
            errors,
            warnings);

        options.WriteDeadline = ServerOptions.ParseDuration("write_deadline", "8s", errors, warnings);

        leafError.ShouldBeNull();
        gatewayError.ShouldBeNull();
        clusterError.ShouldBeNull();
        errors.ShouldBeEmpty();
        options.LeafNode.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(5));
        options.Gateway.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(6));
        options.Cluster.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(7));
        options.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(8));
    }

    [Fact] // T:2587
    public void WriteTimeoutConfigParsing_ShouldSucceed()
    {
        var expectedPolicies = new Dictionary<string, WriteTimeoutPolicy>(StringComparer.Ordinal)
        {
            ["default"] = WriteTimeoutPolicy.Default,
            ["retry"] = WriteTimeoutPolicy.Retry,
            ["close"] = WriteTimeoutPolicy.Close,
        };

        foreach (var (rawPolicy, expectedPolicy) in expectedPolicies)
        {
            var options = new ServerOptions();
            var errors = new List<Exception>();
            var warnings = new List<Exception>();

            var leafError = ServerOptions.ParseLeafNodes(
                new Dictionary<string, object?>
                {
                    ["write_timeout"] = rawPolicy,
                },
                options,
                errors,
                warnings);

            var gatewayError = ServerOptions.ParseGateway(
                new Dictionary<string, object?>
                {
                    ["write_timeout"] = rawPolicy,
                },
                options,
                errors,
                warnings);

            var clusterError = ServerOptions.ParseCluster(
                new Dictionary<string, object?>
                {
                    ["write_timeout"] = rawPolicy,
                },
                options,
                errors,
                warnings);

            options.WriteTimeout = ServerOptions.ParseWriteDeadlinePolicy(rawPolicy, errors);

            leafError.ShouldBeNull();
            gatewayError.ShouldBeNull();
            clusterError.ShouldBeNull();
            errors.ShouldBeEmpty();
            options.LeafNode.WriteTimeout.ShouldBe(expectedPolicy);
            options.Gateway.WriteTimeout.ShouldBe(expectedPolicy);
            options.Cluster.WriteTimeout.ShouldBe(expectedPolicy);
            options.WriteTimeout.ShouldBe(expectedPolicy);
        }
    }
}
