// Ported from golang/nats-server/server/monitor_test.go
// TestMonitorHandleVarz — verify /varz returns valid server identity fields and tracks message stats.

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;

public class VarzParityTests : IAsyncLifetime
{
    private readonly NatsServer _server;
    private readonly int _natsPort;
    private readonly int _monitorPort;
    private readonly CancellationTokenSource _cts = new();
    private readonly HttpClient _http = new();

    public VarzParityTests()
    {
        _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>
    /// Corresponds to Go TestMonitorHandleVarz (first block, mode=0).
    /// Verifies the /varz endpoint returns valid JSON containing required server identity fields:
    /// server_id, version, now, start, host, port, max_payload, mem, cores.
    /// </summary>
    [Fact]
    public async Task Varz_returns_valid_json_with_server_info()
    {
        var response = await _http.GetAsync($"http://127.0.0.1:{_monitorPort}/varz");
        response.StatusCode.ShouldBe(HttpStatusCode.OK);

        var varz = await response.Content.ReadFromJsonAsync<Varz>();
        varz.ShouldNotBeNull();

        // server_id must be present and non-empty
        varz.Id.ShouldNotBeNullOrEmpty();

        // version must be present
        varz.Version.ShouldNotBeNullOrEmpty();

        // now must be a plausible timestamp (not default DateTime.MinValue)
        varz.Now.ShouldBeGreaterThan(DateTime.MinValue);

        // start must be within a reasonable window of now
        (DateTime.UtcNow - varz.Start).ShouldBeLessThan(TimeSpan.FromSeconds(30));

        // host and port must reflect server configuration
        varz.Host.ShouldNotBeNullOrEmpty();
        varz.Port.ShouldBe(_natsPort);

        // max_payload is 1 MB by default (Go reference: defaultMaxPayload = 1MB)
        varz.MaxPayload.ShouldBe(1024 * 1024);

        // uptime must be non-empty
        varz.Uptime.ShouldNotBeNullOrEmpty();

        // runtime metrics must be populated
        varz.Mem.ShouldBeGreaterThan(0L);
        varz.Cores.ShouldBeGreaterThan(0);
    }

    /// <summary>
    /// Corresponds to Go TestMonitorHandleVarz (second block after connecting a client).
    /// Verifies /varz correctly tracks connections, total_connections, in_msgs, in_bytes
    /// after a client connects, subscribes, and publishes a message.
    /// </summary>
    [Fact]
    public async Task Varz_tracks_connections_and_messages()
    {
        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); // consume INFO

        // CONNECT + SUB + PUB "hello" (5 bytes) to "test"
        var cmd = "CONNECT {}\r\nSUB test 1\r\nPUB test 5\r\nhello\r\n"u8.ToArray();
        await sock.SendAsync(cmd, SocketFlags.None);
        await Task.Delay(200);

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

        var varz = await response.Content.ReadFromJsonAsync<Varz>();
        varz.ShouldNotBeNull();

        // At least 1 active connection
        varz.Connections.ShouldBeGreaterThanOrEqualTo(1);

        // Total connections must have been counted
        varz.TotalConnections.ShouldBeGreaterThanOrEqualTo(1UL);

        // in_msgs: at least the 1 PUB we sent
        varz.InMsgs.ShouldBeGreaterThanOrEqualTo(1L);

        // in_bytes: at least 5 bytes ("hello")
        varz.InBytes.ShouldBeGreaterThanOrEqualTo(5L);
    }

    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;
    }
}
