using System.Net;
using System.Net.Sockets;
using System.Text;
using NATS.Server.Routes;
using NATS.Server.Subscriptions;

namespace NATS.Server.Clustering.Tests.Routes;

public class RouteBatchProtoParityBatch3Tests
{
    [Fact]
    public async Task SendRouteSubProtosAsync_writes_batched_rs_plus_frames()
    {
        var (connection, peer) = CreateRoutePair();
        try
        {
            await connection.SendRouteSubProtosAsync(
                [
                    new RemoteSubscription("orders.*", null, "r1", Account: "A"),
                    new RemoteSubscription("orders.q", "workers", "r1", Account: "A", QueueWeight: 2),
                ],
                CancellationToken.None);

            var data = ReadFromPeer(peer);
            data.ShouldContain("RS+ A orders.*");
            data.ShouldContain("RS+ A orders.q workers 2");
        }
        finally
        {
            await connection.DisposeAsync();
            peer.Dispose();
        }
    }

    [Fact]
    public async Task SendRouteUnSubProtosAsync_writes_batched_rs_minus_frames()
    {
        var (connection, peer) = CreateRoutePair();
        try
        {
            await connection.SendRouteUnSubProtosAsync(
                [
                    new RemoteSubscription("orders.*", null, "r1", Account: "A"),
                    new RemoteSubscription("orders.q", "workers", "r1", Account: "A"),
                ],
                CancellationToken.None);

            var data = ReadFromPeer(peer);
            data.ShouldContain("RS- A orders.*");
            data.ShouldContain("RS- A orders.q workers");
        }
        finally
        {
            await connection.DisposeAsync();
            peer.Dispose();
        }
    }

    [Fact]
    public async Task SendRouteSubOrUnSubProtosAsync_skips_empty_lines_and_flushes_once()
    {
        var (connection, peer) = CreateRoutePair();
        try
        {
            await connection.SendRouteSubOrUnSubProtosAsync(
                ["RS+ A foo.bar", "", "  ", "RS- A foo.bar"],
                CancellationToken.None);

            var data = ReadFromPeer(peer);
            data.ShouldContain("RS+ A foo.bar");
            data.ShouldContain("RS- A foo.bar");
            data.ShouldNotContain("\r\n\r\n");
        }
        finally
        {
            await connection.DisposeAsync();
            peer.Dispose();
        }
    }

    private static (RouteConnection Route, Socket Peer) CreateRoutePair()
    {
        var listener = new TcpListener(IPAddress.Loopback, 0);
        listener.Start();
        var endpoint = (IPEndPoint)listener.LocalEndpoint;

        var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        client.Connect(endpoint);

        var server = listener.AcceptSocket();
        listener.Stop();

        return (new RouteConnection(client), server);
    }

    private static string ReadFromPeer(Socket peer)
    {
        peer.ReceiveTimeout = 2_000;
        var buffer = new byte[4096];
        var read = peer.Receive(buffer);
        return Encoding.ASCII.GetString(buffer, 0, read);
    }
}
