using System.Net;
using System.Net.Sockets;
using Microsoft.Extensions.Logging.Abstractions;

namespace NATS.Server.Tests;

public class PprofEndpointTests
{
    [Fact]
    public async Task Debug_pprof_endpoint_returns_profile_index_when_profport_enabled()
    {
        await using var fx = await PprofMonitorFixture.StartWithProfilingAsync();
        var body = await fx.GetStringAsync("/debug/pprof");
        body.ShouldContain("profiles");
    }
}

internal sealed class PprofMonitorFixture : IAsyncDisposable
{
    private readonly NatsServer _server;
    private readonly CancellationTokenSource _cts;
    private readonly HttpClient _http;
    private readonly int _monitorPort;

    private PprofMonitorFixture(NatsServer server, CancellationTokenSource cts, HttpClient http, int monitorPort)
    {
        _server = server;
        _cts = cts;
        _http = http;
        _monitorPort = monitorPort;
    }

    public static async Task<PprofMonitorFixture> StartWithProfilingAsync()
    {
        var monitorPort = GetFreePort();
        var options = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            MonitorPort = monitorPort,
            ProfPort = monitorPort,
        };

        var server = new NatsServer(options, NullLoggerFactory.Instance);
        var cts = new CancellationTokenSource();
        _ = server.StartAsync(cts.Token);
        await server.WaitForReadyAsync();

        var http = new HttpClient();
        for (var i = 0; i < 50; i++)
        {
            try
            {
                var response = await http.GetAsync($"http://127.0.0.1:{monitorPort}/healthz");
                if (response.IsSuccessStatusCode)
                    break;
            }
            catch
            {
            }

            await Task.Delay(50);
        }

        return new PprofMonitorFixture(server, cts, http, monitorPort);
    }

    public Task<string> GetStringAsync(string path)
    {
        return _http.GetStringAsync($"http://127.0.0.1:{_monitorPort}{path}");
    }

    public Task<byte[]> GetBytesAsync(string path)
    {
        return _http.GetByteArrayAsync($"http://127.0.0.1:{_monitorPort}{path}");
    }

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

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