using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server;
using NATS.Server.Gateways;
using NATS.Server.Protocol;
using NATS.Server.Routes;

namespace NATS.Server.Clustering.Tests;

// Go reference: server/route.go processImplicitRoute, server/gateway.go processImplicitGateway

public class ImplicitDiscoveryTests
{
    [Fact]
    public void ProcessImplicitRoute_discovers_new_peer()
    {
        // Go reference: server/route.go processImplicitRoute
        var mgr = RouteManagerTestHelper.Create();
        var serverInfo = new ServerInfo
        {
            ServerId = "server-2",
            ServerName = "server-2",
            Version = "0.1.0",
            Host = "0.0.0.0",
            Port = 4222,
            ConnectUrls = ["nats://10.0.0.2:6222", "nats://10.0.0.3:6222"],
        };

        mgr.ProcessImplicitRoute(serverInfo);

        mgr.DiscoveredRoutes.ShouldContain("nats://10.0.0.2:6222");
        mgr.DiscoveredRoutes.ShouldContain("nats://10.0.0.3:6222");
    }

    [Fact]
    public void ProcessImplicitRoute_skips_known_peers()
    {
        // Go reference: server/route.go processImplicitRoute — skip already-known
        var mgr = RouteManagerTestHelper.Create();
        mgr.AddKnownRoute("nats://10.0.0.2:6222");

        var serverInfo = new ServerInfo
        {
            ServerId = "server-2",
            ServerName = "server-2",
            Version = "0.1.0",
            Host = "0.0.0.0",
            Port = 4222,
            ConnectUrls = ["nats://10.0.0.2:6222", "nats://10.0.0.3:6222"],
        };

        mgr.ProcessImplicitRoute(serverInfo);

        mgr.DiscoveredRoutes.Count.ShouldBe(1); // only 10.0.0.3 is new
        mgr.DiscoveredRoutes.ShouldContain("nats://10.0.0.3:6222");
    }

    [Fact]
    public void ProcessImplicitRoute_with_null_urls_is_noop()
    {
        // Go reference: server/route.go processImplicitRoute — nil ConnectUrls guard
        var mgr = RouteManagerTestHelper.Create();
        var serverInfo = new ServerInfo
        {
            ServerId = "server-2",
            ServerName = "server-2",
            Version = "0.1.0",
            Host = "0.0.0.0",
            Port = 4222,
            ConnectUrls = null,
        };

        mgr.ProcessImplicitRoute(serverInfo);

        mgr.DiscoveredRoutes.Count.ShouldBe(0);
    }

    [Fact]
    public void ProcessImplicitRoute_with_empty_urls_is_noop()
    {
        // Go reference: server/route.go processImplicitRoute — empty ConnectUrls guard
        var mgr = RouteManagerTestHelper.Create();
        var serverInfo = new ServerInfo
        {
            ServerId = "server-2",
            ServerName = "server-2",
            Version = "0.1.0",
            Host = "0.0.0.0",
            Port = 4222,
            ConnectUrls = [],
        };

        mgr.ProcessImplicitRoute(serverInfo);

        mgr.DiscoveredRoutes.Count.ShouldBe(0);
    }

    [Fact]
    public void ProcessImplicitGateway_discovers_new_gateway()
    {
        // Go reference: server/gateway.go processImplicitGateway
        var mgr = GatewayManagerTestHelper.Create();
        var gwInfo = new GatewayInfo
        {
            Name = "cluster-B",
            Urls = ["nats://10.0.1.1:7222"],
        };

        mgr.ProcessImplicitGateway(gwInfo);

        mgr.DiscoveredGateways.ShouldContain("cluster-B");
    }

    [Fact]
    public void ProcessImplicitGateway_with_null_throws()
    {
        // Go reference: server/gateway.go processImplicitGateway — null guard
        var mgr = GatewayManagerTestHelper.Create();

        Should.Throw<ArgumentNullException>(() => mgr.ProcessImplicitGateway(null!));
    }

