// Go: gateway.go:100-150 (InterestMode enum), gateway.go:1500-1600 (switchToInterestOnlyMode)
using NATS.Server.Gateways;

namespace NATS.Server.Gateways.Tests.Gateways;

/// <summary>
/// Unit tests for GatewayInterestTracker — the per-connection interest mode state machine.
/// Covers Optimistic/InterestOnly modes, threshold-based switching, and per-account isolation.
/// Go reference: gateway_test.go, TestGatewaySwitchToInterestOnlyModeImmediately (line 6934),
/// TestGatewayAccountInterest (line 1794), TestGatewayAccountUnsub (line 1912).
/// </summary>
public class GatewayInterestTrackerTests
{
    // Go: TestGatewayBasic server/gateway_test.go:399 — initial state is Optimistic
    [Fact]
    public void StartsInOptimisticMode()
    {
        var tracker = new GatewayInterestTracker();

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.Optimistic);
        tracker.GetMode("ACCT_A").ShouldBe(GatewayInterestMode.Optimistic);
        tracker.GetMode("ANY_ACCOUNT").ShouldBe(GatewayInterestMode.Optimistic);
    }

    // Go: TestGatewayBasic server/gateway_test.go:399 — optimistic mode forwards everything
    [Fact]
    public void OptimisticForwardsEverything()
    {
        var tracker = new GatewayInterestTracker();

        tracker.ShouldForward("$G", "any.subject").ShouldBeTrue();
        tracker.ShouldForward("$G", "orders.created").ShouldBeTrue();
        tracker.ShouldForward("$G", "deeply.nested.subject.path").ShouldBeTrue();
        tracker.ShouldForward("ACCT", "foo").ShouldBeTrue();
    }

    // Go: TestGatewayAccountUnsub server/gateway_test.go:1912 — RS- adds to no-interest
    [Fact]
    public void TrackNoInterest_AddsToNoInterestSet()
    {
        var tracker = new GatewayInterestTracker();

        tracker.TrackNoInterest("$G", "orders.created");

        // Should not forward that specific subject in Optimistic mode
        tracker.ShouldForward("$G", "orders.created").ShouldBeFalse();
        // Other subjects still forwarded
        tracker.ShouldForward("$G", "orders.updated").ShouldBeTrue();
        tracker.ShouldForward("$G", "payments.created").ShouldBeTrue();
    }

    // Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934 — threshold switch
    [Fact]
    public void SwitchesToInterestOnlyAfterThreshold()
    {
        const int threshold = 10;
        var tracker = new GatewayInterestTracker(noInterestThreshold: threshold);

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.Optimistic);

        // Add subjects up to (but not reaching) the threshold
        for (int i = 0; i < threshold - 1; i++)
            tracker.TrackNoInterest("$G", $"subject.{i}");

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.Optimistic);

        // One more crosses the threshold
        tracker.TrackNoInterest("$G", $"subject.{threshold - 1}");

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly);
    }

    // Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934
    [Fact]
    public void InterestOnlyMode_OnlyForwardsTrackedSubjects()
    {
        const int threshold = 5;
        var tracker = new GatewayInterestTracker(noInterestThreshold: threshold);

        // Trigger mode switch
        for (int i = 0; i < threshold; i++)
            tracker.TrackNoInterest("$G", $"noise.{i}");

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly);

        // Nothing forwarded until interest is explicitly tracked
        tracker.ShouldForward("$G", "orders.created").ShouldBeFalse();

        // Track a positive interest
        tracker.TrackInterest("$G", "orders.created");

        // Now only that subject is forwarded
        tracker.ShouldForward("$G", "orders.created").ShouldBeTrue();
        tracker.ShouldForward("$G", "orders.updated").ShouldBeFalse();
        tracker.ShouldForward("$G", "payments.done").ShouldBeFalse();
    }

    // Go: TestGatewaySubjectInterest server/gateway_test.go:1972 — wildcard interest in InterestOnly
    [Fact]
    public void InterestOnlyMode_SupportsWildcards()
    {
        const int threshold = 3;
        var tracker = new GatewayInterestTracker(noInterestThreshold: threshold);

        // Trigger InterestOnly mode
        for (int i = 0; i < threshold; i++)
            tracker.TrackNoInterest("$G", $"x.{i}");

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly);

        // Register a wildcard interest
        tracker.TrackInterest("$G", "foo.>");

        // Matching subjects are forwarded
        tracker.ShouldForward("$G", "foo.bar").ShouldBeTrue();
        tracker.ShouldForward("$G", "foo.bar.baz").ShouldBeTrue();
        tracker.ShouldForward("$G", "foo.anything.deep.nested").ShouldBeTrue();

        // Non-matching subjects are not forwarded
        tracker.ShouldForward("$G", "other.subject").ShouldBeFalse();
        tracker.ShouldForward("$G", "foo").ShouldBeFalse(); // "foo.>" requires at least one token after "foo"
    }

    // Go: TestGatewayAccountInterest server/gateway_test.go:1794 — per-account mode isolation
    [Fact]
    public void ModePerAccount()
    {
        const int threshold = 5;
        var tracker = new GatewayInterestTracker(noInterestThreshold: threshold);

        // Switch ACCT_A to InterestOnly
        for (int i = 0; i < threshold; i++)
            tracker.TrackNoInterest("ACCT_A", $"noise.{i}");

        tracker.GetMode("ACCT_A").ShouldBe(GatewayInterestMode.InterestOnly);

        // ACCT_B remains Optimistic
        tracker.GetMode("ACCT_B").ShouldBe(GatewayInterestMode.Optimistic);

        // ACCT_A blocks unknown subjects, ACCT_B forwards
        tracker.ShouldForward("ACCT_A", "orders.created").ShouldBeFalse();
        tracker.ShouldForward("ACCT_B", "orders.created").ShouldBeTrue();
    }

    // Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934
    [Fact]
    public void ModePersistsAfterSwitch()
    {
        const int threshold = 3;
        var tracker = new GatewayInterestTracker(noInterestThreshold: threshold);

        // Trigger switch
        for (int i = 0; i < threshold; i++)
            tracker.TrackNoInterest("$G", $"y.{i}");

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly);

        // TrackInterest in InterestOnly mode — mode stays InterestOnly
        tracker.TrackInterest("$G", "orders.created");
        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly);

        // TrackNoInterest in InterestOnly mode — mode stays InterestOnly
        tracker.TrackNoInterest("$G", "something.else");
        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly);
    }

    // Go: TestGatewayAccountInterest server/gateway_test.go:1794 — explicit SwitchToInterestOnly
    [Fact]
    public void ExplicitSwitchToInterestOnly_SetsMode()
    {
        var tracker = new GatewayInterestTracker();

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.Optimistic);

        tracker.SwitchToInterestOnly("$G");

        tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly);
    }

    // Go: TestGatewayAccountUnsub server/gateway_test.go:1912 — RS+ restores interest after RS-
    [Fact]
    public void TrackInterest_InOptimisticMode_RemovesFromNoInterestSet()
    {
        var tracker = new GatewayInterestTracker();

        // Mark no interest
        tracker.TrackNoInterest("$G", "orders.created");
        tracker.ShouldForward("$G", "orders.created").ShouldBeFalse();

        // Remote re-subscribes — track interest again
        tracker.TrackInterest("$G", "orders.created");
        tracker.ShouldForward("$G", "orders.created").ShouldBeTrue();
    }

    // Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934
    [Fact]
    public void InterestOnlyMode_TrackNoInterest_RemovesFromInterestSet()
    {
        const int threshold = 3;
        var tracker = new GatewayInterestTracker(noInterestThreshold: threshold);

        // Trigger InterestOnly
        for (int i = 0; i < threshold; i++)
            tracker.TrackNoInterest("$G", $"z.{i}");

        tracker.TrackInterest("$G", "orders.created");
        tracker.ShouldForward("$G", "orders.created").ShouldBeTrue();

        // Remote unsubscribes — subject removed from interest set
        tracker.TrackNoInterest("$G", "orders.created");
        tracker.ShouldForward("$G", "orders.created").ShouldBeFalse();
    }

    // Go: TestGatewaySubjectInterest server/gateway_test.go:1972 — pwc wildcard in InterestOnly
    [Fact]
    public void InterestOnlyMode_SupportsPwcWildcard()
    {
        const int threshold = 3;
        var tracker = new GatewayInterestTracker(noInterestThreshold: threshold);

        for (int i = 0; i < threshold; i++)
            tracker.TrackNoInterest("$G", $"n.{i}");

        tracker.TrackInterest("$G", "orders.*");

        tracker.ShouldForward("$G", "orders.created").ShouldBeTrue();
        tracker.ShouldForward("$G", "orders.deleted").ShouldBeTrue();
        tracker.ShouldForward("$G", "orders.deep.nested").ShouldBeFalse(); // * is single token
        tracker.ShouldForward("$G", "payments.created").ShouldBeFalse();
    }

    // Go: TestGatewayAccountInterest server/gateway_test.go:1794 — unknown account defaults optimistic
    [Fact]
    public void UnknownAccount_DefaultsToOptimisticForwarding()
    {
        var tracker = new GatewayInterestTracker();

        // Account never seen — should forward everything
        tracker.ShouldForward("BRAND_NEW_ACCOUNT", "any.subject").ShouldBeTrue();
    }
}
