// Port of Go server/events_test.go — system event DTO and subject parity tests.
// Reference: golang/nats-server/server/events_test.go
//
// Tests cover: ConnectEventMsg, DisconnectEventMsg, ServerStatsMsg,
// AccountNumConns, AuthErrorEventMsg, ShutdownEventMsg serialization,
// event subject pattern formatting, event filtering by tag/server ID,
// and HealthZ status code mapping.

using System.Text.Json;
using NATS.Server.Events;

namespace NATS.Server.Monitoring.Tests.Events;

/// <summary>
/// Parity tests ported from Go server/events_test.go exercising
/// system event DTOs, JSON serialization shapes, event subjects,
/// and event filtering logic.
/// </summary>
public class EventGoParityTests
{
    // ========================================================================
    // ConnectEventMsg serialization
    // Go reference: events_test.go TestSystemAccountNewConnection
    // ========================================================================

    [Fact]
    public void ConnectEventMsg_JsonShape_MatchesGo()
    {
        // Go: TestSystemAccountNewConnection — verifies connect event JSON shape.
        var evt = new ConnectEventMsg
        {
            Id = "evt-001",
            Time = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            Server = new EventServerInfo
            {
                Name = "test-server",
                Id = "NSVR001",
                Cluster = "test-cluster",
                Version = "2.10.0",
            },
            Client = new EventClientInfo
            {
                Id = 42,
                Account = "$G",
                User = "alice",
                Name = "test-client",
                Lang = "csharp",
                Version = "1.0",
            },
        };

        var json = JsonSerializer.Serialize(evt);

        json.ShouldContain("\"type\":");
        json.ShouldContain(ConnectEventMsg.EventType);
        json.ShouldContain("\"server\":");
        json.ShouldContain("\"client\":");
        json.ShouldContain("\"id\":\"evt-001\"");
    }

    [Fact]
    public void ConnectEventMsg_EventType_Constant()
    {
        // Go: connect event type string.
        ConnectEventMsg.EventType.ShouldBe("io.nats.server.advisory.v1.client_connect");
    }

    [Fact]
    public void ConnectEventMsg_DefaultType_MatchesConstant()
    {
        var evt = new ConnectEventMsg();
        evt.Type.ShouldBe(ConnectEventMsg.EventType);
    }

    // ========================================================================
    // DisconnectEventMsg serialization
    // Go reference: events_test.go TestSystemAccountNewConnection (disconnect part)
    // ========================================================================

    [Fact]
    public void DisconnectEventMsg_JsonShape_MatchesGo()
    {
        // Go: TestSystemAccountNewConnection — verifies disconnect event includes
        // sent/received stats and reason.
        var evt = new DisconnectEventMsg
        {
            Id = "evt-002",
            Time = DateTime.UtcNow,
            Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
            Client = new EventClientInfo { Id = 42, Account = "$G" },
            Sent = new DataStats { Msgs = 100, Bytes = 10240 },
            Received = new DataStats { Msgs = 50, Bytes = 5120 },
            Reason = "Client Closed",
        };

        var json = JsonSerializer.Serialize(evt);

        json.ShouldContain("\"type\":");
        json.ShouldContain(DisconnectEventMsg.EventType);
        json.ShouldContain("\"sent\":");
        json.ShouldContain("\"received\":");
        json.ShouldContain("\"reason\":");
    }

    [Fact]
    public void DisconnectEventMsg_EventType_Constant()
    {
        DisconnectEventMsg.EventType.ShouldBe("io.nats.server.advisory.v1.client_disconnect");
    }

    [Fact]
    public void DisconnectEventMsg_Reason_ClientClosed()
    {
        // Go: TestSystemAccountDisconnectBadLogin — reason is captured on disconnect.
        var evt = new DisconnectEventMsg { Reason = "Client Closed" };
        evt.Reason.ShouldBe("Client Closed");
    }

    [Fact]
    public void DisconnectEventMsg_Reason_AuthViolation()
    {
        // Go: TestSystemAccountDisconnectBadLogin — bad login reason.
        var evt = new DisconnectEventMsg { Reason = "Authentication Violation" };
        evt.Reason.ShouldBe("Authentication Violation");
    }

