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

namespace NATS.Server.Monitoring.Tests.Events;

/// <summary>
/// Tests that EventBuilder produces fully-populated system event messages
/// and that all field contracts are met.
/// Go reference: events.go sendConnectEventMsg, sendDisconnectEventMsg,
/// sendAccountNumConns, sendStatsz — Gap 10.6.
/// </summary>
public class FullEventPayloadTests
{
    // ========================================================================
    // BuildConnectEvent
    // ========================================================================

    [Fact]
    public void BuildConnectEvent_AllFieldsPopulated()
    {
        // Go reference: events.go sendConnectEventMsg — all client fields must be set.
        var evt = EventBuilder.BuildConnectEvent(
            serverId: "SRV-001",
            serverName: "test-server",
            cluster: "east-cluster",
            clientId: 42,
            host: "10.0.0.1",
            account: "$G",
            user: "alice",
            name: "my-client",
            lang: "csharp",
            version: "1.0.0");

        evt.ShouldNotBeNull();
        evt.Id.ShouldNotBeNullOrEmpty();
        evt.Time.ShouldBeGreaterThan(DateTime.MinValue);
        evt.Server.Id.ShouldBe("SRV-001");
        evt.Server.Name.ShouldBe("test-server");
        evt.Server.Cluster.ShouldBe("east-cluster");
        evt.Client.Id.ShouldBe(42UL);
        evt.Client.Host.ShouldBe("10.0.0.1");
        evt.Client.Account.ShouldBe("$G");
        evt.Client.User.ShouldBe("alice");
        evt.Client.Name.ShouldBe("my-client");
        evt.Client.Lang.ShouldBe("csharp");
        evt.Client.Version.ShouldBe("1.0.0");
        evt.Client.Start.ShouldBeGreaterThan(DateTime.MinValue);
    }

    [Fact]
    public void BuildConnectEvent_HasCorrectEventType()
    {
        // Go reference: events.go — connect advisory type constant.
        var evt = EventBuilder.BuildConnectEvent(
            serverId: "SRV-001",
            serverName: "test-server",
            cluster: null,
            clientId: 1,
            host: "127.0.0.1",
            account: null,
            user: null,
            name: null,
            lang: null,
            version: null);

        evt.Type.ShouldBe(ConnectEventMsg.EventType);
        evt.Type.ShouldBe("io.nats.server.advisory.v1.client_connect");
    }

    // ========================================================================
    // BuildDisconnectEvent
    // ========================================================================

    [Fact]
    public void BuildDisconnectEvent_AllFieldsPopulated()
    {
        // Go reference: events.go sendDisconnectEventMsg — sent, received, and reason required.
        var sent = new DataStats { Msgs = 100, Bytes = 2_048 };
        var received = new DataStats { Msgs = 50, Bytes = 1_024 };

        var evt = EventBuilder.BuildDisconnectEvent(
            serverId: "SRV-002",
            serverName: "test-server",
            cluster: "west-cluster",
            clientId: 99,
            host: "192.168.1.5",
            account: "MYACC",
            user: "bob",
            reason: "Client Closed",
            sent: sent,
            received: received);

        evt.ShouldNotBeNull();
        evt.Id.ShouldNotBeNullOrEmpty();
        evt.Time.ShouldBeGreaterThan(DateTime.MinValue);
        evt.Server.Id.ShouldBe("SRV-002");
        evt.Server.Name.ShouldBe("test-server");
        evt.Server.Cluster.ShouldBe("west-cluster");
        evt.Client.Id.ShouldBe(99UL);
        evt.Client.Host.ShouldBe("192.168.1.5");
        evt.Client.Account.ShouldBe("MYACC");
        evt.Client.User.ShouldBe("bob");
        evt.Client.Stop.ShouldBeGreaterThan(DateTime.MinValue);
        evt.Reason.ShouldBe("Client Closed");
        evt.Sent.Msgs.ShouldBe(100);
        evt.Sent.Bytes.ShouldBe(2_048);
        evt.Received.Msgs.ShouldBe(50);
        evt.Received.Bytes.ShouldBe(1_024);
    }

    [Fact]
    public void BuildDisconnectEvent_HasCorrectEventType()
    {
        // Go reference: events.go — disconnect advisory type constant.
        var evt = EventBuilder.BuildDisconnectEvent(
            serverId: "SRV-002",
            serverName: "test-server",
            cluster: null,
            clientId: 1,
            host: "127.0.0.1",
            account: null,
            user: null,
            reason: "Server Closed",
            sent: new DataStats(),
            received: new DataStats());

        evt.Type.ShouldBe(DisconnectEventMsg.EventType);
        evt.Type.ShouldBe("io.nats.server.advisory.v1.client_disconnect");
    }

    // ========================================================================
    // BuildAccountConnsEvent
    // ========================================================================

    [Fact]
    public void BuildAccountConnsEvent_AllFieldsPopulated()
    {
        // Go reference: events.go sendAccountNumConns — all AccountStat fields must transfer.
        var sent = new DataStats { Msgs = 500, Bytes = 10_000 };
        var received = new DataStats { Msgs = 400, Bytes = 8_000 };

        var evt = EventBuilder.BuildAccountConnsEvent(
            serverId: "SRV-003",
            serverName: "test-server",
            accountName: "ACCT-A",
            connections: 7,
            leafNodes: 2,
            totalConnections: 150,
            numSubscriptions: 33,
            sent: sent,
            received: received,
            slowConsumers: 3);

        evt.ShouldNotBeNull();
        evt.Id.ShouldNotBeNullOrEmpty();
        evt.Time.ShouldBeGreaterThan(DateTime.MinValue);
        evt.Server.Id.ShouldBe("SRV-003");
        evt.Server.Name.ShouldBe("test-server");
        evt.AccountName.ShouldBe("ACCT-A");
        evt.Connections.ShouldBe(7);
        evt.LeafNodes.ShouldBe(2);
        evt.TotalConnections.ShouldBe(150);
        evt.NumSubscriptions.ShouldBe(33u);
        evt.Sent.Msgs.ShouldBe(500);
        evt.Received.Bytes.ShouldBe(8_000);
        evt.SlowConsumers.ShouldBe(3);
        evt.Type.ShouldBe(AccountNumConns.EventType);
    }

