// Port of Go server/accounts_test.go — TestSystemAccountDefaultCreation,
// TestSystemAccountSysSubjectRouting, TestNonSystemAccountCannotSubscribeToSys.
// Reference: golang/nats-server/server/accounts_test.go, server.go — initSystemAccount.

using System.Net;
using System.Net.Sockets;
using System.Text;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Auth;

using NATS.Server.TestUtilities;

namespace NATS.Server.Auth.Tests.Auth;

/// <summary>
/// Tests for the $SYS system account functionality including:
///   - Default system account creation with IsSystemAccount flag
///   - $SYS.> subject routing to the system account's SubList
///   - Non-system accounts blocked from subscribing to $SYS.> subjects
///   - System account event publishing
/// Reference: Go server/accounts.go — isSystemAccount, isReservedSubject.
/// </summary>
public class SystemAccountTests
{
    // ─── Helpers ────────────────────────────────────────────────────────────

    private static async Task<(NatsServer server, int port, CancellationTokenSource cts)> StartServerAsync(NatsOptions options)
    {
        var port = TestPortAllocator.GetFreePort();
        options.Port = port;
        var server = new NatsServer(options, NullLoggerFactory.Instance);
        var cts = new CancellationTokenSource();
        _ = server.StartAsync(cts.Token);
        await server.WaitForReadyAsync();
        return (server, port, cts);
    }

    // ─── Tests ──────────────────────────────────────────────────────────────

    /// <summary>
    /// Verifies that the server creates a $SYS system account by default with
    /// IsSystemAccount set to true.
    /// Reference: Go server/server.go — initSystemAccount.
    /// </summary>
    [Fact]
    public void Default_system_account_is_created()
    {
        var options = new NatsOptions { Port = 0 };
        using var server = new NatsServer(options, NullLoggerFactory.Instance);

        server.SystemAccount.ShouldNotBeNull();
        server.SystemAccount.Name.ShouldBe(Account.SystemAccountName);
        server.SystemAccount.IsSystemAccount.ShouldBeTrue();
    }

    /// <summary>
    /// Verifies that the system account constant matches "$SYS".
    /// </summary>
    [Fact]
    public void System_account_name_constant_is_correct()
    {
        Account.SystemAccountName.ShouldBe("$SYS");
    }

    /// <summary>
    /// Verifies that a non-system account does not have IsSystemAccount set.
    /// </summary>
    [Fact]
    public void Regular_account_is_not_system_account()
    {
        var account = new Account("test-account");
        account.IsSystemAccount.ShouldBeFalse();
    }

    /// <summary>
    /// Verifies that IsSystemAccount can be explicitly set on an account.
    /// </summary>
    [Fact]
    public void IsSystemAccount_can_be_set()
    {
        var account = new Account("custom-sys") { IsSystemAccount = true };
        account.IsSystemAccount.ShouldBeTrue();
    }

    /// <summary>
    /// Verifies that IsSystemSubject correctly identifies $SYS subjects.
    /// Reference: Go server/server.go — isReservedSubject.
    /// </summary>
    [Theory]
    [InlineData("$SYS", true)]
    [InlineData("$SYS.ACCOUNT.test.CONNECT", true)]
    [InlineData("$SYS.SERVER.abc.STATSZ", true)]
    [InlineData("$SYS.REQ.SERVER.PING.VARZ", true)]
    [InlineData("foo.bar", false)]
    [InlineData("$G", false)]
    [InlineData("SYS.test", false)]
    [InlineData("$JS.API.STREAM.LIST", false)]
    [InlineData("$SYS.", true)]
    public void IsSystemSubject_identifies_sys_subjects(string subject, bool expected)
    {
        NatsServer.IsSystemSubject(subject).ShouldBe(expected);
    }

    /// <summary>
    /// Verifies that the system account is listed among server accounts.
    /// </summary>
    [Fact]
    public void System_account_is_in_server_accounts()
    {
        var options = new NatsOptions { Port = 0 };
        using var server = new NatsServer(options, NullLoggerFactory.Instance);

        var accounts = server.GetAccounts().ToList();
        accounts.ShouldContain(a => a.Name == Account.SystemAccountName && a.IsSystemAccount);
    }

