using System.Net;
using System.Net.Sockets;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Auth;
using NATS.Server.Monitoring;

namespace NATS.Server.Tests;

public class ConnzParityFilterTests
{
    [Fact]
    public async Task Connz_filters_by_user_account_and_subject_and_includes_tls_peer_and_jwt_metadata()
    {
        await using var fx = await MonitoringParityFixture.StartAsync();
        await fx.ConnectClientAsync("u", "orders.created");
        await fx.ConnectClientAsync("v", "payments.created");

        var connz = fx.GetConnz("?user=u&acc=A&filter_subject=orders.*&subs=detail");
        connz.Conns.ShouldAllBe(c => c.Account == "A" && c.AuthorizedUser == "u");
    }
}

internal sealed class MonitoringParityFixture : IAsyncDisposable
{
    private readonly NatsServer _server;
    private readonly CancellationTokenSource _cts;
    private readonly List<TcpClient> _clients = [];
    private readonly NatsOptions _options;

    private MonitoringParityFixture(NatsServer server, NatsOptions options, CancellationTokenSource cts)
    {
        _server = server;
        _options = options;
        _cts = cts;
    }

    public static async Task<MonitoringParityFixture> StartAsync()
    {
        var options = new NatsOptions
        {
            Host = "127.0.0.1",
            Port = 0,
            Users =
            [
                new User { Username = "u", Password = "p", Account = "A" },
                new User { Username = "v", Password = "p", Account = "B" },
            ],
        };

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

    public async Task ConnectClientAsync(string username, string? subscribeSubject)
    {
        var client = new TcpClient();
        await client.ConnectAsync(IPAddress.Loopback, _server.Port);
        _clients.Add(client);

        var stream = client.GetStream();
        await ReadLineAsync(stream); // INFO

        var connect = $"CONNECT {{\"user\":\"{username}\",\"pass\":\"p\"}}\r\n";
        await stream.WriteAsync(Encoding.ASCII.GetBytes(connect));
        if (!string.IsNullOrEmpty(subscribeSubject))
            await stream.WriteAsync(Encoding.ASCII.GetBytes($"SUB {subscribeSubject} sid-{username}\r\n"));
        await stream.FlushAsync();
        await Task.Delay(30);
    }

    public Connz GetConnz(string queryString)
    {
        var ctx = new DefaultHttpContext();
        ctx.Request.QueryString = new QueryString(queryString);
        return new ConnzHandler(_server).HandleConnz(ctx);
    }

    public async Task<Varz> GetVarzAsync()
    {
        using var handler = new VarzHandler(_server, _options);
        return await handler.HandleVarzAsync();
    }

    public async ValueTask DisposeAsync()
    {
        foreach (var client in _clients)
            client.Dispose();
        await _cts.CancelAsync();
        _server.Dispose();
        _cts.Dispose();
    }

    private static async Task<string> ReadLineAsync(NetworkStream stream)
    {
        var bytes = new List<byte>();
        var one = new byte[1];
        while (true)
        {
            var read = await stream.ReadAsync(one.AsMemory(0, 1));
            if (read == 0)
                break;
            if (one[0] == (byte)'\n')
                break;
            if (one[0] != (byte)'\r')
                bytes.Add(one[0]);
        }

        return Encoding.ASCII.GetString([.. bytes]);
    }
}
