// Go: TestMonitorConnz server/monitor_test.go:367
// Go: TestMonitorConnzWithSubs server/monitor_test.go:442
// Go: TestMonitorConnzWithSubsDetail server/monitor_test.go:463
// Go: TestMonitorClosedConnzWithSubsDetail server/monitor_test.go:484
// Go: TestMonitorConnzRTT server/monitor_test.go:583
// Go: TestMonitorConnzLastActivity server/monitor_test.go:638
// Go: TestMonitorConnzWithOffsetAndLimit server/monitor_test.go:732
// Go: TestMonitorConnzDefaultSorted server/monitor_test.go:806
// Go: TestMonitorConnzSortedByCid server/monitor_test.go:827
// Go: TestMonitorConnzSortedByStart server/monitor_test.go:849
// Go: TestMonitorConnzSortedByBytesAndMsgs server/monitor_test.go:871
// Go: TestMonitorConnzSortedByPending server/monitor_test.go:925
// Go: TestMonitorConnzSortedBySubs server/monitor_test.go:950
// Go: TestMonitorConnzSortedByLast server/monitor_test.go:976
// Go: TestMonitorConnzSortedByUptime server/monitor_test.go:1007
// Go: TestMonitorConnzSortedByIdle server/monitor_test.go:1202
// Go: TestMonitorConnzSortedByStopOnOpen server/monitor_test.go:1074
// Go: TestMonitorConnzSortedByReason server/monitor_test.go:1141
// Go: TestMonitorConnzWithNamedClient server/monitor_test.go:1851
// Go: TestMonitorConnzWithStateForClosedConns server/monitor_test.go:1876
// Go: TestMonitorConcurrentMonitoring server/monitor_test.go:2148
// Go: TestMonitorConnzSortByRTT server/monitor_test.go:5979

using System.Net;
using System.Net.Http.Json;
using System.Net.Sockets;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Monitoring;

namespace NATS.Server.Tests.Monitoring;

/// <summary>
/// Tests covering /connz endpoint behavior, ported from the Go server's monitor_test.go.
/// </summary>
public class MonitorConnzTests : IAsyncLifetime
{
    private readonly NatsServer _server;
    private readonly int _natsPort;
    private readonly int _monitorPort;
    private readonly CancellationTokenSource _cts = new();
    private readonly HttpClient _http = new();

    public MonitorConnzTests()
    {
        _natsPort = GetFreePort();
        _monitorPort = GetFreePort();
        _server = new NatsServer(
            new NatsOptions { Port = _natsPort, MonitorPort = _monitorPort },
            NullLoggerFactory.Instance);
    }

    public async Task InitializeAsync()
    {
        _ = _server.StartAsync(_cts.Token);
        await _server.WaitForReadyAsync();
        for (var i = 0; i < 50; i++)
        {
            try
            {
                var probe = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/healthz");
                if (probe.IsSuccessStatusCode) break;
            }
            catch (HttpRequestException) { }
            await Task.Delay(50);
        }
    }

    public async Task DisposeAsync()
    {
        _http.Dispose();
        await _cts.CancelAsync();
        _server.Dispose();
    }

    /// <summary>
    /// Go: TestMonitorConnz (line 367).
    /// Verifies /connz returns empty connections when no clients are connected.
    /// </summary>
    [Fact]
    public async Task Connz_returns_empty_when_no_clients()
    {
        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
        connz.ShouldNotBeNull();
        connz.NumConns.ShouldBe(0);
        connz.Total.ShouldBe(0);
        connz.Conns.Length.ShouldBe(0);
    }