    /// <summary>
    /// Verifies that IsSubscriptionAllowed blocks non-system accounts from $SYS.> subjects.
    /// Reference: Go server/accounts.go — isReservedForSys.
    /// </summary>
    [Fact]
    public void Non_system_account_cannot_subscribe_to_sys_subjects()
    {
        var options = new NatsOptions { Port = 0 };
        using var server = new NatsServer(options, NullLoggerFactory.Instance);

        var regularAccount = new Account("regular");

        server.IsSubscriptionAllowed(regularAccount, "$SYS.SERVER.abc.STATSZ").ShouldBeFalse();
        server.IsSubscriptionAllowed(regularAccount, "$SYS.ACCOUNT.test.CONNECT").ShouldBeFalse();
        server.IsSubscriptionAllowed(regularAccount, "$SYS.REQ.SERVER.PING.VARZ").ShouldBeFalse();
    }

    /// <summary>
    /// Verifies that the system account IS allowed to subscribe to $SYS.> subjects.
    /// </summary>
    [Fact]
    public void System_account_can_subscribe_to_sys_subjects()
    {
        var options = new NatsOptions { Port = 0 };
        using var server = new NatsServer(options, NullLoggerFactory.Instance);

        server.IsSubscriptionAllowed(server.SystemAccount, "$SYS.SERVER.abc.STATSZ").ShouldBeTrue();
        server.IsSubscriptionAllowed(server.SystemAccount, "$SYS.ACCOUNT.test.CONNECT").ShouldBeTrue();
    }

    /// <summary>
    /// Verifies that any account can subscribe to non-$SYS subjects.
    /// </summary>
    [Fact]
    public void Any_account_can_subscribe_to_regular_subjects()
    {
        var options = new NatsOptions { Port = 0 };
        using var server = new NatsServer(options, NullLoggerFactory.Instance);

        var regularAccount = new Account("regular");

        server.IsSubscriptionAllowed(regularAccount, "foo.bar").ShouldBeTrue();
        server.IsSubscriptionAllowed(regularAccount, "$JS.API.STREAM.LIST").ShouldBeTrue();
        server.IsSubscriptionAllowed(server.SystemAccount, "foo.bar").ShouldBeTrue();
    }

    /// <summary>
    /// Verifies that GetSubListForSubject routes $SYS subjects to the system account's SubList.
    /// Reference: Go server/server.go — sublist routing for internal subjects.
    /// </summary>
    [Fact]
    public void GetSubListForSubject_routes_sys_to_system_account()
    {
        var options = new NatsOptions { Port = 0 };
        using var server = new NatsServer(options, NullLoggerFactory.Instance);

        var globalAccount = server.GetOrCreateAccount(Account.GlobalAccountName);

        // $SYS subjects should route to the system account's SubList
        var sysList = server.GetSubListForSubject(globalAccount, "$SYS.SERVER.abc.STATSZ");
        sysList.ShouldBeSameAs(server.SystemAccount.SubList);

        // Regular subjects should route to the specified account's SubList
        var regularList = server.GetSubListForSubject(globalAccount, "foo.bar");
        regularList.ShouldBeSameAs(globalAccount.SubList);
    }

    /// <summary>
    /// Verifies that the EventSystem publishes to the system account's SubList
    /// and that internal subscriptions for monitoring are registered there.
    /// The subscriptions are wired up during StartAsync via InitEventTracking.
    /// </summary>
    [Fact]
    public async Task Event_system_subscribes_in_system_account()
    {
        var (server, _, cts) = await StartServerAsync(new NatsOptions());
        try
        {
            // The system account's SubList should have subscriptions registered
            // by the internal event system (VARZ, HEALTHZ, etc.)
            server.EventSystem.ShouldNotBeNull();
            server.SystemAccount.SubList.Count.ShouldBeGreaterThan(0u);
        }
        finally
        {
            await cts.CancelAsync();
            server.Dispose();
        }
    }

    /// <summary>
    /// Verifies that the global account is separate from the system account.
    /// </summary>
    [Fact]
    public void Global_and_system_accounts_are_separate()
    {
        var options = new NatsOptions { Port = 0 };
        using var server = new NatsServer(options, NullLoggerFactory.Instance);

        var globalAccount = server.GetOrCreateAccount(Account.GlobalAccountName);
        var systemAccount = server.SystemAccount;

        globalAccount.ShouldNotBeSameAs(systemAccount);
        globalAccount.Name.ShouldBe(Account.GlobalAccountName);
        systemAccount.Name.ShouldBe(Account.SystemAccountName);
        globalAccount.IsSystemAccount.ShouldBeFalse();
        systemAccount.IsSystemAccount.ShouldBeTrue();
        globalAccount.SubList.ShouldNotBeSameAs(systemAccount.SubList);
    }
}
