// Go: TestMonitorStacksz server/monitor_test.go:2135
// Go: TestMonitorConcurrentMonitoring server/monitor_test.go:2148
// Go: TestMonitorHandleRoot server/monitor_test.go:1819
// Go: TestMonitorHTTPBasePath server/monitor_test.go:220
// Go: TestMonitorAccountz server/monitor_test.go:4300
// Go: TestMonitorAccountStatz server/monitor_test.go:4330

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

namespace NATS.Server.Monitoring.Tests.Monitoring;

/// <summary>
/// Tests covering miscellaneous monitoring endpoints: root, accountz, accstatz,
/// gatewayz, leafz, and concurrent monitoring safety.
/// Ported from the Go server's monitor_test.go.
/// </summary>
public class MonitorStackszTests : IAsyncLifetime
{
    private readonly NatsServer _server;
    private readonly int _natsPort;
    private readonly int _monitorPort;
    private readonly CancellationTokenSource _cts = new();
    private readonly HttpClient _http = new();

    public MonitorStackszTests()
    {
        _natsPort = TestPortAllocator.GetFreePort();
        _monitorPort = TestPortAllocator.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: TestMonitorHandleRoot (line 1819).
    /// Verifies GET / returns HTTP 200 with endpoint listing.
    /// </summary>
    [Fact]
    public async Task Root_returns_endpoint_listing()
    {
        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);

        var body = await response.Content.ReadAsStringAsync();
        body.ShouldContain("varz");
        body.ShouldContain("connz");
        body.ShouldContain("routez");
        body.ShouldContain("healthz");
    }

    /// <summary>
    /// Go: TestMonitorHandleRoot (line 1819).
    /// Verifies GET / response includes subsz endpoint.
    /// </summary>
    [Fact]
    public async Task Root_includes_subz_endpoint()
    {
        var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/");
        body.ShouldContain("subz");
    }

    /// <summary>
    /// Go: TestMonitorAccountz (line 4300).
    /// Verifies /accountz returns valid JSON with accounts list.
    /// </summary>
    [Fact]
    public async Task Accountz_returns_accounts_list()
    {
        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/accountz");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);

        var body = await response.Content.ReadAsStringAsync();
        body.ShouldContain("accounts");
        body.ShouldContain("num_accounts");
    }

