// Go reference: jetstream_api.go:200-300 — API requests at non-leader nodes must be
// forwarded to the current leader. Mutating operations return a not-leader error with
// a leader_hint field; read-only operations are handled locally on any node.

using System.Text;
using NATS.Server.JetStream;
using NATS.Server.JetStream.Api;
using NATS.Server.JetStream.Cluster;

namespace NATS.Server.Tests.JetStream.Api;

public class LeaderForwardingTests
{
    /// <summary>
    /// When this node IS the leader, mutating requests are handled locally.
    /// Go reference: jetstream_api.go — leader handles requests directly.
    /// </summary>
    [Fact]
    public void Route_WhenLeader_HandlesLocally()
    {
        // selfIndex=1 matches default leaderIndex=1, so this node is the leader.
        var metaGroup = new JetStreamMetaGroup(nodes: 3, selfIndex: 1);
        var streamManager = new StreamManager(metaGroup);
        var consumerManager = new ConsumerManager();
        var router = new JetStreamApiRouter(streamManager, consumerManager, metaGroup);

        // Create a stream first so the purge has something to operate on.
        var createPayload = Encoding.UTF8.GetBytes("""{"name":"TEST","subjects":["test.>"]}""");
        var createResult = router.Route("$JS.API.STREAM.CREATE.TEST", createPayload);
        createResult.Error.ShouldBeNull();
        createResult.StreamInfo.ShouldNotBeNull();

        // A mutating operation (delete) should succeed locally.
        var deleteResult = router.Route("$JS.API.STREAM.DELETE.TEST", ReadOnlySpan<byte>.Empty);
        deleteResult.Error.ShouldBeNull();
        deleteResult.Success.ShouldBeTrue();
    }

    /// <summary>
    /// When this node is NOT the leader, mutating operations return a not-leader error
    /// with the current leader's identifier in the leader_hint field.
    /// Go reference: jetstream_api.go:200-300 — not-leader response.
    /// </summary>
    [Fact]
    public void Route_WhenNotLeader_MutatingOp_ReturnsNotLeaderError()
    {
        // selfIndex=2, leaderIndex defaults to 1 — this node is NOT the leader.
        var metaGroup = new JetStreamMetaGroup(nodes: 3, selfIndex: 2);
        var streamManager = new StreamManager(metaGroup);
        var consumerManager = new ConsumerManager();
        var router = new JetStreamApiRouter(streamManager, consumerManager, metaGroup);

        var payload = Encoding.UTF8.GetBytes("""{"name":"TEST","subjects":["test.>"]}""");
        var result = router.Route("$JS.API.STREAM.CREATE.TEST", payload);

        result.Error.ShouldNotBeNull();
        result.Error!.Code.ShouldBe(10003);
        result.Error.Description.ShouldBe("not leader");
        result.Error.LeaderHint.ShouldNotBeNull();
        result.Error.LeaderHint.ShouldBe("meta-1");
    }

    /// <summary>
    /// Read-only operations (INFO, NAMES, LIST) are handled locally even when
    /// this node is not the leader.
    /// Go reference: jetstream_api.go — read operations do not require leadership.
    /// </summary>
    [Fact]
    public void Route_WhenNotLeader_ReadOp_HandlesLocally()
    {
        // selfIndex=2, leaderIndex defaults to 1 — this node is NOT the leader.
        var metaGroup = new JetStreamMetaGroup(nodes: 3, selfIndex: 2);
        var streamManager = new StreamManager(metaGroup);
        var consumerManager = new ConsumerManager();
        var router = new JetStreamApiRouter(streamManager, consumerManager, metaGroup);

        // $JS.API.INFO is a read-only operation.
        var infoResult = router.Route("$JS.API.INFO", ReadOnlySpan<byte>.Empty);
        infoResult.Error.ShouldBeNull();

        // $JS.API.STREAM.NAMES is a read-only operation.
        var namesResult = router.Route("$JS.API.STREAM.NAMES", ReadOnlySpan<byte>.Empty);
        namesResult.Error.ShouldBeNull();
        namesResult.StreamNames.ShouldNotBeNull();

        // $JS.API.STREAM.LIST is a read-only operation.
        var listResult = router.Route("$JS.API.STREAM.LIST", ReadOnlySpan<byte>.Empty);
        listResult.Error.ShouldBeNull();
        listResult.StreamNames.ShouldNotBeNull();
    }

    /// <summary>
    /// When there is no meta-group (single-server mode), all operations are handled
    /// locally regardless of the subject type.
    /// Go reference: jetstream_api.go — standalone servers have no meta-group.
    /// </summary>
    [Fact]
    public void Route_NoMetaGroup_HandlesLocally()
    {
        // No meta-group — single server mode.
        var streamManager = new StreamManager();
        var consumerManager = new ConsumerManager();
        var router = new JetStreamApiRouter(streamManager, consumerManager, metaGroup: null);

        var payload = Encoding.UTF8.GetBytes("""{"name":"TEST","subjects":["test.>"]}""");
        var result = router.Route("$JS.API.STREAM.CREATE.TEST", payload);

        // Should succeed — no leader check in single-server mode.
        result.Error.ShouldBeNull();
        result.StreamInfo.ShouldNotBeNull();
        result.StreamInfo!.Config.Name.ShouldBe("TEST");
    }

    /// <summary>
    /// IsLeaderRequired returns true for Create, Update, Delete, and Purge operations.
    /// Go reference: jetstream_api.go:200-300 — mutating operations require leader.
    /// </summary>
    [Fact]
    public void IsLeaderRequired_CreateUpdate_ReturnsTrue()
    {
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.CREATE.TEST").ShouldBeTrue();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.UPDATE.TEST").ShouldBeTrue();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.DELETE.TEST").ShouldBeTrue();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.PURGE.TEST").ShouldBeTrue();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.RESTORE.TEST").ShouldBeTrue();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.MSG.DELETE.TEST").ShouldBeTrue();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.CONSUMER.CREATE.STREAM.CON").ShouldBeTrue();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.CONSUMER.DELETE.STREAM.CON").ShouldBeTrue();
    }

    /// <summary>
    /// IsLeaderRequired returns false for Info, Names, List, and other read operations.
    /// Go reference: jetstream_api.go — read-only operations do not need leadership.
    /// </summary>
    [Fact]
    public void IsLeaderRequired_InfoList_ReturnsFalse()
    {
        JetStreamApiRouter.IsLeaderRequired("$JS.API.INFO").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.INFO.TEST").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.NAMES").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.LIST").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.MSG.GET.TEST").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.STREAM.SNAPSHOT.TEST").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.CONSUMER.INFO.STREAM.CON").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.CONSUMER.NAMES.STREAM").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.CONSUMER.LIST.STREAM").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.CONSUMER.MSG.NEXT.STREAM.CON").ShouldBeFalse();
        JetStreamApiRouter.IsLeaderRequired("$JS.API.DIRECT.GET.TEST").ShouldBeFalse();
    }
}
