using NATS.Server.JetStream.Api.Handlers;

namespace NATS.Server.JetStream.Api;

/// <summary>
/// Abstraction for forwarding a JetStream API request to the meta-group leader.
/// Allows the forwarding behaviour to be replaced with a test double.
/// Go reference: jetstream_api.go — jsClusteredStreamXxxRequest helpers forward to the leader.
/// </summary>
public interface ILeaderForwarder
{
    /// <summary>
    /// Forwards the request to the current meta-group leader.
    /// Returns the leader's response, or null when forwarding is not available
    /// (e.g. no route to leader) so the caller can fall back to a NotLeader error.
    /// </summary>
    Task<JetStreamApiResponse?> ForwardAsync(
        string subject,
        ReadOnlyMemory<byte> payload,
        string leaderName,
        CancellationToken ct);
}

/// <summary>
/// Default implementation of <see cref="ILeaderForwarder"/>.
/// In a real cluster, this would send the request to the leader over the internal
/// route connection using a request-reply pattern.
/// The current implementation returns null (forwarding unavailable) so the caller
/// falls back to a NotLeader error response, matching the stub behaviour from before Task 19.
/// Go reference: jetstream_api.go — jsClusteredStreamRequest request-reply to leader.
/// </summary>
public sealed class DefaultLeaderForwarder
{
    /// <summary>
    /// How long to wait for a response from the leader before giving up.
    /// </summary>
    public TimeSpan Timeout { get; }

    public DefaultLeaderForwarder(TimeSpan? timeout = null)
    {
        Timeout = timeout ?? TimeSpan.FromSeconds(5);
    }

    /// <inheritdoc/>
    public Task<JetStreamApiResponse?> ForwardAsync(
        string subject,
        ReadOnlyMemory<byte> payload,
        string leaderName,
        CancellationToken ct)
    {
        // In a real cluster this would serialise the request and forward it to
        // the leader over the internal route connection using request-reply.
        // Returning null signals "forwarding unavailable" — the router falls back
        // to a NotLeader error so the client can retry against the leader directly.
        _ = subject;
        _ = payload;
        _ = leaderName;
        _ = ct;
        return Task.FromResult<JetStreamApiResponse?>(null);
    }
}

/// <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;
    private readonly ILeaderForwarder? _forwarder;
    private long _forwardedCount;

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

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

    /// <summary>
    /// Number of requests that were successfully forwarded to the leader (non-null response).
    /// Useful for monitoring leader-forwarding activity.
    /// Go reference: jetstream_api.go — observable for forwarded API calls.
    /// </summary>
    public long ForwardedCount => Interlocked.Read(ref _forwardedCount);

    /// <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>
    /// Returns a not-leader error for backward-compatible synchronous callers.
    /// Async callers should use <see cref="RouteAsync"/> which also attempts forwarding.
    /// Go reference: jetstream_api.go — jsClusteredStreamXxxRequest helpers.
    /// </summary>
    public static JetStreamApiResponse ForwardToLeader(string subject, ReadOnlySpan<byte> payload, string leaderName)
    {
        _ = subject;
        _ = payload;
        return JetStreamApiResponse.NotLeader(leaderName);
    }

    /// <summary>
    /// Routes a JetStream API request asynchronously.
    /// When this node is not the meta-group leader and the subject requires leadership,
    /// attempts to forward the request to the leader via <see cref="_forwarder"/>.
    /// Falls back to a NotLeader error when no forwarder is configured or when the
    /// forwarder returns null or throws <see cref="OperationCanceledException"/>.
    /// Read-only operations are always handled locally regardless of leadership.
    /// Go reference: jetstream_api.go:200-300 — leader-forwarding path.
    /// </summary>
    public async Task<JetStreamApiResponse> RouteAsync(
        string subject,
        ReadOnlyMemory<byte> payload,
        CancellationToken ct = default)
    {
        // Go reference: jetstream_api.go:200-300 — leader check + forwarding.
        if (_metaGroup is not null && IsLeaderRequired(subject) && !_metaGroup.IsLeader())
        {
            if (_forwarder is not null)
            {
                JetStreamApiResponse? forwarded = null;
                bool forwardTimedOut = false;
                try
                {
                    forwarded = await _forwarder.ForwardAsync(subject, payload, _metaGroup.Leader, ct);
                }
                catch (OperationCanceledException ex) when (!ct.IsCancellationRequested)
                {
                    // Forward attempt timed out internally (not cancelled by the caller).
                    // Fall through to the NotLeader response below.
                    forwardTimedOut = true;
                    _ = ex; // acknowledged: timeout during forward, falling back to NotLeader
                }

                if (!forwardTimedOut && forwarded is not null)
                {
                    Interlocked.Increment(ref _forwardedCount);
                    return forwarded;
                }
            }

            return JetStreamApiResponse.NotLeader(_metaGroup.Leader);
        }

        // Handle locally (leader or read-only operation).
        return Route(subject, payload.Span);
    }

    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(payload, _streamManager);

        if (subject.Equals(JetStreamApiSubjects.StreamList, StringComparison.Ordinal))
            return StreamApiHandlers.HandleList(payload, _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, payload, _consumerManager);

        if (subject.StartsWith(JetStreamApiSubjects.ConsumerList, StringComparison.Ordinal))
            return ConsumerApiHandlers.HandleList(subject, payload, _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);
    }
}
