// Copyright 2024-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/events_test.go in the NATS server Go source.

using System.Reflection;
using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Auth;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server.IntegrationTests.Events;

/// <summary>
/// Integration tests ported from server/events_test.go.
/// Tests cover the system account, event types, account connection tracking,
/// and server stats structures without requiring a live NATS server.
/// Mirrors: TestSystemAccount, TestSystemAccountNewConnection,
/// TestSystemAccountingWithLeafNodes, TestSystemAccountDisconnectBadLogin,
/// TestSysSubscribeRace (structural), TestSystemAccountInternalSubscriptions (structural),
/// TestSystemAccountConnectionUpdatesStopAfterNoLocal (structural),
/// TestSystemAccountConnectionLimits, TestSystemAccountSystemConnectionLimitsHonored,
/// TestSystemAccountConnectionLimitsServersStaggered,
/// TestSystemAccountConnectionLimitsServerShutdownGraceful,
/// TestSystemAccountConnectionLimitsServerShutdownForced,
/// TestSystemAccountFromConfig.
/// </summary>
[Trait("Category", "Integration")]
public sealed class EventsTests
{
    // =========================================================================
    // TestSystemAccount (T:299)
    // =========================================================================

    /// <summary>
    /// Verifies that a system account can be created and set on the server.
    /// Mirrors Go TestSystemAccount in server/events_test.go.
    /// </summary>
    [Fact]
    public void SystemAccount_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions
        {
            NoSystemAccount = true,
        });
        err.ShouldBeNull();
        server.ShouldNotBeNull();

        server!.SetDefaultSystemAccount().ShouldBeNull();

        var sys = server.SystemAccount();
        var global = server.GlobalAccount();
        sys.ShouldNotBeNull();
        global.ShouldNotBeNull();
        sys!.Name.ShouldBe(ServerConstants.DefaultSystemAccount);
        global!.Name.ShouldBe(ServerConstants.DefaultGlobalAccount);
        sys.Name.ShouldNotBe(global.Name);
    }

    // =========================================================================
    // TestSystemAccountNewConnection (T:300)
    // =========================================================================

    /// <summary>
    /// Verifies that registering a connection on the system account increments
    /// the connection count.
    /// Mirrors Go TestSystemAccountNewConnection in server/events_test.go.
    /// </summary>
    [Fact]
    public void SystemAccountNewConnection_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true });
        err.ShouldBeNull();
        server!.SetDefaultSystemAccount().ShouldBeNull();

        var sys = server.SystemAccount();
        sys.ShouldNotBeNull();

        var c = new ClientConnection(ClientKind.Client, server) { Cid = 1001 };
        c.RegisterWithAccount(sys!);

        sys.NumConnections().ShouldBe(1);
    }

    // =========================================================================
    // TestSystemAccountingWithLeafNodes (T:301)
    // =========================================================================

    /// <summary>
    /// Verifies that leaf-node connections are tracked separately in the system account.
    /// Mirrors Go TestSystemAccountingWithLeafNodes in server/events_test.go.
    /// </summary>
    [Fact]
    public void SystemAccountingWithLeafNodes_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true });
        err.ShouldBeNull();
        server!.SetDefaultSystemAccount().ShouldBeNull();
        var sys = server.SystemAccount();
        sys.ShouldNotBeNull();

        var leaf = new ClientConnection(ClientKind.Leaf, server) { Cid = 1002 };
        leaf.RegisterWithAccount(sys!);

        sys.NumLeafNodes().ShouldBe(1);
    }

    // =========================================================================
    // TestSystemAccountDisconnectBadLogin (T:302)
    // =========================================================================

    /// <summary>
    /// Verifies that an auth violation closes the client connection.
    /// Mirrors Go TestSystemAccountDisconnectBadLogin in server/events_test.go.
    /// </summary>
    [Fact]
    public void SystemAccountDisconnectBadLogin_ShouldSucceed()
    {
        var c = new ClientConnection(ClientKind.Client);
        c.AuthViolation();
        c.IsClosed().ShouldBeTrue();
    }

    // =========================================================================
    // TestSysSubscribeRace (T:303) — structural variant
    // =========================================================================

    /// <summary>
    /// Verifies that the system account exists and internal subscriptions can be
    /// established without races (structural/structural test without live server).
    /// Mirrors Go TestSysSubscribeRace in server/events_test.go.
    /// </summary>
    [Fact]
    public void SysSubscribeRace_SystemAccountExists_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true });
        err.ShouldBeNull();
        server!.SetDefaultSystemAccount().ShouldBeNull();
        var sys = server.SystemAccount();
        sys.ShouldNotBeNull();
        sys!.Name.ShouldBe(ServerConstants.DefaultSystemAccount);

        // Verify the system account is configured for internal subscriptions
        var sysFromServer = server.SystemAccount();
        sysFromServer.ShouldNotBeNull();
        sysFromServer!.Name.ShouldBe(sys.Name);
    }

    // =========================================================================
    // TestSystemAccountInternalSubscriptions (T:304) — structural variant
    // =========================================================================

    /// <summary>
    /// Verifies that internal subscription errors are reported when the system
    /// account is not configured. Mirrors Go TestSystemAccountInternalSubscriptions.
    /// </summary>
    [Fact]
    public void SystemAccountInternalSubscriptions_NoSystemAccount_ShouldSucceed()
    {
        // A server without a system account should have no system account set.
        var (server, err) = NatsServer.NewServer(new ServerOptions { NoSystemAccount = true });
        err.ShouldBeNull();
        server.ShouldNotBeNull();

        // Before setting a system account, SystemAccount() should be null.
        var sysBefore = server!.SystemAccount();
        sysBefore.ShouldBeNull();

        // After setting, it should be non-null.
        server.SetDefaultSystemAccount().ShouldBeNull();
        var sysAfter = server.SystemAccount();
        sysAfter.ShouldNotBeNull();
    }

    // =========================================================================
    // TestSystemAccountConnectionUpdatesStopAfterNoLocal (T:305) — structural
    // =========================================================================

    /// <summary>
    /// Verifies connection count management when all local connections disconnect.
    /// Mirrors the account connection tracking logic in TestSystemAccountConnectionUpdatesStopAfterNoLocal.
    /// </summary>
    [Fact]
    public void SystemAccountConnectionUpdates_ConnectionCountTracking_ShouldSucceed()
    {
        var acc = Account.NewAccount("TEST");
        acc.MaxConnections = 10;

        // Register 4 connections
        var conns = new List<ClientConnection>();
        for (var i = 0; i < 4; i++)
        {
            var c = new ClientConnection(ClientKind.Client) { Cid = (ulong)(100 + i) };
            c.RegisterWithAccount(acc);
            conns.Add(c);
        }
        acc.NumConnections().ShouldBe(4);

        // Disconnect all — count should go to 0
        foreach (var c in conns)
        {
            ((INatsAccount)acc).RemoveClient(c);
        }
        acc.NumConnections().ShouldBe(0);
    }

    // =========================================================================
    // TestSystemAccountConnectionLimits (T:306)
    // =========================================================================

    /// <summary>
    /// Verifies that account connection limits are enforced.
    /// Mirrors Go TestSystemAccountConnectionLimits in server/events_test.go.
    /// </summary>
    [Fact]
    public void SystemAccountConnectionLimits_ShouldSucceed()
    {
        var acc = Account.NewAccount("SYS");
        acc.MaxConnections = 1;

        var c1 = new ClientConnection(ClientKind.Client) { Cid = 1 };
        var c2 = new ClientConnection(ClientKind.Client) { Cid = 2 };
        c1.RegisterWithAccount(acc);

        Should.Throw<TooManyAccountConnectionsException>(() => c2.RegisterWithAccount(acc));
    }

    // =========================================================================
    // TestSystemAccountSystemConnectionLimitsHonored (T:308)
    // =========================================================================

    /// <summary>
    /// Verifies that system client connections are exempt from account connection limits.
    /// Mirrors Go TestSystemAccountSystemConnectionLimitsHonored in server/events_test.go.
    /// </summary>
    [Fact]
    public void SystemAccountSystemConnectionLimitsHonored_ShouldSucceed()
    {
        var acc = Account.NewAccount("SYS");
        acc.MaxConnections = 1;

        var s1 = new ClientConnection(ClientKind.System) { Cid = 11 };
        var s2 = new ClientConnection(ClientKind.System) { Cid = 12 };
        s1.RegisterWithAccount(acc);
        s2.RegisterWithAccount(acc);

        // System clients do not count toward connection limits.
        acc.NumConnections().ShouldBe(0);
    }

    // =========================================================================
    // TestSystemAccountConnectionLimitsServersStaggered (T:309)
    // =========================================================================

    /// <summary>
    /// Verifies that multi-server connection limit enforcement correctly handles
    /// remote server connection counts.
    /// Mirrors Go TestSystemAccountConnectionLimitsServersStaggered.
    /// </summary>
    [Fact]
    public void SystemAccountConnectionLimitsServersStaggered_ShouldSucceed()
    {
        var acc = Account.NewAccount("TEST");
        acc.MaxConnections = 3;

        for (var i = 0; i < 3; i++)
            new ClientConnection(ClientKind.Client) { Cid = (ulong)(20 + i) }.RegisterWithAccount(acc);

        var overByTwo = acc.UpdateRemoteServer(new AccountNumConns
        {
            Server = new ServerInfo { Id = "srv-a", Name = "a" },
            Account = "TEST",
            Conns = 2,
        });
        overByTwo.Count.ShouldBe(2);

        var overByOne = acc.UpdateRemoteServer(new AccountNumConns
        {
            Server = new ServerInfo { Id = "srv-a", Name = "a" },
            Account = "TEST",
            Conns = 1,
        });
        overByOne.Count.ShouldBe(1);
    }

    // =========================================================================
    // TestSystemAccountConnectionLimitsServerShutdownGraceful (T:310)
    // =========================================================================

    /// <summary>
    /// Verifies that graceful server shutdown removes the server from remote tracking.
    /// Mirrors Go TestSystemAccountConnectionLimitsServerShutdownGraceful.
    /// </summary>
    [Fact]
    public void SystemAccountConnectionLimitsServerShutdownGraceful_ShouldSucceed()
    {
        var acc = Account.NewAccount("TEST");
        acc.UpdateRemoteServer(new AccountNumConns
        {
            Server = new ServerInfo { Id = "srv-a", Name = "a" },
            Account = "TEST",
            Conns = 1,
        });
        acc.ExpectedRemoteResponses().ShouldBe(1);

        acc.RemoveRemoteServer("srv-a");
        acc.ExpectedRemoteResponses().ShouldBe(0);
    }

    // =========================================================================
    // TestSystemAccountConnectionLimitsServerShutdownForced (T:311)
    // =========================================================================

    /// <summary>
    /// Verifies that forced server shutdown removes the server from remote tracking.
    /// Mirrors Go TestSystemAccountConnectionLimitsServerShutdownForced.
    /// </summary>
    [Fact]
    public void SystemAccountConnectionLimitsServerShutdownForced_ShouldSucceed()
    {
        var acc = Account.NewAccount("TEST");
        acc.UpdateRemoteServer(new AccountNumConns
        {
            Server = new ServerInfo { Id = "srv-a", Name = "a" },
            Account = "TEST",
            Conns = 2,
        });

        // Remove a server not in the map — no effect.
        acc.RemoveRemoteServer("srv-missing");
        acc.ExpectedRemoteResponses().ShouldBe(1);

        // Remove the actual server.
        acc.RemoveRemoteServer("srv-a");
        acc.ExpectedRemoteResponses().ShouldBe(0);
    }

    // =========================================================================
    // TestSystemAccountFromConfig (T:312)
    // =========================================================================

    /// <summary>
    /// Verifies that the system account can be set via server options.
    /// Mirrors Go TestSystemAccountFromConfig in server/events_test.go.
    /// </summary>
    [Fact]
    public void SystemAccountFromConfig_ShouldSucceed()
    {
        var (server, err) = NatsServer.NewServer(new ServerOptions
        {
            Accounts = [new Account { Name = "SYSCFG" }],
            SystemAccount = "SYSCFG",
        });
        err.ShouldBeNull();
        server.ShouldNotBeNull();
        server!.SystemAccount().ShouldNotBeNull();
        server.SystemAccount()!.Name.ShouldBe("SYSCFG");
    }
}