    // ========================================================================
    // BuildServerStats
    // ========================================================================

    [Fact]
    public void BuildServerStats_AllFieldsPopulated()
    {
        // Go reference: events.go sendStatsz — all stat fields must be set.
        var sent = new DataStats { Msgs = 1_000, Bytes = 50_000 };
        var received = new DataStats { Msgs = 800, Bytes = 40_000 };

        var msg = EventBuilder.BuildServerStats(
            serverId: "SRV-004",
            serverName: "stats-server",
            mem: 134_217_728,
            cores: 8,
            cpu: 15.5,
            connections: 12,
            totalConnections: 600,
            activeAccounts: 4,
            subscriptions: 55,
            sent: sent,
            received: received);

        msg.ShouldNotBeNull();
        msg.Server.Id.ShouldBe("SRV-004");
        msg.Server.Name.ShouldBe("stats-server");
        msg.Stats.Mem.ShouldBe(134_217_728);
        msg.Stats.Cores.ShouldBe(8);
        msg.Stats.Cpu.ShouldBe(15.5);
        msg.Stats.Connections.ShouldBe(12);
        msg.Stats.TotalConnections.ShouldBe(600);
        msg.Stats.ActiveAccounts.ShouldBe(4);
        msg.Stats.Subscriptions.ShouldBe(55);
        msg.Stats.Sent.Msgs.ShouldBe(1_000);
        msg.Stats.Received.Bytes.ShouldBe(40_000);
        // Flat counters mirror the structured stats
        msg.Stats.OutMsgs.ShouldBe(1_000);
        msg.Stats.InMsgs.ShouldBe(800);
        msg.Stats.OutBytes.ShouldBe(50_000);
        msg.Stats.InBytes.ShouldBe(40_000);
        msg.Stats.Start.ShouldBeGreaterThan(DateTime.MinValue);
    }

    // ========================================================================
    // GenerateEventId
    // ========================================================================

    [Fact]
    public void GenerateEventId_UniquePerCall()
    {
        // Go reference: events.go uses nuid.Next() — every call produces a distinct ID.
        var ids = Enumerable.Range(0, 20).Select(_ => EventBuilder.GenerateEventId()).ToList();

        ids.Distinct().Count().ShouldBe(20);
        foreach (var id in ids)
        {
            id.ShouldNotBeNullOrEmpty();
            id.Length.ShouldBe(32); // Guid.ToString("N") is always 32 hex chars
        }
    }

    // ========================================================================
    // GetTimestamp
    // ========================================================================

    [Fact]
    public void GetTimestamp_ReturnsIso8601()
    {
        // Go reference: events use RFC3339Nano timestamps; .NET "O" format is ISO 8601.
        var ts = EventBuilder.GetTimestamp();

        ts.ShouldNotBeNullOrEmpty();
        // "O" format: 2025-02-25T12:34:56.7890000Z  — parseable as UTC DateTimeOffset
        var parsed = DateTimeOffset.Parse(ts);
        parsed.Year.ShouldBeGreaterThanOrEqualTo(2024);
    }

    // ========================================================================
    // DataStats default values
    // ========================================================================

    [Fact]
    public void DataStats_DefaultZeroValues()
    {
        // Go reference: zero-value DataStats is valid and all fields default to 0.
        var ds = new DataStats();

        ds.Msgs.ShouldBe(0L);
        ds.Bytes.ShouldBe(0L);
        ds.Gateways.ShouldBeNull();
        ds.Routes.ShouldBeNull();
        ds.Leafs.ShouldBeNull();
    }

    // ========================================================================
    // JSON roundtrip
    // ========================================================================

    [Fact]
    public void ConnectEventMsg_SerializesToJson()
    {
        // Go reference: events.go — connect event serializes to JSON and round-trips.
        var original = EventBuilder.BuildConnectEvent(
            serverId: "SRV-RT",
            serverName: "roundtrip-server",
            cluster: "rt-cluster",
            clientId: 77,
            host: "10.1.2.3",
            account: "RTACC",
            user: "rtuser",
            name: "rt-client",
            lang: "dotnet",
            version: "2.0.0");

        var json = JsonSerializer.Serialize(original);

        json.ShouldNotBeNullOrEmpty();
        json.ShouldContain("\"type\":");
        json.ShouldContain(ConnectEventMsg.EventType);
        json.ShouldContain("\"server\":");
        json.ShouldContain("\"client\":");
        json.ShouldContain("\"SRV-RT\"");

        var deserialized = JsonSerializer.Deserialize<ConnectEventMsg>(json);

        deserialized.ShouldNotBeNull();
        deserialized!.Type.ShouldBe(ConnectEventMsg.EventType);
        deserialized.Id.ShouldBe(original.Id);
        deserialized.Server.Id.ShouldBe("SRV-RT");
        deserialized.Server.Name.ShouldBe("roundtrip-server");
        deserialized.Server.Cluster.ShouldBe("rt-cluster");
        deserialized.Client.Id.ShouldBe(77UL);
        deserialized.Client.Account.ShouldBe("RTACC");
        deserialized.Client.User.ShouldBe("rtuser");
        deserialized.Client.Lang.ShouldBe("dotnet");
        deserialized.Client.Version.ShouldBe("2.0.0");
    }
}
