using System.Text.Json;
using NATS.Server;
using NATS.Server.Events;
using Microsoft.Extensions.Logging.Abstractions;

namespace NATS.Server.Tests;

public class SystemEventsTests
{
    [Fact]
    public async Task Server_publishes_connect_event_on_client_auth()
    {
        using var server = CreateTestServer();
        _ = server.StartAsync(CancellationToken.None);
        await server.WaitForReadyAsync();

        var received = new TaskCompletionSource<string>();
        server.EventSystem!.SysSubscribe("$SYS.ACCOUNT.*.CONNECT", (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(subject);
        });

        // Connect a real client
        using var sock = new System.Net.Sockets.Socket(
            System.Net.Sockets.AddressFamily.InterNetwork,
            System.Net.Sockets.SocketType.Stream,
            System.Net.Sockets.ProtocolType.Tcp);
        await sock.ConnectAsync(System.Net.IPAddress.Loopback, server.Port);

        // Read INFO
        var buf = new byte[4096];
        await sock.ReceiveAsync(buf);

        // Send CONNECT
        var connect = System.Text.Encoding.ASCII.GetBytes("CONNECT {}\r\n");
        await sock.SendAsync(connect);

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        result.ShouldStartWith("$SYS.ACCOUNT.");
        result.ShouldEndWith(".CONNECT");

        await server.ShutdownAsync();
    }

    [Fact]
    public async Task Server_publishes_disconnect_event_on_client_close()
    {
        using var server = CreateTestServer();
        _ = server.StartAsync(CancellationToken.None);
        await server.WaitForReadyAsync();

        var received = new TaskCompletionSource<string>();
        server.EventSystem!.SysSubscribe("$SYS.ACCOUNT.*.DISCONNECT", (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(subject);
        });

        // Connect and then disconnect
        using var sock = new System.Net.Sockets.Socket(
            System.Net.Sockets.AddressFamily.InterNetwork,
            System.Net.Sockets.SocketType.Stream,
            System.Net.Sockets.ProtocolType.Tcp);
        await sock.ConnectAsync(System.Net.IPAddress.Loopback, server.Port);
        var buf = new byte[4096];
        await sock.ReceiveAsync(buf);
        await sock.SendAsync(System.Text.Encoding.ASCII.GetBytes("CONNECT {}\r\n"));
        await Task.Delay(100);
        sock.Shutdown(System.Net.Sockets.SocketShutdown.Both);

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        result.ShouldStartWith("$SYS.ACCOUNT.");
        result.ShouldEndWith(".DISCONNECT");

        await server.ShutdownAsync();
    }

    [Fact]
    public async Task Server_publishes_statsz_periodically()
    {
        using var server = CreateTestServer();
        _ = server.StartAsync(CancellationToken.None);
        await server.WaitForReadyAsync();

        var received = new TaskCompletionSource<string>();
        server.EventSystem!.SysSubscribe("$SYS.SERVER.*.STATSZ", (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(subject);
        });

        // Trigger a manual stats publish (don't wait 10s)
        server.EventSystem!.PublishServerStats();

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        result.ShouldContain(".STATSZ");

        await server.ShutdownAsync();
    }

    [Fact]
    public async Task Server_publishes_shutdown_event()
    {
        using var server = CreateTestServer();
        _ = server.StartAsync(CancellationToken.None);
        await server.WaitForReadyAsync();

        var received = new TaskCompletionSource<string>();
        server.EventSystem!.SysSubscribe("$SYS.SERVER.*.SHUTDOWN", (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(subject);
        });

        await server.ShutdownAsync();

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        result.ShouldContain(".SHUTDOWN");
    }

    private static NatsServer CreateTestServer()
    {
        var port = GetFreePort();
        return new NatsServer(new NatsOptions { Port = port }, NullLoggerFactory.Instance);
    }

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