    /// <summary>
    /// Go: TestMonitorAccountz (line 4300).
    /// Verifies /accountz num_accounts is at least 1 (global account).
    /// </summary>
    [Fact]
    public async Task Accountz_num_accounts_at_least_one()
    {
        var doc = JsonDocument.Parse(await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accountz"));
        doc.RootElement.GetProperty("num_accounts").GetInt32().ShouldBeGreaterThanOrEqualTo(1);
    }

    /// <summary>
    /// Go: TestMonitorAccountStatz (line 4330).
    /// Verifies /accstatz returns aggregate account statistics.
    /// </summary>
    [Fact]
    public async Task Accstatz_returns_aggregate_stats()
    {
        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/accstatz");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);

        var body = await response.Content.ReadAsStringAsync();
        body.ShouldContain("total_accounts");
        body.ShouldContain("total_connections");
        body.ShouldContain("total_subscriptions");
    }

    /// <summary>
    /// Go: TestMonitorAccountStatz (line 4330).
    /// Verifies /accstatz total_accounts is at least 1.
    /// </summary>
    [Fact]
    public async Task Accstatz_total_accounts_at_least_one()
    {
        var doc = JsonDocument.Parse(await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accstatz"));
        doc.RootElement.GetProperty("total_accounts").GetInt32().ShouldBeGreaterThanOrEqualTo(1);
    }

    /// <summary>
    /// Go: TestMonitorGateway (line 2880).
    /// Verifies /gatewayz returns valid JSON.
    /// </summary>
    [Fact]
    public async Task Gatewayz_returns_valid_json()
    {
        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/gatewayz");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);

        var body = await response.Content.ReadAsStringAsync();
        body.ShouldContain("gateways");
    }

    /// <summary>
    /// Go: TestMonitorLeafNode (line 3112).
    /// Verifies /leafz returns valid JSON.
    /// </summary>
    [Fact]
    public async Task Leafz_returns_valid_json()
    {
        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/leafz");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);

        var body = await response.Content.ReadAsStringAsync();
        body.ShouldContain("leafs");
    }

    /// <summary>
    /// Go: TestMonitorConcurrentMonitoring (line 2148).
    /// Verifies concurrent requests across multiple endpoint types do not fail.
    /// </summary>
    [Fact]
    public async Task Concurrent_requests_across_endpoints_succeed()
    {
        var endpoints = new[] { "varz", "varz", "connz", "connz", "subz", "subz", "routez", "routez" };
        var tasks = endpoints.Select(async endpoint =>
        {
            for (var i = 0; i < 10; i++)
            {
                var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/{endpoint}");
                response.StatusCode.ShouldBe(HttpStatusCode.OK);
            }
        });

        await Task.WhenAll(tasks);
    }

    /// <summary>
    /// Go: TestMonitorConcurrentMonitoring (line 2148).
    /// Verifies concurrent /healthz requests do not fail.
    /// </summary>
    [Fact]
    public async Task Concurrent_healthz_requests_succeed()
    {
        var tasks = Enumerable.Range(0, 20).Select(async _ =>
        {
            var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/healthz");
            response.StatusCode.ShouldBe(HttpStatusCode.OK);
        });

        await Task.WhenAll(tasks);
    }

    /// <summary>
    /// Go: TestMonitorHttpStatsNoUpdatedWhenUsingServerFuncs (line 2435).
    /// Verifies /varz http_req_stats keys include all endpoints that were accessed.
    /// </summary>
    [Fact]
    public async Task Http_req_stats_tracks_accessed_endpoints()
    {
        // Access multiple endpoints
        await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/connz");
        await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/subz");
        await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/routez");
        await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/varz");

        var varz = await _http.GetFromJsonAsync<Varz>($"http://127.0.0.1:{_monitorPort}/varz");
        varz.ShouldNotBeNull();
        varz.HttpReqStats.ShouldContainKey("/connz");
        varz.HttpReqStats.ShouldContainKey("/subz");
        varz.HttpReqStats.ShouldContainKey("/routez");
        varz.HttpReqStats.ShouldContainKey("/varz");
    }

    /// <summary>
    /// Go: TestMonitorHandleRoot (line 1819).
    /// Verifies GET / includes jsz endpoint in listing.
    /// </summary>
    [Fact]
    public async Task Root_includes_jsz_endpoint()
    {
        var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/");
        body.ShouldContain("jsz");
    }

    /// <summary>
    /// Go: TestMonitorHandleRoot (line 1819).
    /// Verifies GET / includes accountz endpoint in listing.
    /// </summary>
    [Fact]
    public async Task Root_includes_accountz_endpoint()
    {
        var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/");
        body.ShouldContain("accountz");
    }

    /// <summary>
    /// Go: TestMonitorServerIDs (line 2410).
    /// Verifies multiple monitoring endpoints return the same server_id.
    /// </summary>
    [Fact]
    public async Task All_endpoints_return_consistent_server_id()
    {
        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");
        var subsz = await _http.GetFromJsonAsync<Subsz>($"http://127.0.0.1:{_monitorPort}/subz");

        varz.ShouldNotBeNull();
        connz.ShouldNotBeNull();
        subsz.ShouldNotBeNull();

        var serverId = varz.Id;
        serverId.ShouldNotBeNullOrEmpty();
        connz.Id.ShouldBe(serverId);
        subsz.Id.ShouldBe(serverId);
    }

    /// <summary>
    /// Go: TestMonitorAccountStatz (line 4330).
    /// Verifies /accstatz total_connections updates after a client connects.
    /// </summary>
    [Fact]
    public async Task Accstatz_total_connections_updates_after_connect()
    {
        using 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);
        await sock.SendAsync("CONNECT {}\r\n"u8.ToArray(), SocketFlags.None);
        await Task.Delay(200);

        var doc = JsonDocument.Parse(await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accstatz"));
        doc.RootElement.GetProperty("total_connections").GetInt32().ShouldBeGreaterThanOrEqualTo(1);
    }

    /// <summary>
    /// Go: TestMonitorAccountStatz (line 4330).
    /// Verifies /accstatz total_subscriptions updates after a client subscribes.
    /// </summary>
    [Fact]
    public async Task Accstatz_total_subscriptions_updates_after_subscribe()
    {
        using 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);
        await sock.SendAsync("CONNECT {}\r\nSUB test 1\r\n"u8.ToArray(), SocketFlags.None);
        await Task.Delay(200);

        var doc = JsonDocument.Parse(await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accstatz"));
        doc.RootElement.GetProperty("total_subscriptions").GetInt32().ShouldBeGreaterThanOrEqualTo(1);
    }

    /// <summary>
    /// Go: TestMonitorAccountz (line 4300).
    /// Verifies /accountz includes per-account fields: name, connections, subscriptions.
    /// </summary>
    [Fact]
    public async Task Accountz_includes_per_account_fields()
    {
        using 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);
        await sock.SendAsync("CONNECT {}\r\nSUB test 1\r\n"u8.ToArray(), SocketFlags.None);
        await Task.Delay(200);

        var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/accountz");
        body.ShouldContain("\"name\"");
        body.ShouldContain("\"connections\"");
        body.ShouldContain("\"subscriptions\"");
    }

    /// <summary>
    /// Go: TestMonitorGateway (line 2880).
    /// Verifies /gatewayz includes num_gateways field.
    /// </summary>
    [Fact]
    public async Task Gatewayz_includes_num_gateways()
    {
        var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/gatewayz");
        body.ShouldContain("gateways");
    }

    /// <summary>
    /// Go: TestMonitorLeafNode (line 3112).
    /// Verifies /leafz includes num_leafs field.
    /// </summary>
    [Fact]
    public async Task Leafz_includes_num_leafs()
    {
        var body = await _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}/leafz");
        body.ShouldContain("leafs");
    }

}