    [Fact]
    public void ProcessImplicitGateway_deduplicates_same_cluster()
    {
        // Go reference: server/gateway.go processImplicitGateway — idempotent discovery
        var mgr = GatewayManagerTestHelper.Create();
        var gwInfo = new GatewayInfo { Name = "cluster-B", Urls = ["nats://10.0.1.1:7222"] };

        mgr.ProcessImplicitGateway(gwInfo);
        mgr.ProcessImplicitGateway(gwInfo);

        mgr.DiscoveredGateways.Count.ShouldBe(1);
    }

    [Fact]
    public void ForwardNewRouteInfo_invokes_event()
    {
        // Go reference: server/route.go forwardNewRouteInfoToKnownServers
        var mgr = RouteManagerTestHelper.Create();
        var forwarded = new List<string>();
        mgr.OnForwardInfo += urls => forwarded.AddRange(urls);

        mgr.ForwardNewRouteInfoToKnownServers("nats://10.0.0.5:6222");

        forwarded.ShouldContain("nats://10.0.0.5:6222");
    }

    [Fact]
    public void ForwardNewRouteInfo_with_no_handler_does_not_throw()
    {
        // Go reference: server/route.go forwardNewRouteInfoToKnownServers — no subscribers
        var mgr = RouteManagerTestHelper.Create();

        Should.NotThrow(() => mgr.ForwardNewRouteInfoToKnownServers("nats://10.0.0.5:6222"));
    }

    [Fact]
    public void AddKnownRoute_prevents_later_discovery()
    {
        // Go reference: server/route.go processImplicitRoute — pre-seeded known routes
        var mgr = RouteManagerTestHelper.Create();
        mgr.AddKnownRoute("nats://10.0.0.9:6222");

        var serverInfo = new ServerInfo
        {
            ServerId = "server-3",
            ServerName = "server-3",
            Version = "0.1.0",
            Host = "0.0.0.0",
            Port = 4222,
            ConnectUrls = ["nats://10.0.0.9:6222"],
        };

        mgr.ProcessImplicitRoute(serverInfo);

        mgr.DiscoveredRoutes.Count.ShouldBe(0);
    }

    [Fact]
    public void ConnectUrls_is_serialized_when_set()
    {
        // Go reference: server/route.go INFO message includes connect_urls
        var info = new ServerInfo
        {
            ServerId = "s1",
            ServerName = "s1",
            Version = "0.1.0",
            Host = "0.0.0.0",
            Port = 4222,
            ConnectUrls = ["nats://10.0.0.1:4222"],
        };

        var json = System.Text.Json.JsonSerializer.Serialize(info);
        json.ShouldContain("connect_urls");
        json.ShouldContain("nats://10.0.0.1:4222");
    }

    [Fact]
    public void ConnectUrls_is_omitted_when_null()
    {
        // Go reference: server/route.go INFO omits connect_urls when empty
        var info = new ServerInfo
        {
            ServerId = "s1",
            ServerName = "s1",
            Version = "0.1.0",
            Host = "0.0.0.0",
            Port = 4222,
            ConnectUrls = null,
        };

        var json = System.Text.Json.JsonSerializer.Serialize(info);
        json.ShouldNotContain("connect_urls");
    }
}

public static class RouteManagerTestHelper
{
    public static RouteManager Create()
    {
        var options = new ClusterOptions { Name = "test-cluster", Host = "127.0.0.1", Port = 0 };
        var stats = new ServerStats();
        return new RouteManager(options, stats, "server-1", _ => { }, _ => { }, NullLogger<RouteManager>.Instance);
    }
}

public static class GatewayManagerTestHelper
{
    public static GatewayManager Create()
    {
        var options = new GatewayOptions { Name = "cluster-A", Host = "127.0.0.1", Port = 0 };
        var stats = new ServerStats();
        return new GatewayManager(options, stats, "server-1", _ => { }, _ => { }, NullLogger<GatewayManager>.Instance);
    }
}