    // ========================================================================
    // DataStats
    // Go reference: events_test.go TestSystemAccountingWithLeafNodes
    // ========================================================================

    [Fact]
    public void DataStats_JsonSerialization()
    {
        // Go: TestSystemAccountingWithLeafNodes — verifies sent/received stats structure.
        var stats = new DataStats
        {
            Msgs = 1000,
            Bytes = 65536,
            Routes = new MsgBytesStats { Msgs = 200, Bytes = 10240 },
            Gateways = new MsgBytesStats { Msgs = 50, Bytes = 2048 },
            Leafs = new MsgBytesStats { Msgs = 100, Bytes = 5120 },
        };

        var json = JsonSerializer.Serialize(stats);

        json.ShouldContain("\"msgs\":");
        json.ShouldContain("\"bytes\":");
        json.ShouldContain("\"routes\":");
        json.ShouldContain("\"gateways\":");
        json.ShouldContain("\"leafs\":");
    }

    [Fact]
    public void DataStats_NullSubStats_OmittedFromJson()
    {
        // Go: When no routes/gateways/leafs, those fields are omitted (omitempty).
        var stats = new DataStats { Msgs = 100, Bytes = 1024 };

        var json = JsonSerializer.Serialize(stats);

        json.ShouldNotContain("\"routes\":");
        json.ShouldNotContain("\"gateways\":");
        json.ShouldNotContain("\"leafs\":");
    }

    // ========================================================================
    // AccountNumConns
    // Go reference: events_test.go TestAccountReqMonitoring
    // ========================================================================

    [Fact]
    public void AccountNumConns_JsonShape_MatchesGo()
    {
        // Go: TestAccountReqMonitoring — verifies account connection count event shape.
        var evt = new AccountNumConns
        {
            Id = "evt-003",
            Time = DateTime.UtcNow,
            Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
            AccountName = "MYACCOUNT",
            Connections = 5,
            LeafNodes = 2,
            TotalConnections = 10,
            NumSubscriptions = 42,
            Sent = new DataStats { Msgs = 500, Bytes = 25600 },
            Received = new DataStats { Msgs = 250, Bytes = 12800 },
        };

        var json = JsonSerializer.Serialize(evt);

        json.ShouldContain("\"type\":");
        json.ShouldContain(AccountNumConns.EventType);
        json.ShouldContain("\"acc\":");
        json.ShouldContain("\"conns\":");
        json.ShouldContain("\"leafnodes\":");
        json.ShouldContain("\"total_conns\":");
        json.ShouldContain("\"num_subscriptions\":");
    }

    [Fact]
    public void AccountNumConns_EventType_Constant()
    {
        AccountNumConns.EventType.ShouldBe("io.nats.server.advisory.v1.account_connections");
    }

    [Fact]
    public void AccountNumConns_SlowConsumers_IncludedWhenNonZero()
    {
        var evt = new AccountNumConns { SlowConsumers = 3 };
        var json = JsonSerializer.Serialize(evt);
        json.ShouldContain("\"slow_consumers\":3");
    }

    [Fact]
    public void AccountNumConns_SlowConsumers_OmittedWhenZero()
    {
        // Go: omitempty behavior — zero slow_consumers omitted.
        var evt = new AccountNumConns { SlowConsumers = 0 };
        var json = JsonSerializer.Serialize(evt);
        json.ShouldNotContain("\"slow_consumers\":");
    }

    // ========================================================================
    // ServerStatsMsg
    // Go reference: events_test.go TestServerEventsPingStatsZDedicatedRecvQ
    // ========================================================================

