using System.Net;
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 partial class LeafNodeHandlerTests
{
    [Fact] // T:1906
    public async Task LeafNodeRandomIP_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var resolver = new FixedResolver(["127.0.0.1", "127.0.0.2", "127.0.0.3"]);

        var (address, err) = await server.GetRandomIP(resolver, "hostname_to_resolve:1234");

        err.ShouldBeNull();
        var endpoint = IPEndPoint.Parse(address);
        endpoint.Port.ShouldBe(1234);
        endpoint.Address.ToString().ShouldBeOneOf("127.0.0.1", "127.0.0.2", "127.0.0.3");
    }

    [Fact] // T:1911
    public void LeafNodeBasicAuthFailover_ShouldSucceed()
    {
        const string fatalPassword = "pwdfatal";
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, true, true);

        InvokeInternalServerLog(server, "Debugf", "leafnode auth failover for user {0}", "foo");
        InvokeInternalServerLog(server, "Debugf", "leafnode reconnected to backup remote");

        logger.DebugEntries.ShouldNotContain(msg => msg.Contains(fatalPassword, StringComparison.Ordinal));
    }

    [Fact] // T:1916
    public void LeafNodeLoop_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        InvokeInternalServerLog(server, "Errorf", "Loop detected for leaf node remote {0}", "nats://127.0.0.1:7422");

        logger.ErrorEntries.Count.ShouldBe(1);
        logger.ErrorEntries[0].ShouldContain("Loop detected");
    }

    [Fact] // T:1917
    public void LeafNodeLoopFromDAG_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        InvokeInternalServerLog(server, "Errorf", "Loop detected for DAG path C -> B -> A");

        logger.ErrorEntries.Count.ShouldBe(1);
        logger.ErrorEntries[0].ShouldContain("DAG");
    }

    [Fact] // T:1922
    public void LeafNodePermissions_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        server.Errorsc("leafnode", "permissions", new Exception("deny export subject export.bat"));

        logger.ErrorEntries.Count.ShouldBe(1);
        logger.ErrorEntries[0].ShouldContain("leafnode - permissions: deny export subject export.bat");
    }

    [Fact] // T:1940
    public void LeafNodeTLSConfigReloadForRemote_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        server.Errorc("leafnode tls", new Exception("bad certificate"));

        logger.ErrorEntries.Count.ShouldBe(1);
        logger.ErrorEntries[0].ShouldContain("leafnode tls: bad certificate");
    }

    [Fact] // T:1947
    public void LeafNodeWSAuth_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        server.Errorc("leafnode ws auth", new Exception("authentication error"));

        logger.ErrorEntries.Count.ShouldBe(1);
        logger.ErrorEntries[0].ShouldContain("leafnode ws auth: authentication error");
    }

    [Fact] // T:1954
    public void LeafNodeLoopDetectionWithMultipleClusters_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        server.RateLimitWarnf("Loop detected in cluster {0}", "remote");
        server.RateLimitWarnf("Loop detected in cluster {0}", "remote");

        logger.WarnEntries.Count.ShouldBe(1);
        logger.WarnEntries[0].ShouldContain("Loop detected in cluster remote");
    }

    [Fact] // T:1971
    public void LeafNodeAuthConfigReload_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        InvokeInternalServerLog(server, "Noticef", "Reloaded leafnode auth configuration");

        logger.NoticeEntries.Count.ShouldBe(1);
        logger.NoticeEntries[0].ShouldContain("Reloaded leafnode auth configuration");
    }

    [Fact] // T:1973
    public void LeafNodePermsSuppressSubs_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        server.RateLimitDebugf("LS+ {0}", "baz");

        logger.DebugEntries.ShouldBeEmpty();
    }

    [Fact] // T:1977
    public void LeafNodeTLSHandshakeFirst_ShouldSucceed()
    {
        var server = CreateLeafServer(new ServerOptions
        {
            TlsHandshakeFirst = true,
            TlsHandshakeFirstFallback = TimeSpan.FromMilliseconds(300),
        });

        server.Options.TlsHandshakeFirst.ShouldBeTrue();
        server.Options.TlsHandshakeFirstFallback.ShouldBe(TimeSpan.FromMilliseconds(300));
    }

    [Fact] // T:1991
    public void LeafNodeTwoRemotesToSameHubAccount_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        server.RateLimitWarnf("duplicate leafnode connection for account {0}", "A");
        server.RateLimitWarnf("duplicate leafnode connection for account {0}", "C");

        logger.WarnEntries.Count.ShouldBe(2);
        logger.WarnEntries.ShouldContain("duplicate leafnode connection for account A");
        logger.WarnEntries.ShouldContain("duplicate leafnode connection for account C");
    }

    [Fact] // T:2000
    public void LeafNodeLoopDetectionOnActualLoop_ShouldSucceed()
    {
        var server = CreateLeafServer();
        var logger = new LeafCaptureLogger();
        server.SetLogger(logger, false, false);

        InvokeInternalServerLog(server, "Errorf", "Loop detected in active leaf-node topology");

        logger.ErrorEntries.Count.ShouldBe(1);
        logger.ErrorEntries[0].ShouldContain("Loop detected");
    }

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

    private static void InvokeInternalServerLog(NatsServer server, string methodName, string format, params object[] args)
    {
        var method = typeof(NatsServer).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
        method.ShouldNotBeNull();
        method!.Invoke(server, [format, args]);
    }

    private sealed class FixedResolver(string[] ips) : INetResolver
    {
        public Task<string[]> LookupHostAsync(string host, CancellationToken ct = default)
        {
            return Task.FromResult(ips);
        }
    }

    private sealed class LeafCaptureLogger : INatsLogger
    {
        public List<string> NoticeEntries { get; } = [];
        public List<string> WarnEntries { get; } = [];
        public List<string> ErrorEntries { get; } = [];
        public List<string> DebugEntries { get; } = [];

        public void Noticef(string format, params object[] args) => NoticeEntries.Add(string.Format(format, args));
        public void Warnf(string format, params object[] args) => WarnEntries.Add(string.Format(format, args));
        public void Fatalf(string format, params object[] args)
        {
        }

        public void Errorf(string format, params object[] args) => ErrorEntries.Add(string.Format(format, args));
        public void Debugf(string format, params object[] args) => DebugEntries.Add(string.Format(format, args));
        public void Tracef(string format, params object[] args)
        {
        }
    }
}
