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

namespace NATS.Server.Tests;

public class EventSystemTests
{
    [Fact]
    public void ConnectEventMsg_serializes_with_correct_type()
    {
        var evt = new ConnectEventMsg
        {
            Type = ConnectEventMsg.EventType,
            Id = "test123",
            Time = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            Server = new EventServerInfo { Name = "test-server", Id = "SRV1" },
            Client = new EventClientInfo { Id = 1, Account = "$G" },
        };

        var json = JsonSerializer.Serialize(evt, EventJsonContext.Default.ConnectEventMsg);
        json.ShouldContain("\"type\":\"io.nats.server.advisory.v1.client_connect\"");
        json.ShouldContain("\"server\":");
        json.ShouldContain("\"client\":");
    }

    [Fact]
    public void DisconnectEventMsg_serializes_with_reason()
    {
        var evt = new DisconnectEventMsg
        {
            Type = DisconnectEventMsg.EventType,
            Id = "test456",
            Time = DateTime.UtcNow,
            Server = new EventServerInfo { Name = "test-server", Id = "SRV1" },
            Client = new EventClientInfo { Id = 2, Account = "myacc" },
            Reason = "Client Closed",
            Sent = new DataStats { Msgs = 10, Bytes = 1024 },
            Received = new DataStats { Msgs = 5, Bytes = 512 },
        };

        var json = JsonSerializer.Serialize(evt, EventJsonContext.Default.DisconnectEventMsg);
        json.ShouldContain("\"reason\":\"Client Closed\"");
    }

    [Fact]
    public void ServerStatsMsg_serializes()
    {
        var evt = new ServerStatsMsg
        {
            Server = new EventServerInfo { Name = "srv1", Id = "ABC" },
            Stats = new ServerStatsData
            {
                Connections = 10,
                TotalConnections = 100,
                InMsgs = 5000,
                OutMsgs = 4500,
                InBytes = 1_000_000,
                OutBytes = 900_000,
                Mem = 50 * 1024 * 1024,
                Subscriptions = 42,
            },
        };

        var json = JsonSerializer.Serialize(evt, EventJsonContext.Default.ServerStatsMsg);
        json.ShouldContain("\"connections\":10");
        json.ShouldContain("\"in_msgs\":5000");
    }

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

        var eventSystem = server.EventSystem;
        eventSystem.ShouldNotBeNull();
        eventSystem.SystemClient.ShouldNotBeNull();
        eventSystem.SystemClient.Kind.ShouldBe(ClientKind.System);

        await server.ShutdownAsync();
    }

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

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

        server.SendInternalMsg("test.subject", null, new { Value = "hello" });

        var result = await received.Task.WaitAsync(TimeSpan.FromSeconds(5));
        result.ShouldBe("test.subject");

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