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

namespace NATS.Server.Tests;

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

        var received = new TaskCompletionSource<byte[]>();
        var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
        server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(msg.ToArray());
        });

        var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "VARZ");
        server.SendInternalMsg(reqSubject, replySubject, null);

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        var json = Encoding.UTF8.GetString(result);
        json.ShouldContain("\"server_id\"");
        json.ShouldContain("\"version\"");
        json.ShouldContain("\"host\"");
        json.ShouldContain("\"port\"");

        await server.ShutdownAsync();
    }

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

        var received = new TaskCompletionSource<byte[]>();
        var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
        server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(msg.ToArray());
        });

        var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "HEALTHZ");
        server.SendInternalMsg(reqSubject, replySubject, null);

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        var json = Encoding.UTF8.GetString(result);
        json.ShouldContain("ok");

        await server.ShutdownAsync();
    }

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

        var received = new TaskCompletionSource<byte[]>();
        var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
        server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(msg.ToArray());
        });

        var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "SUBSZ");
        server.SendInternalMsg(reqSubject, replySubject, null);

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        var json = Encoding.UTF8.GetString(result);
        json.ShouldContain("\"num_subscriptions\"");

        await server.ShutdownAsync();
    }

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

        var received = new TaskCompletionSource<byte[]>();
        var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
        server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(msg.ToArray());
        });

        var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "IDZ");
        server.SendInternalMsg(reqSubject, replySubject, null);

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        var json = Encoding.UTF8.GetString(result);
        json.ShouldContain("\"server_id\"");
        json.ShouldContain("\"server_name\"");

        await server.ShutdownAsync();
    }

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

        var received = new TaskCompletionSource<byte[]>();
        var replySubject = $"_INBOX.test.{Guid.NewGuid():N}";
        server.EventSystem!.SysSubscribe(replySubject, (sub, client, acc, subject, reply, hdr, msg) =>
        {
            received.TrySetResult(msg.ToArray());
        });

        var pingSubject = string.Format(EventSubjects.ServerPing, "VARZ");
        server.SendInternalMsg(pingSubject, replySubject, null);

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        var json = Encoding.UTF8.GetString(result);
        json.ShouldContain("\"server_id\"");

        await server.ShutdownAsync();
    }

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

        // Send a request with no reply subject -- should not crash
        var reqSubject = string.Format(EventSubjects.ServerReq, server.ServerId, "VARZ");
        server.SendInternalMsg(reqSubject, null, null);

        // Give it a moment to process without error
        await Task.Delay(200);

        // Server should still be running
        server.IsShuttingDown.ShouldBeFalse();

        await server.ShutdownAsync();
    }

    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;
    }
}
