using NATS.Server.JetStream.Api.Handlers;

namespace NATS.Server.JetStream.Api;

/// <summary>
/// Routes JetStream API requests to the appropriate handler.
/// Go reference: jetstream_api.go:200-300 — non-leader nodes must forward or reject
/// mutating operations (Create, Update, Delete, Purge) to the current meta-group leader.
/// </summary>
public sealed class JetStreamApiRouter
{
    private readonly StreamManager _streamManager;
    private readonly ConsumerManager _consumerManager;
    private readonly JetStream.Cluster.JetStreamMetaGroup? _metaGroup;

    public JetStreamApiRouter()
        : this(new StreamManager(), new ConsumerManager(), null)
    {
    }

    public JetStreamApiRouter(StreamManager streamManager, ConsumerManager consumerManager, JetStream.Cluster.JetStreamMetaGroup? metaGroup = null)
    {
        _streamManager = streamManager;
        _consumerManager = consumerManager;
        _metaGroup = metaGroup;
    }

    /// <summary>
    /// Determines whether the given API subject requires leader-only handling.
    /// Mutating operations (Create, Update, Delete, Purge, Restore, Pause, Reset, Unpin,
    /// message delete, peer/leader stepdown, server remove, account purge/move) require the leader.
    /// Read-only operations (Info, Names, List, MessageGet, Snapshot, DirectGet, Next) do not.
    /// Go reference: jetstream_api.go:200-300.
    /// </summary>
    public static bool IsLeaderRequired(string subject)
    {
        // Stream mutating operations
        if (subject.StartsWith(JetStreamApiSubjects.StreamCreate, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.StreamUpdate, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.StreamDelete, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.StreamPurge, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.StreamRestore, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.StreamMessageDelete, StringComparison.Ordinal))
            return true;

        // Consumer mutating operations
        if (subject.StartsWith(JetStreamApiSubjects.ConsumerCreate, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.ConsumerDelete, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.ConsumerPause, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.ConsumerReset, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.ConsumerUnpin, StringComparison.Ordinal))
            return true;

        // Cluster control operations
        if (subject.StartsWith(JetStreamApiSubjects.StreamLeaderStepdown, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.StreamPeerRemove, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.ConsumerLeaderStepdown, StringComparison.Ordinal))
            return true;
        // MetaLeaderStepdown is handled specially: the stepdown request itself
        // does not require the current node to be the leader, because in a real cluster
        // the request would be forwarded to the leader. In a single-node simulation the
        // StepDown() call is applied locally regardless of leader state.
        // Go reference: jetstream_api.go — meta leader stepdown is always processed.
        // if (subject.Equals(JetStreamApiSubjects.MetaLeaderStepdown, StringComparison.Ordinal))
        //     return true;

        // Account-level control
        if (subject.Equals(JetStreamApiSubjects.ServerRemove, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.AccountPurge, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.AccountStreamMove, StringComparison.Ordinal))
            return true;
        if (subject.StartsWith(JetStreamApiSubjects.AccountStreamMoveCancel, StringComparison.Ordinal))
            return true;

        return false;
    }

    /// <summary>
    /// Stub for future leader-forwarding implementation.
    /// In a clustered deployment this would serialize the request and forward it
    /// to the leader node over the internal route connection.
    /// Go reference: jetstream_api.go — jsClusteredStreamXxxRequest helpers.
    /// </summary>
    public static JetStreamApiResponse ForwardToLeader(string subject, ReadOnlySpan<byte> payload, string leaderName)
    {
        // For now, return the not-leader error with a hint so the client can retry.
        return JetStreamApiResponse.NotLeader(leaderName);
    }

    public JetStreamApiResponse Route(string subject, ReadOnlySpan<byte> payload)
    {
        // Go reference: jetstream_api.go:200-300 — leader check + forwarding.
        if (_metaGroup is not null && IsLeaderRequired(subject) && !_metaGroup.IsLeader())
        {
            return ForwardToLeader(subject, payload, _metaGroup.Leader);
        }

        if (subject.Equals(JetStreamApiSubjects.Info, StringComparison.Ordinal))
            return AccountApiHandlers.HandleInfo(_streamManager, _consumerManager);

        if (subject.Equals(JetStreamApiSubjects.ServerRemove, StringComparison.Ordinal))
            return AccountControlApiHandlers.HandleServerRemove();

        if (subject.StartsWith(JetStreamApiSubjects.AccountPurge, StringComparison.Ordinal))
            return AccountControlApiHandlers.HandleAccountPurge(subject);

        if (subject.StartsWith(JetStreamApiSubjects.AccountStreamMoveCancel, StringComparison.Ordinal))
            return AccountControlApiHandlers.HandleAccountStreamMoveCancel(subject);

        if (subject.StartsWith(JetStreamApiSubjects.AccountStreamMove, StringComparison.Ordinal))
            return AccountControlApiHandlers.HandleAccountStreamMove(subject);

        if (subject.StartsWith(JetStreamApiSubjects.StreamCreate, StringComparison.Ordinal))
            return StreamApiHandlers.HandleCreate(subject, payload, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamInfo, StringComparison.Ordinal))
            return StreamApiHandlers.HandleInfo(subject, _streamManager);

        if (subject.Equals(JetStreamApiSubjects.StreamNames, StringComparison.Ordinal))
            return StreamApiHandlers.HandleNames(_streamManager);

        if (subject.Equals(JetStreamApiSubjects.StreamList, StringComparison.Ordinal))
            return StreamApiHandlers.HandleList(_streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamUpdate, StringComparison.Ordinal))
            return StreamApiHandlers.HandleUpdate(subject, payload, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamDelete, StringComparison.Ordinal))
            return StreamApiHandlers.HandleDelete(subject, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamPurge, StringComparison.Ordinal))
            return StreamApiHandlers.HandlePurge(subject, payload, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamMessageGet, StringComparison.Ordinal))
            return StreamApiHandlers.HandleMessageGet(subject, payload, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamMessageDelete, StringComparison.Ordinal))
            return StreamApiHandlers.HandleMessageDelete(subject, payload, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamSnapshot, StringComparison.Ordinal))
            return StreamApiHandlers.HandleSnapshot(subject, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamRestore, StringComparison.Ordinal))
            return StreamApiHandlers.HandleRestore(subject, payload, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamLeaderStepdown, StringComparison.Ordinal))
            return ClusterControlApiHandlers.HandleStreamLeaderStepdown(subject, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.StreamPeerRemove, StringComparison.Ordinal))
            return ClusterControlApiHandlers.HandleStreamPeerRemove(subject);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerCreate, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleCreate(subject, payload, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerInfo, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleInfo(subject, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerNames, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleNames(subject, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerList, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleList(subject, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerDelete, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleDelete(subject, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerPause, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandlePause(subject, payload, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerReset, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleReset(subject, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerUnpin, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleUnpin(subject, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerNext, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleNext(subject, payload, _consumerManager, _streamManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerLeaderStepdown, StringComparison.Ordinal))
            return ClusterControlApiHandlers.HandleConsumerLeaderStepdown(subject);

        if (subject.StartsWith(JetStreamApiSubjects.DirectGet, StringComparison.Ordinal))
            return DirectApiHandlers.HandleGet(subject, payload, _streamManager);

        if (subject.Equals(JetStreamApiSubjects.MetaLeaderStepdown, StringComparison.Ordinal) && _metaGroup != null)
            return ClusterControlApiHandlers.HandleMetaLeaderStepdown(_metaGroup);

        return JetStreamApiResponse.NotFound(subject);
    }
}
