using System.Net;
using System.Net.Sockets;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Protocol;
using NATS.Server.Routes;

namespace NATS.Server.Clustering.Tests.Routes;

public class RouteParityHelpersBatch1Tests
{
    [Fact]
    public void BuildConnectInfoJson_includes_connectinfo_compat_fields()
    {
        var json = RouteConnection.BuildConnectInfoJson("S1", ["A"], "topo-v1");

        json.ShouldContain("\"verbose\":false");
        json.ShouldContain("\"pedantic\":false");
        json.ShouldContain("\"echo\":false");
        json.ShouldContain("\"tls_required\":false");
        json.ShouldContain("\"headers\":true");
        json.ShouldContain("\"name\":\"S1\"");
        json.ShouldContain("\"cluster\":\"\"");
        json.ShouldContain("\"dynamic\":false");
        json.ShouldContain("\"lnoc\":false");
        json.ShouldContain("\"lnocu\":false");
    }

    [Fact]
    public void HasThisRouteConfigured_matches_explicit_routes_with_scheme_normalization()
    {
        var manager = CreateManager(new ClusterOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Routes = ["127.0.0.1:7222"],
        });

        manager.HasThisRouteConfigured("127.0.0.1:7222").ShouldBeTrue();
        manager.HasThisRouteConfigured("nats-route://127.0.0.1:7222").ShouldBeTrue();
        manager.HasThisRouteConfigured("nats://127.0.0.1:7222").ShouldBeTrue();
        manager.HasThisRouteConfigured("127.0.0.1:7999").ShouldBeFalse();
    }

    [Fact]
    public void ProcessImplicitRoute_skips_configured_routes_and_tracks_new_routes()
    {
        var manager = CreateManager(new ClusterOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Routes = ["127.0.0.1:7222"],
        });

        var serverInfo = new ServerInfo
        {
            ServerId = "S2",
            ServerName = "S2",
            Version = NatsProtocol.Version,
            Host = "127.0.0.1",
            Port = 7222,
            ConnectUrls = ["127.0.0.1:7222", "nats-route://127.0.0.1:7444"],
        };

        manager.ProcessImplicitRoute(serverInfo);

        manager.DiscoveredRoutes.ShouldNotContain("127.0.0.1:7222");
        manager.DiscoveredRoutes.ShouldContain("nats-route://127.0.0.1:7444");
    }

    [Fact]
    public void RouteStillValid_checks_configured_and_discovered_routes()
    {
        var manager = CreateManager(new ClusterOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Routes = ["127.0.0.1:7222"],
        });

        manager.RouteStillValid("nats://127.0.0.1:7222").ShouldBeTrue();
        manager.RouteStillValid("127.0.0.1:7555").ShouldBeFalse();

        manager.ProcessImplicitRoute(new ServerInfo
        {
            ServerId = "S2",
            ServerName = "S2",
            Version = NatsProtocol.Version,
            Host = "127.0.0.1",
            Port = 7444,
            ConnectUrls = ["127.0.0.1:7444"],
        });

        manager.RouteStillValid("nats-route://127.0.0.1:7444").ShouldBeTrue();
    }

    [Fact]
    public async Task Solicited_route_helpers_upgrade_and_query_status()
    {
        var manager = CreateManager();
        await using var connection = MakeRouteConnection();

        manager.RegisterRoute("S2", connection);
        manager.HasSolicitedRoute("S2").ShouldBeFalse();

        manager.UpgradeRouteToSolicited("S2").ShouldBeTrue();
        connection.IsSolicitedRoute().ShouldBeTrue();
        manager.HasSolicitedRoute("S2").ShouldBeTrue();
        manager.IsDuplicateServerName("S2").ShouldBeTrue();
    }

    [Fact]
    public async Task RemoveRoute_cleans_hash_and_account_route_indexes()
    {
        var manager = CreateManager();
        var connection = MakeRouteConnection();

        manager.RegisterRoute("S2", connection);
        manager.RegisterRouteByHash("S2", connection);
        manager.RegisterAccountRoute("A", connection);

        manager.HashedRouteCount.ShouldBe(1);
        manager.DedicatedRouteCount.ShouldBe(1);

        manager.RemoveRoute("S2").ShouldBeTrue();
        manager.HashedRouteCount.ShouldBe(0);
        manager.DedicatedRouteCount.ShouldBe(0);
    }

    [Fact]
    public async Task TryParseRemoteUnsub_parses_rs_minus_and_ls_minus()
    {
        RouteConnection.TryParseRemoteUnsub("RS- ACCT_A foo.bar q1", out var account1, out var subject1, out var queue1).ShouldBeTrue();
        account1.ShouldBe("ACCT_A");
        subject1.ShouldBe("foo.bar");
        queue1.ShouldBe("q1");

        RouteConnection.TryParseRemoteUnsub("LS- ACCT_B foo.>", out var account2, out var subject2, out var queue2).ShouldBeTrue();
        account2.ShouldBe("ACCT_B");
        subject2.ShouldBe("foo.>");
        queue2.ShouldBeNull();

        RouteConnection.TryParseRemoteUnsub("RS+ ACCT_A foo.bar", out _, out _, out _).ShouldBeFalse();
    }

    private static RouteManager CreateManager(ClusterOptions? options = null)
        => new(
            options ?? new ClusterOptions { Host = "127.0.0.1", Port = 0 },
            new ServerStats(),
            "S1",
            _ => { },
            _ => { },
            NullLogger<RouteManager>.Instance);

    private static RouteConnection MakeRouteConnection()
    {
        var listener = new TcpListener(IPAddress.Loopback, 0);
        listener.Start();
        var endpoint = (IPEndPoint)listener.LocalEndpoint;

        var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        client.Connect(endpoint);

        var server = listener.AcceptSocket();
        server.Dispose();
        listener.Stop();

        return new RouteConnection(client);
    }
}
