using System.Net;
using System.Net.Sockets;
using System.Text;
using NATS.Server.Mqtt;

namespace NATS.Server.Tests;

public class MqttListenerParityTests
{
    [Fact]
    public async Task Mqtt_listener_accepts_connect_and_routes_publish_to_matching_subscription()
    {
        await using var listener = new MqttListener("127.0.0.1", 0);
        using var cts = new CancellationTokenSource();
        await listener.StartAsync(cts.Token);

        using var sub = new TcpClient();
        await sub.ConnectAsync(IPAddress.Loopback, listener.Port);
        var subStream = sub.GetStream();
        await MqttTestWire.WriteLineAsync(subStream, "CONNECT sub");
        (await MqttTestWire.ReadLineAsync(subStream, 1000)).ShouldBe("CONNACK");
        await MqttTestWire.WriteLineAsync(subStream, "SUB sensors.temp");
        var subAck = await MqttTestWire.ReadLineAsync(subStream, 1000);
        subAck.ShouldNotBeNull();
        subAck.ShouldContain("SUBACK");

        using var pub = new TcpClient();
        await pub.ConnectAsync(IPAddress.Loopback, listener.Port);
        var pubStream = pub.GetStream();
        await MqttTestWire.WriteLineAsync(pubStream, "CONNECT pub");
        _ = await MqttTestWire.ReadLineAsync(pubStream, 1000);
        await MqttTestWire.WriteLineAsync(pubStream, "PUB sensors.temp 42");

        var message = await MqttTestWire.ReadLineAsync(subStream, 1000);
        message.ShouldBe("MSG sensors.temp 42");
    }
}

internal static class MqttTestWire
{
    public static async Task WriteLineAsync(NetworkStream stream, string line)
    {
        var bytes = Encoding.UTF8.GetBytes(line + "\n");
        await stream.WriteAsync(bytes);
        await stream.FlushAsync();
    }

    public static async Task<string?> ReadLineAsync(NetworkStream stream, int timeoutMs)
    {
        using var timeout = new CancellationTokenSource(timeoutMs);
        var bytes = new List<byte>();
        var one = new byte[1];
        try
        {
            while (true)
            {
                var read = await stream.ReadAsync(one.AsMemory(0, 1), timeout.Token);
                if (read == 0)
                    return null;
                if (one[0] == (byte)'\n')
                    break;
                if (one[0] != (byte)'\r')
                    bytes.Add(one[0]);
            }
        }
        catch (OperationCanceledException)
        {
            return null;
        }

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