using NATS.Server.Monitoring;

namespace NATS.Server.Monitoring.Tests.Monitoring;

/// <summary>
/// Unit tests for account-scoped /connz filtering (Gap 10.2).
/// Exercises ConnzFilterOptions.Parse, ConnzFilter.FilterByAccount, and
/// ConnzFilter.ApplyFilters in isolation — no running server required.
/// Go reference: monitor_test.go — TestConnzFilterByAccount, TestConnzWithAccount.
/// </summary>
public class ConnzAccountFilterTests
{
    // -----------------------------------------------------------------------
    // Helper — build a ConnzConnectionInfo with sensible defaults
    // -----------------------------------------------------------------------

    private static ConnzConnectionInfo MakeConn(
        ulong clientId,
        string? accountName,
        string remoteAddress = "127.0.0.1:1234",
        string? name = null,
        long inMsgs = 0,
        long outMsgs = 0,
        long inBytes = 0,
        long outBytes = 0) =>
        new(clientId, remoteAddress, accountName, name, DateTime.UtcNow,
            inMsgs, outMsgs, inBytes, outBytes);

    // -----------------------------------------------------------------------
    // FilterByAccount tests
    // -----------------------------------------------------------------------

    [Fact]
    public void FilterByAccount_MatchingAccount_ReturnsFiltered()
    {
        // Go reference: monitor.go Connz() — "if opts.Account != "" { ... }" filter
        var connections = new[]
        {
            MakeConn(1, "acctA"),
            MakeConn(2, "acctB"),
            MakeConn(3, "acctA"),
        };

        var result = ConnzFilter.FilterByAccount(connections, "acctA");

        result.Count.ShouldBe(2);
        result.ShouldAllBe(c => c.AccountName == "acctA");
    }

    [Fact]
    public void FilterByAccount_NoMatch_ReturnsEmpty()
    {
        var connections = new[]
        {
            MakeConn(1, "acctA"),
            MakeConn(2, "acctB"),
        };

        var result = ConnzFilter.FilterByAccount(connections, "acctC");

        result.ShouldBeEmpty();
    }

    [Fact]
    public void FilterByAccount_CaseInsensitive()
    {
        // Go reference: monitor.go — account name comparison is case-insensitive
        var connections = new[]
        {
            MakeConn(1, "AcctA"),
            MakeConn(2, "acctb"),
        };

        // Upper-case query against lower-case stored name
        var lowerResult = ConnzFilter.FilterByAccount(connections, "accta");
        lowerResult.Count.ShouldBe(1);
        lowerResult[0].ClientId.ShouldBe(1UL);

        // Lower-case query against mixed-case stored name
        var upperResult = ConnzFilter.FilterByAccount(connections, "ACCTB");
        upperResult.Count.ShouldBe(1);
        upperResult[0].ClientId.ShouldBe(2UL);
    }

    // -----------------------------------------------------------------------
    // ConnzFilterOptions.Parse tests
    // -----------------------------------------------------------------------

    [Fact]
    public void Parse_WithAccParam_SetsAccountFilter()
    {
        var opts = ConnzFilterOptions.Parse("?acc=myAccount");

        opts.AccountFilter.ShouldBe("myAccount");
    }

    [Fact]
    public void Parse_WithoutParams_DefaultValues()
    {
        var opts = ConnzFilterOptions.Parse(null);

        opts.AccountFilter.ShouldBeNull();
        opts.StateFilter.ShouldBeNull();
        opts.Offset.ShouldBe(0);
        opts.Limit.ShouldBe(1024);
    }

    [Fact]
    public void Parse_WithOffsetAndLimit_ParsesCorrectly()
    {
        var opts = ConnzFilterOptions.Parse("?acc=acctA&state=open&offset=10&limit=50");

        opts.AccountFilter.ShouldBe("acctA");
        opts.StateFilter.ShouldBe("open");
        opts.Offset.ShouldBe(10);
        opts.Limit.ShouldBe(50);
    }

    // -----------------------------------------------------------------------
    // ApplyFilters tests
    // -----------------------------------------------------------------------

    [Fact]
    public void ApplyFilters_WithAccountFilter_FiltersCorrectly()
    {
        var connections = new[]
        {
            MakeConn(1, "acctA"),
            MakeConn(2, "acctB"),
            MakeConn(3, "acctA"),
        };

        var opts = new ConnzFilterOptions { AccountFilter = "acctA" };
        var result = ConnzFilter.ApplyFilters(connections, opts);

        result.Total.ShouldBe(2);
        result.Connections.Count.ShouldBe(2);
        result.Connections.ShouldAllBe(c => c.AccountName == "acctA");
    }

    [Fact]
    public void ApplyFilters_WithOffset_SkipsEntries()
    {
        var connections = Enumerable.Range(1, 5)
            .Select(i => MakeConn((ulong)i, "acctA"))
            .ToList();

        var opts = new ConnzFilterOptions { Offset = 3, Limit = 10 };
        var result = ConnzFilter.ApplyFilters(connections, opts);

        result.Total.ShouldBe(5);
        result.Connections.Count.ShouldBe(2);
        result.Offset.ShouldBe(3);
        // The paged items should be the last two (clientId 4 and 5)
        result.Connections[0].ClientId.ShouldBe(4UL);
        result.Connections[1].ClientId.ShouldBe(5UL);
    }

    [Fact]
    public void ApplyFilters_WithLimit_CapsResults()
    {
        var connections = Enumerable.Range(1, 10)
            .Select(i => MakeConn((ulong)i, "acctA"))
            .ToList();

        var opts = new ConnzFilterOptions { Limit = 3 };
        var result = ConnzFilter.ApplyFilters(connections, opts);

        result.Total.ShouldBe(10);
        result.Connections.Count.ShouldBe(3);
        result.Limit.ShouldBe(3);
    }

    [Fact]
    public void ApplyFilters_NoFilters_ReturnsAll()
    {
        var connections = new[]
        {
            MakeConn(1, "acctA"),
            MakeConn(2, "acctB"),
            MakeConn(3, null),
        };

        var opts = new ConnzFilterOptions();
        var result = ConnzFilter.ApplyFilters(connections, opts);

        result.Total.ShouldBe(3);
        result.Connections.Count.ShouldBe(3);
    }
}