    /// <summary>
    /// Go: TestMonitorConnz (line 367).
    /// Verifies /connz lists active connections with populated identity fields.
    /// </summary>
    [Fact]
    public async Task Connz_lists_active_connections_with_fields()
    {
        using var sock = await ConnectClientAsync("{\"name\":\"c1\",\"lang\":\"csharp\",\"version\":\"1.0\"}", "SUB foo 1\r\nPUB foo 5\r\nhello\r\n");
        await Task.Delay(200);

        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
        connz.ShouldNotBeNull();
        connz.NumConns.ShouldBe(1);
        connz.Total.ShouldBe(1);
        connz.Conns.Length.ShouldBe(1);

        var ci = connz.Conns[0];
        // Go: ci.IP == "127.0.0.1"
        ci.Ip.ShouldBe("127.0.0.1");
        ci.Port.ShouldBeGreaterThan(0);
        ci.Cid.ShouldBeGreaterThan(0UL);
        ci.Name.ShouldBe("c1");
        ci.Lang.ShouldBe("csharp");
        ci.Version.ShouldBe("1.0");
        ci.Start.ShouldBeGreaterThan(DateTime.MinValue);
        ci.LastActivity.ShouldBeGreaterThanOrEqualTo(ci.Start);
        ci.Uptime.ShouldNotBeNullOrEmpty();
        ci.Idle.ShouldNotBeNullOrEmpty();
    }

    /// <summary>
    /// Go: TestMonitorConnz (line 367).
    /// Verifies /connz default limit is 1024 and offset is 0.
    /// </summary>
    [Fact]
    public async Task Connz_default_limit_and_offset()
    {
        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
        connz.ShouldNotBeNull();
        connz.Limit.ShouldBe(1024); // Go: DefaultConnListSize
        connz.Offset.ShouldBe(0);
    }

    /// <summary>
    /// Go: TestMonitorConnzWithSubs (line 442).
    /// Verifies /connz?subs=1 includes subscriptions list.
    /// </summary>
    [Fact]
    public async Task Connz_with_subs_includes_subscription_list()
    {
        using var sock = await ConnectClientAsync("{}", "SUB hello.foo 1\r\n");
        await Task.Delay(200);

        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?subs=1");
        connz.ShouldNotBeNull();
        connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(1);

        var ci = connz.Conns[0];
        // Go: len(ci.Subs) != 1 || ci.Subs[0] != "hello.foo"
        ci.Subs.ShouldContain("hello.foo");
    }

    /// <summary>
    /// Go: TestMonitorConnzWithSubsDetail (line 463).
    /// Verifies /connz?subs=detail includes subscription detail objects.
    /// </summary>
    [Fact]
    public async Task Connz_with_subs_detail_includes_subscription_detail()
    {
        using var sock = await ConnectClientAsync("{}", "SUB hello.foo 1\r\n");
        await Task.Delay(200);

        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?subs=detail");
        connz.ShouldNotBeNull();
        connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(1);

        var ci = connz.Conns[0];
        // Go: len(ci.SubsDetail) != 1 || ci.SubsDetail[0].Subject != "hello.foo"
        ci.SubsDetail.Length.ShouldBeGreaterThanOrEqualTo(1);
        ci.SubsDetail.ShouldContain(sd => sd.Subject == "hello.foo");
    }

    /// <summary>
    /// Go: TestMonitorConnzWithNamedClient (line 1851).
    /// Verifies /connz exposes client name set in CONNECT options.
    /// </summary>
    [Fact]
    public async Task Connz_shows_named_client()
    {
        using var sock = await ConnectClientAsync("{\"name\":\"test-client\"}");
        await Task.Delay(200);

        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
        connz.ShouldNotBeNull();
        connz.Conns.Length.ShouldBe(1);
        connz.Conns[0].Name.ShouldBe("test-client");
    }