    [Fact]
    public void ServerStatsMsg_JsonShape_MatchesGo()
    {
        // Go: TestServerEventsPingStatsZDedicatedRecvQ — verifies server stats shape.
        var msg = new ServerStatsMsg
        {
            Server = new EventServerInfo
            {
                Name = "test-server",
                Id = "NSVR001",
                Version = "2.10.0",
                JetStream = true,
            },
            Stats = new ServerStatsData
            {
                Start = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc),
                Mem = 134217728,
                Cores = 8,
                Cpu = 12.5,
                Connections = 10,
                TotalConnections = 100,
                ActiveAccounts = 5,
                Subscriptions = 42,
                Sent = new DataStats { Msgs = 1000, Bytes = 65536 },
                Received = new DataStats { Msgs = 500, Bytes = 32768 },
                InMsgs = 500,
                OutMsgs = 1000,
                InBytes = 32768,
                OutBytes = 65536,
            },
        };

        var json = JsonSerializer.Serialize(msg);

        json.ShouldContain("\"server\":");
        json.ShouldContain("\"statsz\":");
        json.ShouldContain("\"mem\":");
        json.ShouldContain("\"cores\":");
        json.ShouldContain("\"connections\":");
        json.ShouldContain("\"total_connections\":");
        json.ShouldContain("\"subscriptions\":");
        json.ShouldContain("\"in_msgs\":");
        json.ShouldContain("\"out_msgs\":");
    }

    [Fact]
    public void ServerStatsData_SlowConsumerStats_JsonShape()
    {
        // Go: TestServerEventsPingStatsSlowConsumersStats — breakdown by type.
        var data = new ServerStatsData
        {
            SlowConsumers = 10,
            SlowConsumerStats = new NATS.Server.Events.SlowConsumersStats
            {
                Clients = 5,
                Routes = 2,
                Gateways = 1,
                Leafs = 2,
            },
        };

        var json = JsonSerializer.Serialize(data);

        json.ShouldContain("\"slow_consumers\":10");
        json.ShouldContain("\"slow_consumer_stats\":");
        json.ShouldContain("\"clients\":5");
        json.ShouldContain("\"routes\":2");
    }

    [Fact]
    public void ServerStatsData_StaleConnectionStats_JsonShape()
    {
        // Go: TestServerEventsPingStatsStaleConnectionStats — stale conn breakdown.
        var data = new ServerStatsData
        {
            StaleConnections = 7,
            StaleConnectionStats = new NATS.Server.Events.StaleConnectionStats
            {
                Clients = 3,
                Routes = 1,
                Gateways = 2,
                Leafs = 1,
            },
        };

        var json = JsonSerializer.Serialize(data);

        json.ShouldContain("\"stale_connections\":7");
        json.ShouldContain("\"stale_connection_stats\":");
    }

    [Fact]
    public void ServerStatsData_RouteStats_JsonShape()
    {
        // Go: TestServerEventsPingStatsZDedicatedRecvQ — route stats in statsz.
        var data = new ServerStatsData
        {
            Routes =
            [
                new RouteStat
                {
                    Id = 100,
                    Name = "route-1",
                    Sent = new DataStats { Msgs = 200, Bytes = 10240 },
                    Received = new DataStats { Msgs = 150, Bytes = 7680 },
                    Pending = 5,
                },
            ],
        };

        var json = JsonSerializer.Serialize(data);

        json.ShouldContain("\"routes\":");
        json.ShouldContain("\"rid\":100");
        json.ShouldContain("\"pending\":5");
    }

    [Fact]
    public void ServerStatsData_GatewayStats_JsonShape()
    {
        // Go: TestGatewayNameClientInfo — gateway stats in statsz.
        var data = new ServerStatsData
        {
            Gateways =
            [
                new GatewayStat
                {
                    Id = 200,
                    Name = "gw-east",
                    Sent = new DataStats { Msgs = 500, Bytes = 25600 },
                    Received = new DataStats { Msgs = 300, Bytes = 15360 },
                    InboundConnections = 3,
                },
            ],
        };

        var json = JsonSerializer.Serialize(data);

        json.ShouldContain("\"gateways\":");
        json.ShouldContain("\"gwid\":200");
        json.ShouldContain("\"inbound_connections\":3");
    }

    // ========================================================================
    // ShutdownEventMsg
    // Go reference: events_test.go TestServerEventsLDMKick
    // ========================================================================

    [Fact]
    public void ShutdownEventMsg_JsonShape_MatchesGo()
    {
        // Go: ShutdownEventMsg includes server info and reason.
        var evt = new ShutdownEventMsg
        {
            Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
            Reason = "process exit",
        };

        var json = JsonSerializer.Serialize(evt);

        json.ShouldContain("\"server\":");
        json.ShouldContain("\"reason\":");
        json.ShouldContain("\"process exit\"");
    }

    // ========================================================================
    // LameDuckEventMsg
    // Go reference: events_test.go TestServerEventsLDMKick
    // ========================================================================

    [Fact]
    public void LameDuckEventMsg_JsonShape_MatchesGo()
    {
        // Go: TestServerEventsLDMKick — lame duck event emitted before shutdown.
        var evt = new LameDuckEventMsg
        {
            Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
        };

        var json = JsonSerializer.Serialize(evt);

        json.ShouldContain("\"server\":");
        json.ShouldContain("\"name\":\"test-server\"");
    }

    // ========================================================================
    // AuthErrorEventMsg
    // Go reference: events_test.go TestSystemAccountDisconnectBadLogin
    // ========================================================================

    [Fact]
    public void AuthErrorEventMsg_JsonShape_MatchesGo()
    {
        // Go: TestSystemAccountDisconnectBadLogin — auth error advisory.
        var evt = new AuthErrorEventMsg
        {
            Id = "evt-004",
            Time = DateTime.UtcNow,
            Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
            Client = new EventClientInfo { Id = 99, Host = "192.168.1.100" },
            Reason = "Authorization Violation",
        };

        var json = JsonSerializer.Serialize(evt);

        json.ShouldContain("\"type\":");
        json.ShouldContain(AuthErrorEventMsg.EventType);
        json.ShouldContain("\"reason\":");
        json.ShouldContain("\"Authorization Violation\"");
    }

    [Fact]
    public void AuthErrorEventMsg_EventType_Constant()
    {
        AuthErrorEventMsg.EventType.ShouldBe("io.nats.server.advisory.v1.client_auth");
    }

    // ========================================================================
    // OcspPeerRejectEventMsg
    // Go reference: events.go OCSPPeerRejectEventMsg struct
    // ========================================================================

    [Fact]
    public void OcspPeerRejectEventMsg_JsonShape_MatchesGo()
    {
        var evt = new OcspPeerRejectEventMsg
        {
            Id = "evt-005",
            Time = DateTime.UtcNow,
            Kind = "client",
            Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
            Reason = "OCSP certificate revoked",
        };

        var json = JsonSerializer.Serialize(evt);

        json.ShouldContain("\"type\":");
        json.ShouldContain(OcspPeerRejectEventMsg.EventType);
        json.ShouldContain("\"kind\":\"client\"");
        json.ShouldContain("\"reason\":");
    }

    [Fact]
    public void OcspPeerRejectEventMsg_EventType_Constant()
    {
        OcspPeerRejectEventMsg.EventType.ShouldBe("io.nats.server.advisory.v1.ocsp_peer_reject");
    }

    // ========================================================================
    // AccNumConnsReq
    // Go reference: events.go accNumConnsReq
    // ========================================================================

    [Fact]
    public void AccNumConnsReq_JsonShape_MatchesGo()
    {
        var req = new AccNumConnsReq
        {
            Server = new EventServerInfo { Name = "test-server", Id = "NSVR001" },
            Account = "$G",
        };

        var json = JsonSerializer.Serialize(req);

        json.ShouldContain("\"server\":");
        json.ShouldContain("\"acc\":\"$G\"");
    }

    // ========================================================================
    // EventServerInfo
    // Go reference: events_test.go TestServerEventsFilteredByTag
    // ========================================================================

    [Fact]
    public void EventServerInfo_Tags_Serialized()
    {
        // Go: TestServerEventsFilteredByTag — server info includes tags for filtering.
        var info = new EventServerInfo
        {
            Name = "test-server",
            Id = "NSVR001",
            Tags = ["region:us-east-1", "env:production"],
        };

        var json = JsonSerializer.Serialize(info);

        json.ShouldContain("\"tags\":");
        json.ShouldContain("\"region:us-east-1\"");
        json.ShouldContain("\"env:production\"");
    }

    [Fact]
    public void EventServerInfo_NullTags_OmittedFromJson()
    {
        // Go: omitempty — nil tags are not serialized.
        var info = new EventServerInfo { Name = "test-server", Id = "NSVR001" };
        var json = JsonSerializer.Serialize(info);
        json.ShouldNotContain("\"tags\":");
    }

    [Fact]
    public void EventServerInfo_Metadata_Serialized()
    {
        var info = new EventServerInfo
        {
            Name = "test-server",
            Id = "NSVR001",
            Metadata = new Dictionary<string, string>
            {
                ["cloud"] = "aws",
                ["zone"] = "us-east-1a",
            },
        };

        var json = JsonSerializer.Serialize(info);

        json.ShouldContain("\"metadata\":");
        json.ShouldContain("\"cloud\":");
        json.ShouldContain("\"aws\"");
    }

    [Fact]
    public void EventServerInfo_NullMetadata_OmittedFromJson()
    {
        var info = new EventServerInfo { Name = "test-server", Id = "NSVR001" };
        var json = JsonSerializer.Serialize(info);
        json.ShouldNotContain("\"metadata\":");
    }

    [Fact]
    public void EventServerInfo_JetStream_IncludedWhenTrue()
    {
        var info = new EventServerInfo { Name = "s1", Id = "N1", JetStream = true };
        var json = JsonSerializer.Serialize(info);
        json.ShouldContain("\"jetstream\":true");
    }

    [Fact]
    public void EventServerInfo_JetStream_OmittedWhenFalse()
    {
        // Go: omitempty — JetStream false is not serialized.
        var info = new EventServerInfo { Name = "s1", Id = "N1", JetStream = false };
        var json = JsonSerializer.Serialize(info);
        json.ShouldNotContain("\"jetstream\":");
    }

    // ========================================================================
    // EventClientInfo
    // Go reference: events_test.go TestGatewayNameClientInfo
    // ========================================================================

    [Fact]
    public void EventClientInfo_AllFields_Serialized()
    {
        // Go: TestGatewayNameClientInfo — client info includes all connection metadata.
        var info = new EventClientInfo
        {
            Id = 42,
            Account = "MYACCOUNT",
            User = "alice",
            Name = "test-client",
            Lang = "go",
            Version = "1.30.0",
            RttNanos = 1_500_000, // 1.5ms
            Host = "192.168.1.100",
            Kind = "Client",
            ClientType = "nats",
            Tags = ["role:publisher"],
        };

        var json = JsonSerializer.Serialize(info);

        json.ShouldContain("\"id\":42");
        json.ShouldContain("\"acc\":\"MYACCOUNT\"");
        json.ShouldContain("\"user\":\"alice\"");
        json.ShouldContain("\"name\":\"test-client\"");
        json.ShouldContain("\"lang\":\"go\"");
        json.ShouldContain("\"rtt\":");
        json.ShouldContain("\"kind\":\"Client\"");
    }

    [Fact]
    public void EventClientInfo_MqttClient_Serialized()
    {
        // Go: MQTT client ID is included in client info when present.
        var info = new EventClientInfo
        {
            Id = 10,
            MqttClient = "mqtt-device-42",
        };

        var json = JsonSerializer.Serialize(info);

        json.ShouldContain("\"client_id\":\"mqtt-device-42\"");
    }

    [Fact]
    public void EventClientInfo_NullOptionalFields_OmittedFromJson()
    {
        // Go: omitempty — null optional fields are not serialized.
        var info = new EventClientInfo { Id = 1 };

        var json = JsonSerializer.Serialize(info);

        json.ShouldNotContain("\"acc\":");
        json.ShouldNotContain("\"user\":");
        json.ShouldNotContain("\"name\":");
        json.ShouldNotContain("\"lang\":");
        json.ShouldNotContain("\"kind\":");
        json.ShouldNotContain("\"tags\":");
    }

    // ========================================================================
    // Event Subject Patterns
    // Go reference: events.go subject constants
    // ========================================================================

    [Fact]
    public void EventSubjects_ConnectEvent_Format()
    {
        // Go: $SYS.ACCOUNT.%s.CONNECT
        var subject = string.Format(EventSubjects.ConnectEvent, "$G");
        subject.ShouldBe("$SYS.ACCOUNT.$G.CONNECT");
    }

    [Fact]
    public void EventSubjects_DisconnectEvent_Format()
    {
        // Go: $SYS.ACCOUNT.%s.DISCONNECT
        var subject = string.Format(EventSubjects.DisconnectEvent, "$G");
        subject.ShouldBe("$SYS.ACCOUNT.$G.DISCONNECT");
    }

    [Fact]
    public void EventSubjects_AccountConns_Format()
    {
        // Go: $SYS.ACCOUNT.%s.SERVER.CONNS (new format)
        var subject = string.Format(EventSubjects.AccountConnsNew, "MYACCOUNT");
        subject.ShouldBe("$SYS.ACCOUNT.MYACCOUNT.SERVER.CONNS");
    }

    [Fact]
    public void EventSubjects_AccountConnsOld_Format()
    {
        // Go: $SYS.SERVER.ACCOUNT.%s.CONNS (old format for backward compat)
        var subject = string.Format(EventSubjects.AccountConnsOld, "MYACCOUNT");
        subject.ShouldBe("$SYS.SERVER.ACCOUNT.MYACCOUNT.CONNS");
    }

    [Fact]
    public void EventSubjects_ServerStats_Format()
    {
        // Go: $SYS.SERVER.%s.STATSZ
        var subject = string.Format(EventSubjects.ServerStats, "NSVR001");
        subject.ShouldBe("$SYS.SERVER.NSVR001.STATSZ");
    }

    [Fact]
    public void EventSubjects_ServerShutdown_Format()
    {
        // Go: $SYS.SERVER.%s.SHUTDOWN
        var subject = string.Format(EventSubjects.ServerShutdown, "NSVR001");
        subject.ShouldBe("$SYS.SERVER.NSVR001.SHUTDOWN");
    }

    [Fact]
    public void EventSubjects_ServerLameDuck_Format()
    {
        // Go: $SYS.SERVER.%s.LAMEDUCK
        var subject = string.Format(EventSubjects.ServerLameDuck, "NSVR001");
        subject.ShouldBe("$SYS.SERVER.NSVR001.LAMEDUCK");
    }

    [Fact]
    public void EventSubjects_AuthError_Format()
    {
        // Go: $SYS.SERVER.%s.CLIENT.AUTH.ERR
        var subject = string.Format(EventSubjects.AuthError, "NSVR001");
        subject.ShouldBe("$SYS.SERVER.NSVR001.CLIENT.AUTH.ERR");
    }

    [Fact]
    public void EventSubjects_AuthErrorAccount_IsConstant()
    {
        // Go: $SYS.ACCOUNT.CLIENT.AUTH.ERR (no server ID interpolation)
        EventSubjects.AuthErrorAccount.ShouldBe("$SYS.ACCOUNT.CLIENT.AUTH.ERR");
    }

    [Fact]
    public void EventSubjects_ServerPing_Format()
    {
        // Go: $SYS.REQ.SERVER.PING.%s (e.g., STATSZ, VARZ)
        var subject = string.Format(EventSubjects.ServerPing, "STATSZ");
        subject.ShouldBe("$SYS.REQ.SERVER.PING.STATSZ");
    }

    [Fact]
    public void EventSubjects_ServerReq_Format()
    {
        // Go: $SYS.REQ.SERVER.%s.%s (server ID + request type)
        var subject = string.Format(EventSubjects.ServerReq, "NSVR001", "VARZ");
        subject.ShouldBe("$SYS.REQ.SERVER.NSVR001.VARZ");
    }

    [Fact]
    public void EventSubjects_AccountReq_Format()
    {
        // Go: $SYS.REQ.ACCOUNT.%s.%s (account + request type)
        var subject = string.Format(EventSubjects.AccountReq, "MYACCOUNT", "CONNZ");
        subject.ShouldBe("$SYS.REQ.ACCOUNT.MYACCOUNT.CONNZ");
    }

    // ========================================================================
    // Event filtering by tag
    // Go reference: events_test.go TestServerEventsFilteredByTag
    // ========================================================================

    [Fact]
    public void EventServerInfo_TagFiltering_MatchesTag()
    {
        // Go: TestServerEventsFilteredByTag — servers can be filtered by tag value.
        var server = new EventServerInfo
        {
            Name = "s1",
            Id = "NSVR001",
            Tags = ["region:us-east-1", "env:prod"],
        };

        // Simulate filtering: check if server has a specific tag.
        server.Tags.ShouldContain("region:us-east-1");
        server.Tags.ShouldContain("env:prod");
        server.Tags.ShouldNotContain("region:eu-west-1");
    }

    [Fact]
    public void EventServerInfo_TagFiltering_EmptyTags_NoMatch()
    {
        // Go: TestServerEventsFilteredByTag — server with no tags does not match any filter.
        var server = new EventServerInfo { Name = "s1", Id = "NSVR001" };
        server.Tags.ShouldBeNull();
    }

    [Fact]
    public void EventServerInfo_FilterByServerId()
    {
        // Go: TestServerEventsPingStatsZFilter — filter stats events by server ID.
        var servers = new[]
        {
            new EventServerInfo { Name = "s1", Id = "NSVR001" },
            new EventServerInfo { Name = "s2", Id = "NSVR002" },
            new EventServerInfo { Name = "s3", Id = "NSVR003" },
        };

        var filtered = servers.Where(s => s.Id == "NSVR002").ToArray();
        filtered.Length.ShouldBe(1);
        filtered[0].Name.ShouldBe("s2");
    }

    [Fact]
    public void EventServerInfo_FilterByServerId_NoMatch()
    {
        // Go: TestServerEventsPingStatsZFailFilter — non-existent server ID returns nothing.
        var servers = new[]
        {
            new EventServerInfo { Name = "s1", Id = "NSVR001" },
        };

        var filtered = servers.Where(s => s.Id == "NONEXISTENT").ToArray();
        filtered.Length.ShouldBe(0);
    }

    // ========================================================================
    // Event JSON roundtrip via source-generated context
    // Go reference: events_test.go TestServerEventsReceivedByQSubs
    // ========================================================================

    [Fact]
    public void ConnectEventMsg_RoundTrip_ViaContext()
    {
        // Go: TestServerEventsReceivedByQSubs — events received and parsed correctly.
        var original = new ConnectEventMsg
        {
            Id = "roundtrip-001",
            Time = new DateTime(2024, 6, 15, 12, 0, 0, DateTimeKind.Utc),
            Server = new EventServerInfo { Name = "s1", Id = "NSVR001" },
            Client = new EventClientInfo { Id = 42, Account = "$G", User = "alice" },
        };

        var json = JsonSerializer.Serialize(original, EventJsonContext.Default.ConnectEventMsg);
        var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.ConnectEventMsg);

        deserialized.ShouldNotBeNull();
        deserialized!.Id.ShouldBe("roundtrip-001");
        deserialized.Type.ShouldBe(ConnectEventMsg.EventType);
        deserialized.Server.Name.ShouldBe("s1");
        deserialized.Client.Id.ShouldBe(42UL);
        deserialized.Client.Account.ShouldBe("$G");
    }

    [Fact]
    public void DisconnectEventMsg_RoundTrip_ViaContext()
    {
        var original = new DisconnectEventMsg
        {
            Id = "roundtrip-002",
            Time = DateTime.UtcNow,
            Server = new EventServerInfo { Name = "s1", Id = "NSVR001" },
            Client = new EventClientInfo { Id = 99 },
            Sent = new DataStats { Msgs = 100, Bytes = 1024 },
            Received = new DataStats { Msgs = 50, Bytes = 512 },
            Reason = "Client Closed",
        };

        var json = JsonSerializer.Serialize(original, EventJsonContext.Default.DisconnectEventMsg);
        var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.DisconnectEventMsg);

        deserialized.ShouldNotBeNull();
        deserialized!.Reason.ShouldBe("Client Closed");
        deserialized.Sent.Msgs.ShouldBe(100);
        deserialized.Received.Bytes.ShouldBe(512);
    }

    [Fact]
    public void ServerStatsMsg_RoundTrip_ViaContext()
    {
        var original = new ServerStatsMsg
        {
            Server = new EventServerInfo { Name = "s1", Id = "NSVR001", JetStream = true },
            Stats = new ServerStatsData
            {
                Mem = 134217728,
                Cores = 8,
                Connections = 10,
                Subscriptions = 42,
                Sent = new DataStats { Msgs = 1000, Bytes = 65536 },
                Received = new DataStats { Msgs = 500, Bytes = 32768 },
            },
        };

        var json = JsonSerializer.Serialize(original, EventJsonContext.Default.ServerStatsMsg);
        var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.ServerStatsMsg);

        deserialized.ShouldNotBeNull();
        deserialized!.Server.JetStream.ShouldBeTrue();
        deserialized.Stats.Mem.ShouldBe(134217728);
        deserialized.Stats.Connections.ShouldBe(10);
    }

    [Fact]
    public void AccountNumConns_RoundTrip_ViaContext()
    {
        var original = new AccountNumConns
        {
            Id = "roundtrip-004",
            Time = DateTime.UtcNow,
            Server = new EventServerInfo { Name = "s1", Id = "NSVR001" },
            AccountName = "$G",
            Connections = 5,
            TotalConnections = 20,
            NumSubscriptions = 15,
        };

        var json = JsonSerializer.Serialize(original, EventJsonContext.Default.AccountNumConns);
        var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.AccountNumConns);

        deserialized.ShouldNotBeNull();
        deserialized!.AccountName.ShouldBe("$G");
        deserialized.Connections.ShouldBe(5);
        deserialized.TotalConnections.ShouldBe(20);
    }

    [Fact]
    public void AuthErrorEventMsg_RoundTrip_ViaContext()
    {
        var original = new AuthErrorEventMsg
        {
            Id = "roundtrip-005",
            Time = DateTime.UtcNow,
            Server = new EventServerInfo { Name = "s1", Id = "NSVR001" },
            Client = new EventClientInfo { Id = 99, Host = "10.0.0.1" },
            Reason = "Authorization Violation",
        };

        var json = JsonSerializer.Serialize(original, EventJsonContext.Default.AuthErrorEventMsg);
        var deserialized = JsonSerializer.Deserialize(json, EventJsonContext.Default.AuthErrorEventMsg);

        deserialized.ShouldNotBeNull();
        deserialized!.Reason.ShouldBe("Authorization Violation");
        deserialized.Type.ShouldBe(AuthErrorEventMsg.EventType);
    }

    // ========================================================================
    // Event subject $SYS prefix validation
    // Go reference: events.go — all system subjects start with $SYS
    // ========================================================================

    [Fact]
    public void AllEventSubjects_StartWithSysDollarPrefix()
    {
        // Go: All system event subjects must start with $SYS.
        EventSubjects.ConnectEvent.ShouldStartWith("$SYS.");
        EventSubjects.DisconnectEvent.ShouldStartWith("$SYS.");
        EventSubjects.AccountConnsNew.ShouldStartWith("$SYS.");
        EventSubjects.AccountConnsOld.ShouldStartWith("$SYS.");
        EventSubjects.ServerStats.ShouldStartWith("$SYS.");
        EventSubjects.ServerShutdown.ShouldStartWith("$SYS.");
        EventSubjects.ServerLameDuck.ShouldStartWith("$SYS.");
        EventSubjects.AuthError.ShouldStartWith("$SYS.");
        EventSubjects.AuthErrorAccount.ShouldStartWith("$SYS.");
        EventSubjects.ServerPing.ShouldStartWith("$SYS.");
        EventSubjects.ServerReq.ShouldStartWith("$SYS.");
        EventSubjects.AccountReq.ShouldStartWith("$SYS.");
        EventSubjects.InboxResponse.ShouldStartWith("$SYS.");
    }
}
