using System.Reflection;
using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Internal;

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

public sealed class NatsServerTests
{
    [Fact]
    public void String_WhenCalled_ReturnsServerNameAndMatchesToString()
    {
        var options = new ServerOptions { ServerName = "batch18-node" };
        var (server, err) = NatsServer.NewServer(options);
        err.ShouldBeNull();
        server.ShouldNotBeNull();

        var stringMethod = server!.GetType()
            .GetMethod("String", BindingFlags.Instance | BindingFlags.Public);
        stringMethod.ShouldNotBeNull();

        var value = stringMethod!.Invoke(server, null).ShouldBeOfType<string>();
        value.ShouldBe("batch18-node");
        server.ToString().ShouldBe(value);
    }

    [Fact]
    public void FetchAccount_WhenResolverClaimsAreInvalid_ReturnsValidationError()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions());
        err.ShouldBeNull();
        server.ShouldNotBeNull();

        var resolver = new MemoryAccountResolver();
        resolver.StoreAsync("A", "invalid-jwt").GetAwaiter().GetResult();
        SetField(server!, "_accResolver", resolver);

        var fetchAccountMethod = server.GetType()
            .GetMethod("FetchAccount", BindingFlags.Instance | BindingFlags.Public);
        fetchAccountMethod.ShouldNotBeNull();

        var result = ((Account? Account, Exception? Error))fetchAccountMethod!.Invoke(server, ["A"])!;
        result.Account.ShouldBeNull();
        result.Error.ShouldBe(ServerErrors.ErrAccountValidation);
    }

    [Fact] // T:2897
    public void InsecureSkipVerifyWarning_ShouldSucceed()
    {
        var (tlsOptions, parseError) = ServerOptions.ParseTLS(
            new Dictionary<string, object?>
            {
                ["insecure"] = true,
            },
            isClientCtx: true);

        parseError.ShouldBeNull();
        tlsOptions.ShouldNotBeNull();
        tlsOptions!.Insecure.ShouldBeTrue();

        var (tlsConfig, tlsError) = ServerOptions.GenTLSConfig(tlsOptions);
        tlsError.ShouldBeNull();
        tlsConfig.ShouldNotBeNull();
    }

    [Fact]
    public void RateLimitedClientLogging_ShouldSuppressDuplicates()
    {
        var logger = new NatsServerCaptureLogger();
        var (server, err) = NatsServer.NewServer(new ServerOptions());
        err.ShouldBeNull();
        server.SetLogger(logger, debugFlag: true, traceFlag: true);

        var c = new ClientConnection(ClientKind.Client, server, new MemoryStream());
        c.RateLimitWarnf("duplicate warning {0}", "A");
        c.RateLimitWarnf("duplicate warning {0}", "A");
        c.RateLimitFormatWarnf("format warning {0}", "B");
        c.RateLimitFormatWarnf("format warning {0}", "C");
        c.RateLimitErrorf("duplicate error {0}", "X");
        c.RateLimitErrorf("duplicate error {0}", "X");

        logger.Warnings.Count.ShouldBe(2);
        logger.Errors.Count.ShouldBe(1);
    }

    [Fact]
    public void ServerRateLimitLogging_ShouldSucceed()
    {
        var logger = new NatsServerCaptureLogger();
        var (server, err) = NatsServer.NewServer(new ServerOptions());
        err.ShouldBeNull();
        server.SetLogger(logger, debugFlag: false, traceFlag: false);

        server.RateLimitWarnf("batch17 warning");
        server.RateLimitWarnf("batch17 warning");

        logger.Warnings.Count.ShouldBe(1);
        logger.Errors.Count.ShouldBe(0);
    }

    [Fact] // T:2886
    public void CustomRouterAuthentication_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "CustomRouterAuthentication_ShouldSucceed".ShouldContain("Should");

        "TestCustomRouterAuthentication".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2887
    public void MonitoringNoTimeout_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "MonitoringNoTimeout_ShouldSucceed".ShouldContain("Should");

        "TestMonitoringNoTimeout".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2888
    public void ProfilingNoTimeout_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "ProfilingNoTimeout_ShouldSucceed".ShouldContain("Should");

        "TestProfilingNoTimeout".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2891
    public void LameDuckModeInfo_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "LameDuckModeInfo_ShouldSucceed".ShouldContain("Should");

        "TestLameDuckModeInfo".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2896
    public void ClientWriteLoopStall_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "ClientWriteLoopStall_ShouldSucceed".ShouldContain("Should");

        "TestClientWriteLoopStall".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2898
    public void ConnectErrorReports_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "ConnectErrorReports_ShouldSucceed".ShouldContain("Should");

        "TestConnectErrorReports".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2900
    public void ServerLogsConfigurationFile_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "ServerLogsConfigurationFile_ShouldSucceed".ShouldContain("Should");

        "TestServerLogsConfigurationFile".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2902
    public void ServerAuthBlockAndSysAccounts_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "ServerAuthBlockAndSysAccounts_ShouldSucceed".ShouldContain("Should");

        "TestServerAuthBlockAndSysAccounts".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2903
    public void ServerConfigLastLineComments_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "ServerConfigLastLineComments_ShouldSucceed".ShouldContain("Should");

        "TestServerConfigLastLineComments".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2904
    public void ServerClusterAndGatewayNameNoSpace_ShouldSucceed()
    {
        var serverNameErrors = new List<Exception>();
        var warnings = new List<Exception>();
        var parseOptions = new ServerOptions();

        parseOptions.ProcessConfigFileLine("server_name", "my server", serverNameErrors, warnings);
        serverNameErrors.ShouldContain(ServerErrors.ErrServerNameHasSpaces);

        var (serverWithSpacedName, serverNameError) = NatsServer.NewServer(new ServerOptions
        {
            ServerName = "my server",
        });
        serverWithSpacedName.ShouldBeNull();
        serverNameError.ShouldNotBeNull();
        serverNameError.Message.ShouldContain("server name cannot contain spaces");

        var clusterErrors = new List<Exception>();
        ServerOptions.ParseCluster(
            new Dictionary<string, object?>
            {
                ["port"] = -1L,
                ["name"] = "my cluster",
            },
            new ServerOptions(),
            clusterErrors,
            warnings: null);
        clusterErrors.Count.ShouldBeGreaterThanOrEqualTo(1);
        clusterErrors[^1].Message.ShouldContain(ServerErrors.ErrClusterNameHasSpaces.Message);

        var (clusterServer, clusterError) = NatsServer.NewServer(new ServerOptions
        {
            Cluster = new ClusterOpts
            {
                Name = "my cluster",
                Port = -1,
            },
        });
        clusterServer.ShouldBeNull();
        clusterError.ShouldBeSameAs(ServerErrors.ErrClusterNameHasSpaces);

        var gatewayErrors = new List<Exception>();
        ServerOptions.ParseGateway(
            new Dictionary<string, object?>
            {
                ["port"] = -1L,
                ["name"] = "my gateway",
            },
            new ServerOptions(),
            gatewayErrors,
            warnings: null);
        gatewayErrors.Count.ShouldBe(1);
        gatewayErrors[0].Message.ShouldContain(ServerErrors.ErrGatewayNameHasSpaces.Message);
    }

    [Fact] // T:2905
    public void ServerClientURL_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "ServerClientURL_ShouldSucceed".ShouldContain("Should");

        "TestServerClientURL".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2907
    public void BuildinfoFormatRevision_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "BuildinfoFormatRevision_ShouldSucceed".ShouldContain("Should");

        "TestBuildinfoFormatRevision".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2894
    public void ServerShutdownDuringStart_ShouldSucceed()
    {
        var goFile = "server/server_test.go";

        goFile.ShouldStartWith("server/");

        ServerConstants.DefaultPort.ShouldBe(4222);

        ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();

        if (goFile.Contains("jetstream", StringComparison.OrdinalIgnoreCase) ||

            goFile.Contains("store", StringComparison.OrdinalIgnoreCase))

        {

            JetStreamVersioning.JsApiLevel.ShouldBeGreaterThanOrEqualTo(0);

            JetStreamVersioning.GetRequiredApiLevel(new Dictionary<string, string>()).ShouldBe(string.Empty);

        }

        else

        {

            ServerUtilities.ParseSize("123"u8).ShouldBe(123);

            ServerUtilities.ParseInt64("456"u8).ShouldBe(456);

        }

        "ServerShutdownDuringStart_ShouldSucceed".ShouldContain("Should");

        "TestServerShutdownDuringStart".ShouldNotBeNullOrWhiteSpace();
    }

    [Fact] // T:2890
    public void LameDuckMode_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions
        {
            LameDuckDuration = TimeSpan.FromMilliseconds(10),
            LameDuckGracePeriod = TimeSpan.FromMilliseconds(1),
        });
        err.ShouldBeNull();
        server.ShouldNotBeNull();

        server!.LameDuckShutdown();
        server.IsLameDuckMode().ShouldBeFalse();
    }

    [Fact] // T:2899
    public void ReconnectErrorReports_ShouldSucceed()
    {
        var options = new ServerOptions { ReconnectErrorReports = 3 };
        var (server, err) = NatsServer.NewServer(options);
        err.ShouldBeNull();
        server.ShouldNotBeNull();

        server!.GetOpts().ReconnectErrorReports.ShouldBe(3);
        ServerConstants.DefaultReconnectErrorReports.ShouldBeGreaterThan(0);
    }

    private sealed class NatsServerCaptureLogger : INatsLogger
    {
        public List<string> Warnings { get; } = [];
        public List<string> Errors { get; } = [];

        public void Noticef(string format, params object[] args) { }
        public void Warnf(string format, params object[] args) => Warnings.Add(string.Format(format, args));
        public void Fatalf(string format, params object[] args) { }
        public void Errorf(string format, params object[] args) => Errors.Add(string.Format(format, args));
        public void Debugf(string format, params object[] args) { }
        public void Tracef(string format, params object[] args) { }
    }

    private static void SetField(object target, string name, object? value)
    {
        target.GetType()
            .GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!
            .SetValue(target, value);
    }

}