    /// <summary>
    /// Go: TestMonitorConnzWithOffsetAndLimit (line 732).
    /// Verifies /connz pagination with offset and limit parameters.
    /// </summary>
    [Fact]
    public async Task Connz_pagination_with_offset_and_limit()
    {
        var sockets = new List<Socket>();
        try
        {
            for (var i = 0; i < 3; i++)
                sockets.Add(await ConnectClientAsync("{}"));

            await Task.Delay(200);

            // offset=1, limit=1 should return 1 connection with total of 3
            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?offset=1&limit=1");
            connz.ShouldNotBeNull();
            connz.Limit.ShouldBe(1);
            connz.Offset.ShouldBe(1);
            connz.Conns.Length.ShouldBe(1);
            connz.NumConns.ShouldBe(1);
            connz.Total.ShouldBeGreaterThanOrEqualTo(3);

            // offset past end should return 0
            var connz2 = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?offset=10&limit=1");
            connz2.ShouldNotBeNull();
            connz2.Conns.Length.ShouldBe(0);
            connz2.NumConns.ShouldBe(0);
            connz2.Total.ShouldBeGreaterThanOrEqualTo(3);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzDefaultSorted (line 806).
    /// Verifies /connz defaults to ascending CID sort order.
    /// </summary>
    [Fact]
    public async Task Connz_default_sorted_by_cid_ascending()
    {
        var sockets = new List<Socket>();
        try
        {
            for (var i = 0; i < 4; i++)
                sockets.Add(await ConnectClientAsync("{}"));

            await Task.Delay(200);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(4);

            // Go: Conns[0].Cid < Conns[1].Cid < Conns[2].Cid < Conns[3].Cid
            for (var i = 1; i < connz.Conns.Length; i++)
                connz.Conns[i].Cid.ShouldBeGreaterThan(connz.Conns[i - 1].Cid);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByCid (line 827).
    /// Verifies /connz?sort=cid returns connections sorted by CID.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_cid()
    {
        var sockets = new List<Socket>();
        try
        {
            for (var i = 0; i < 4; i++)
                sockets.Add(await ConnectClientAsync("{}"));

            await Task.Delay(200);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=cid");
            connz.ShouldNotBeNull();
            for (var i = 1; i < connz.Conns.Length; i++)
                connz.Conns[i].Cid.ShouldBeGreaterThan(connz.Conns[i - 1].Cid);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByStart (line 849).
    /// Verifies /connz?sort=start returns connections sorted by start time.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_start()
    {
        var sockets = new List<Socket>();
        try
        {
            for (var i = 0; i < 3; i++)
            {
                sockets.Add(await ConnectClientAsync("{}"));
                await Task.Delay(10);
            }

            await Task.Delay(200);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=start");
            connz.ShouldNotBeNull();
            for (var i = 1; i < connz.Conns.Length; i++)
                connz.Conns[i].Start.ShouldBeGreaterThanOrEqualTo(connz.Conns[i - 1].Start);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
    /// Verifies /connz?sort=bytes_to returns connections sorted by out_bytes descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_bytes_to()
    {
        var sockets = new List<Socket>();
        try
        {
            // Subscriber first
            sockets.Add(await ConnectClientAsync("{}", "SUB foo 1\r\n"));

            // High-traffic publisher
            var pub = await ConnectClientAsync("{}");
            sockets.Add(pub);
            using var ns = new NetworkStream(pub);
            for (var i = 0; i < 50; i++)
                await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
            await ns.FlushAsync();

            // Low-traffic client
            sockets.Add(await ConnectClientAsync("{}"));

            await Task.Delay(300);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=bytes_to");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);

            // First entry should have >= out_bytes than second
            connz.Conns[0].OutBytes.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].OutBytes);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
    /// Verifies /connz?sort=msgs_to returns connections sorted by out_msgs descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_msgs_to()
    {
        var sockets = new List<Socket>();
        try
        {
            sockets.Add(await ConnectClientAsync("{}", "SUB foo 1\r\n"));

            var pub = await ConnectClientAsync("{}");
            sockets.Add(pub);
            using var ns = new NetworkStream(pub);
            for (var i = 0; i < 50; i++)
                await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
            await ns.FlushAsync();

            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(300);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=msgs_to");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
            connz.Conns[0].OutMsgs.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].OutMsgs);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
    /// Verifies /connz?sort=msgs_from returns connections sorted by in_msgs descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_msgs_from()
    {
        var sockets = new List<Socket>();
        try
        {
            var pub = await ConnectClientAsync("{}");
            sockets.Add(pub);
            using var ns = new NetworkStream(pub);
            for (var i = 0; i < 50; i++)
                await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
            await ns.FlushAsync();

            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(300);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=msgs_from");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
            connz.Conns[0].InMsgs.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].InMsgs);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedBySubs (line 950).
    /// Verifies /connz?sort=subs returns connections sorted by subscription count descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_subs()
    {
        var sockets = new List<Socket>();
        try
        {
            // Client with many subs
            sockets.Add(await ConnectClientAsync("{}", "SUB a 1\r\nSUB b 2\r\nSUB c 3\r\n"));
            // Client with no subs
            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(200);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=subs");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
            connz.Conns[0].NumSubs.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].NumSubs);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByLast (line 976).
    /// Verifies /connz?sort=last returns connections sorted by last_activity descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_last_activity()
    {
        var sockets = new List<Socket>();
        try
        {
            // First client connects and does something early
            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(50);

            // Second client connects later and does activity
            sockets.Add(await ConnectClientAsync("{}", "PUB foo 2\r\nhi\r\n"));
            await Task.Delay(200);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=last");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
            connz.Conns[0].LastActivity.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].LastActivity);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByUptime (line 1007).
    /// Verifies /connz?sort=uptime returns connections sorted by uptime descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_uptime()
    {
        var sockets = new List<Socket>();
        try
        {
            // First client has longer uptime
            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(100);
            // Second client has shorter uptime
            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(200);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=uptime");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
            // Descending by uptime means first entry started earlier
            connz.Conns[0].Start.ShouldBeLessThanOrEqualTo(connz.Conns[1].Start);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByIdle (line 1202).
    /// Verifies /connz?sort=idle returns connections sorted by idle time descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_idle()
    {
        var sockets = new List<Socket>();
        try
        {
            // First client: older activity (more idle)
            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(200);

            // Second client: recent activity (less idle)
            sockets.Add(await ConnectClientAsync("{}", "PUB foo 2\r\nhi\r\n"));
            await Task.Delay(200);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=idle");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
            // Idle descending: first entry has older last activity
            connz.Conns[0].LastActivity.ShouldBeLessThanOrEqualTo(connz.Conns[1].LastActivity);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzWithStateForClosedConns (line 1876).
    /// Verifies /connz?state=closed returns recently disconnected clients.
    /// </summary>
    [Fact]
    public async Task Connz_state_closed_returns_disconnected_clients()
    {
        var sock = await ConnectClientAsync("{\"name\":\"closing-client\"}");
        await Task.Delay(200);
        sock.Shutdown(SocketShutdown.Both);
        sock.Dispose();
        await Task.Delay(500);

        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?state=closed");
        connz.ShouldNotBeNull();
        connz.Conns.ShouldContain(c => c.Name == "closing-client");
        var closed = connz.Conns.First(c => c.Name == "closing-client");
        closed.Stop.ShouldNotBeNull();
        closed.Reason.ShouldNotBeNullOrEmpty();
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByStopOnOpen (line 1074).
    /// Verifies /connz?sort=stop&state=open falls back to CID sort without error.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_stop_with_open_state_falls_back_to_cid()
    {
        using var sock = await ConnectClientAsync("{}");
        await Task.Delay(200);

        // Go: sort by stop on open state should fallback
        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=stop&state=open");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByReason (line 1141).
    /// Verifies /connz?sort=reason&state=closed sorts by close reason.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_reason_on_closed()
    {
        var sock = await ConnectClientAsync("{}");
        await Task.Delay(100);
        sock.Shutdown(SocketShutdown.Both);
        sock.Dispose();
        await Task.Delay(500);

        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=reason&state=closed");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByReasonOnOpen (line 1180).
    /// Verifies /connz?sort=reason&state=open falls back to CID sort without error.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_reason_with_open_state_falls_back()
    {
        using var sock = await ConnectClientAsync("{}");
        await Task.Delay(200);

        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=reason&state=open");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);
    }

    /// <summary>
    /// Go: TestMonitorConnzSortByRTT (line 5979).
    /// Verifies /connz?sort=rtt does not error.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_rtt_succeeds()
    {
        using var sock = await ConnectClientAsync("{}");
        await Task.Delay(200);

        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=rtt");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);
    }

    /// <summary>
    /// Go: TestMonitorConnz (line 367).
    /// Verifies /connz per-connection message stats are populated after pub/sub.
    /// </summary>
    [Fact]
    public async Task Connz_per_connection_message_stats()
    {
        using var sock = await ConnectClientAsync("{}", "SUB foo 1\r\nPUB foo 5\r\nhello\r\n");
        await Task.Delay(200);

        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
        connz.ShouldNotBeNull();
        connz.Conns.Length.ShouldBe(1);

        var ci = connz.Conns[0];
        // Go: ci.InMsgs == 1, ci.InBytes == 5
        ci.InMsgs.ShouldBeGreaterThanOrEqualTo(1L);
        ci.InBytes.ShouldBeGreaterThanOrEqualTo(5L);
    }

    /// <summary>
    /// Go: TestMonitorConnzRTT (line 583).
    /// Verifies /connz includes RTT field for connected clients.
    /// </summary>
    [Fact]
    public async Task Connz_includes_rtt_field()
    {
        using var sock = await ConnectClientAsync("{}");
        // Send a PING to trigger RTT measurement
        using var ns = new NetworkStream(sock);
        await ns.WriteAsync("PING\r\n"u8.ToArray());
        await Task.Delay(200);

        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
        connz.ShouldNotBeNull();
        connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(1);
        // RTT may or may not be populated depending on implementation, but field must exist
        connz.Conns[0].Rtt.ShouldNotBeNull();
    }

    /// <summary>
    /// Go: TestMonitorConnzLastActivity (line 638).
    /// Verifies /connz last_activity is updated after message activity.
    /// </summary>
    [Fact]
    public async Task Connz_last_activity_updates_after_message()
    {
        using var sock = await ConnectClientAsync("{}");
        await Task.Delay(100);

        // Record initial last activity
        var connz1 = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
        var initial = connz1!.Conns[0].LastActivity;

        // Do more activity
        using var ns = new NetworkStream(sock);
        await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
        await ns.FlushAsync();
        await Task.Delay(200);

        var connz2 = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");
        var updated = connz2!.Conns[0].LastActivity;

        // Activity should have updated
        updated.ShouldBeGreaterThanOrEqualTo(initial);
    }

    /// <summary>
    /// Go: TestMonitorConcurrentMonitoring (line 2148).
    /// Verifies concurrent /connz requests do not cause errors.
    /// </summary>
    [Fact]
    public async Task Connz_handles_concurrent_requests()
    {
        using var sock = await ConnectClientAsync("{}");
        await Task.Delay(200);

        var tasks = Enumerable.Range(0, 10).Select(async _ =>
        {
            var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz");
            response.StatusCode.ShouldBe(HttpStatusCode.OK);
        });

        await Task.WhenAll(tasks);
    }

    /// <summary>
    /// Go: TestMonitorConnz (line 367).
    /// Verifies /connz JSON uses correct Go-compatible field names.
    /// </summary>
    [Fact]
    public async Task Connz_json_uses_go_field_names()
    {
        using var sock = await ConnectClientAsync("{}");
        await Task.Delay(200);

        var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/connz");
        body.ShouldContain("\"server_id\"");
        body.ShouldContain("\"num_connections\"");
        body.ShouldContain("\"connections\"");
    }

    /// <summary>
    /// Go: TestMonitorConnzWithStateForClosedConns (line 1876).
    /// Verifies /connz?state=all returns both open and closed connections.
    /// </summary>
    [Fact]
    public async Task Connz_state_all_returns_both_open_and_closed()
    {
        // Connect and disconnect one client
        var sock = await ConnectClientAsync("{\"name\":\"will-close\"}");
        await Task.Delay(100);
        sock.Shutdown(SocketShutdown.Both);
        sock.Dispose();
        await Task.Delay(300);

        // Connect another client that stays open
        using var sock2 = await ConnectClientAsync("{\"name\":\"stays-open\"}");
        await Task.Delay(200);

        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?state=all");
        connz.ShouldNotBeNull();
        connz.Total.ShouldBeGreaterThanOrEqualTo(2);
    }

    /// <summary>
    /// Go: TestMonitorConnz (line 367).
    /// Verifies /connz server_id matches the server's ID.
    /// </summary>
    [Fact]
    public async Task Connz_server_id_matches_server()
    {
        var varz = await _http.GetFromJsonAsync<Varz>($"http://127.0.0.1:{_monitorPort}/varz");
        var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz");

        connz!.Id.ShouldBe(varz!.Id);
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByPending (line 925).
    /// Verifies /connz?sort=pending returns connections sorted by pending bytes descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_pending()
    {
        var sockets = new List<Socket>();
        try
        {
            sockets.Add(await ConnectClientAsync("{}"));
            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(200);

            var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz?sort=pending");
            response.StatusCode.ShouldBe(HttpStatusCode.OK);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Go: TestMonitorConnzSortedByBytesAndMsgs (line 871).
    /// Verifies /connz?sort=bytes_from returns connections sorted by in_bytes descending.
    /// </summary>
    [Fact]
    public async Task Connz_sort_by_bytes_from()
    {
        var sockets = new List<Socket>();
        try
        {
            // High-traffic publisher
            var pub = await ConnectClientAsync("{}");
            sockets.Add(pub);
            using var ns = new NetworkStream(pub);
            for (var i = 0; i < 50; i++)
                await ns.WriteAsync("PUB foo 5\r\nhello\r\n"u8.ToArray());
            await ns.FlushAsync();

            // Low-traffic client
            sockets.Add(await ConnectClientAsync("{}"));
            await Task.Delay(300);

            var connz = await _http.GetFromJsonAsync<Connz>($"http://127.0.0.1:{_monitorPort}/connz?sort=bytes_from");
            connz.ShouldNotBeNull();
            connz.Conns.Length.ShouldBeGreaterThanOrEqualTo(2);
            connz.Conns[0].InBytes.ShouldBeGreaterThanOrEqualTo(connz.Conns[1].InBytes);
        }
        finally
        {
            foreach (var s in sockets) s.Dispose();
        }
    }

    /// <summary>
    /// Helper to connect a raw TCP client to the NATS server, send CONNECT and optional commands,
    /// and return the socket. The caller is responsible for disposing the socket.
    /// </summary>
    private async Task<Socket> ConnectClientAsync(string connectJson, string? extraCommands = null)
    {
        var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        await sock.ConnectAsync(new IPEndPoint(IPAddress.Loopback, _natsPort));
        var buf = new byte[4096];
        _ = await sock.ReceiveAsync(buf, SocketFlags.None); // consume INFO

        var cmd = $"CONNECT {connectJson}\r\n";
        if (extraCommands is not null)
            cmd += extraCommands;
        await sock.SendAsync(System.Text.Encoding.ASCII.GetBytes(cmd), SocketFlags.None);
        return sock;
    }

    private static int GetFreePort()
    {
        using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        sock.Bind(new IPEndPoint(IPAddress.Loopback, 0));
        return ((IPEndPoint)sock.LocalEndPoint!).Port;
    }
}
