// Copyright 2012-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0

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

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

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

        var route = new ClientConnection(ClientKind.Router) { Cid = 42 };
        var routes = new Dictionary<string, List<ClientConnection>> { ["pool"] = [route] };
        var clients = new Dictionary<ulong, ClientConnection> { [route.Cid] = route };

        SetField(server!, "_routes", routes);
        SetField(server!, "_clients", clients);

        server.ForEachRoute(_ => { });

        InvokePrivate(server!, "RemoveRoute", route);
        ((Dictionary<string, List<ClientConnection>>)GetField(server!, "_routes")).Count.ShouldBe(0);
        ((Dictionary<ulong, ClientConnection>)GetField(server!, "_clients")).Count.ShouldBe(0);

        var nonce = new byte[16];
        InvokePrivate(server!, "GenerateNonce", nonce);
        nonce.Any(b => b != 0).ShouldBeTrue();

        var before = (DateTime)GetField(server!, "_configTime");
        server.Reload();
        var after = (DateTime)GetField(server!, "_configTime");
        after.ShouldBeGreaterThanOrEqualTo(before);
    }

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

        SetField(
            server!,
            "_routes",
            new Dictionary<string, List<ClientConnection>>
            {
                ["a"] = [],
                ["b"] = [],
                ["c"] = [],
            });

        var numRemotesInternal = server.GetType()
            .GetMethod("NumRemotesInternal", BindingFlags.Instance | BindingFlags.NonPublic);
        numRemotesInternal.ShouldNotBeNull();

        var count = (int)numRemotesInternal!.Invoke(server, null)!;
        count.ShouldBe(3);
        server.NumRemotes().ShouldBe(3);
    }

    [Fact]
    public void ReadyForConnectionsInternal_WhenRouteListenerErrorPresent_ReturnsDetailedFailure()
    {
        var opts = new ServerOptions
        {
            DontListen = true,
            Cluster = new ClusterOpts { Port = 6222 },
            LeafNode = new LeafNodeOpts { Port = 0 },
            Websocket = new WebsocketOpts { Port = 0 },
            Mqtt = new MqttOpts { Port = 0 },
        };
        var (server, err) = NatsServer.NewServer(opts);
        err.ShouldBeNull();
        server.ShouldNotBeNull();

        SetField(server!, "_routeListenerErr", new InvalidOperationException("route listener failed"));

        var readyForConnectionsInternal = server.GetType()
            .GetMethod("ReadyForConnectionsInternal", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        readyForConnectionsInternal.ShouldNotBeNull();

        var readyError = readyForConnectionsInternal!.Invoke(server, [TimeSpan.FromMilliseconds(40)]) as Exception;
        readyError.ShouldNotBeNull();
        readyError!.Message.ShouldContain("route(");
        readyError.Message.ShouldContain("route listener failed");
    }

    private static object GetField(object target, string name)
    {
        return target.GetType()
            .GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!
            .GetValue(target)!;
    }

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

    private static void InvokePrivate(object target, string name, params object[] args)
    {
        target.GetType()
            .GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic)!
            .Invoke(target, args);
    }
}
