// Copyright 2019-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/stream.go and server/consumer.go in the NATS server Go source.

using System.Text.Json.Serialization;

namespace ZB.MOM.NatsNet.Server;

// ============================================================================
// Stream API types (from stream.go)
// ============================================================================

/// <summary>
/// A stream create request that extends <see cref="StreamConfig"/> with a pedantic flag.
/// Mirrors <c>streamConfigRequest</c> in server/stream.go.
/// </summary>
public sealed class StreamConfigRequest
{
    [JsonPropertyName("config")]
    public StreamConfig Config { get; set; } = new();

    /// <summary>If true, strict validation is applied during stream creation/update.</summary>
    [JsonPropertyName("pedantic")]
    public bool Pedantic { get; set; }
}

/// <summary>
/// Information about a stream, returned from info requests.
/// Mirrors <c>StreamInfo</c> in server/stream.go.
/// </summary>
public sealed class StreamInfo
{
    [JsonPropertyName("config")]
    public StreamConfig Config { get; set; } = new();

    [JsonPropertyName("created")]
    public DateTime Created { get; set; }

    [JsonPropertyName("state")]
    public StreamState State { get; set; } = new();

    [JsonPropertyName("mirror")]
    public StreamSourceInfo? Mirror { get; set; }

    [JsonPropertyName("sources")]
    public StreamSourceInfo[]? Sources { get; set; }

    [JsonPropertyName("cluster")]
    public ClusterInfo? Cluster { get; set; }

    [JsonPropertyName("mirror_direct")]
    public bool Mirror_Direct { get; set; }

    [JsonPropertyName("allow_direct")]
    public bool Allow_Direct { get; set; }

    /// <summary>Alternate cluster name.</summary>
    [JsonPropertyName("alternates")]
    public string? Alt { get; set; }
}

/// <summary>
/// Information about a stream mirror or source.
/// Mirrors <c>StreamSourceInfo</c> in server/stream.go.
/// </summary>
public sealed class StreamSourceInfo
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("filter_subject")]
    public string? FilterSubject { get; set; }

    [JsonPropertyName("lag")]
    public ulong Lag { get; set; }

    [JsonPropertyName("active")]
    public DateTime? Active { get; set; }

    [JsonPropertyName("external")]
    public StreamSource? External { get; set; }

    [JsonPropertyName("error")]
    public string? Error { get; set; }
}

/// <summary>
/// Request parameters for stream info, allowing filtering.
/// Mirrors <c>streamInfoRequest</c> in server/stream.go.
/// </summary>
public sealed class StreamInfoRequest
{
    [JsonPropertyName("subjects_filter")]
    public string? SubjectsFilter { get; set; }

    [JsonPropertyName("mirror_check_until")]
    public string? MirrorCheckUntil { get; set; }

    [JsonPropertyName("deleted_details")]
    public bool DeletedDetails { get; set; }

    [JsonPropertyName("subjects_detail")]
    public bool SubjectsDetail { get; set; }
}

/// <summary>
/// Request parameters for purging a stream.
/// Mirrors <c>StreamPurgeRequest</c> in server/stream.go.
/// </summary>
public sealed class StreamPurgeRequest
{
    [JsonPropertyName("filter")]
    public string? Filter { get; set; }

    [JsonPropertyName("seq")]
    public ulong Sequence { get; set; }

    [JsonPropertyName("keep")]
    public ulong Keep { get; set; }
}

/// <summary>
/// Request for deleting a specific stream message.
/// Mirrors <c>StreamMsgDeleteRequest</c> in server/stream.go.
/// </summary>
public sealed class StreamMsgDeleteRequest
{
    [JsonPropertyName("seq")]
    public ulong Seq { get; set; }

    [JsonPropertyName("no_erase")]
    public bool NoErase { get; set; }
}

/// <summary>
/// Request for retrieving a specific stream message.
/// Mirrors <c>StreamGetMsgRequest</c> in server/stream.go.
/// </summary>
public sealed class StreamGetMsgRequest
{
    [JsonPropertyName("seq")]
    public ulong Seq { get; set; }

    [JsonPropertyName("last_by_subj")]
    public string? LastBySubject { get; set; }

    [JsonPropertyName("next_by_subj")]
    public string? NextBySubject { get; set; }
}

/// <summary>
/// Publish acknowledgement response from JetStream.
/// Mirrors <c>JSPubAckResponse</c> in server/stream.go.
/// </summary>
public sealed class JSPubAckResponse
{
    [JsonPropertyName("stream")]
    public string Stream { get; set; } = string.Empty;

    [JsonPropertyName("seq")]
    public ulong Seq { get; set; }

    [JsonPropertyName("duplicate")]
    public bool Duplicate { get; set; }

    [JsonPropertyName("domain")]
    public string? Domain { get; set; }

    [JsonPropertyName("error")]
    public JsApiError? PubAckError { get; set; }

    /// <summary>
    /// Returns an exception if the response contains an error, otherwise null.
    /// Mirrors <c>ToError()</c> helper pattern in NATS Go server.
    /// </summary>
    public Exception? ToError()
    {
        if (PubAckError is { ErrCode: > 0 })
            return new InvalidOperationException($"{PubAckError.Description} (errCode={PubAckError.ErrCode})");
        return null;
    }
}

/// <summary>
/// A raw published message before JetStream processing.
/// Mirrors <c>pubMsg</c> (JetStream variant) in server/stream.go.
/// Note: renamed <c>JsStreamPubMsg</c> to avoid collision with the server-level
/// <c>PubMsg</c> (events.go) which lives in the same namespace.
/// </summary>
public sealed class JsStreamPubMsg
{
    public string Subject { get; set; } = string.Empty;
    public string? Reply { get; set; }
    public byte[]? Hdr { get; set; }
    public byte[]? Msg { get; set; }
    public Dictionary<string, string>? Meta { get; set; }
}

/// <summary>
/// A JetStream publish message with sync tracking.
/// Mirrors <c>jsPubMsg</c> in server/stream.go.
/// </summary>
public sealed class JsPubMsg
{
    public string Subject { get; set; } = string.Empty;
    public string? Reply { get; set; }
    public byte[]? Hdr { get; set; }
    public byte[]? Msg { get; set; }

    /// <summary>Publish argument (opaque, set at runtime).</summary>
    public object? Pa { get; set; }

    /// <summary>Sync/ack channel (opaque, set at runtime).</summary>
    public object? Sync { get; set; }
}

/// <summary>
/// An inbound message to be processed by the JetStream layer.
/// Mirrors <c>inMsg</c> in server/stream.go.
/// </summary>
public sealed class InMsg
{
    public string Subject { get; set; } = string.Empty;
    public string? Reply { get; set; }
    public byte[]? Hdr { get; set; }
    public byte[]? Msg { get; set; }

    /// <summary>The originating client (opaque, set at runtime).</summary>
    public object? Client { get; set; }
}

/// <summary>
/// A cached/clustered message for replication.
/// Mirrors <c>cMsg</c> in server/stream.go.
/// </summary>
public sealed class CMsg
{
    public string Subject { get; set; } = string.Empty;
    public byte[]? Msg { get; set; }
    public ulong Seq { get; set; }
}

// ============================================================================
// Consumer API types (from consumer.go)
// ============================================================================

/// <summary>
/// Information about a consumer, returned from info requests.
/// Mirrors <c>ConsumerInfo</c> in server/consumer.go.
/// </summary>
public sealed class ConsumerInfo
{
    [JsonPropertyName("stream_name")]
    public string Stream { get; set; } = string.Empty;

    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("created")]
    public DateTime Created { get; set; }

    [JsonPropertyName("config")]
    public ConsumerConfig? Config { get; set; }

    [JsonPropertyName("delivered")]
    public SequenceInfo Delivered { get; set; } = new();

    [JsonPropertyName("ack_floor")]
    public SequenceInfo AckFloor { get; set; } = new();

    [JsonPropertyName("num_ack_pending")]
    public int NumAckPending { get; set; }

    [JsonPropertyName("num_redelivered")]
    public int NumRedelivered { get; set; }

    [JsonPropertyName("num_waiting")]
    public int NumWaiting { get; set; }

    [JsonPropertyName("num_pending")]
    public ulong NumPending { get; set; }

    [JsonPropertyName("cluster")]
    public ClusterInfo? Cluster { get; set; }

    [JsonPropertyName("push_bound")]
    public bool PushBound { get; set; }

    [JsonPropertyName("paused")]
    public bool Paused { get; set; }

    [JsonPropertyName("pause_remaining")]
    public TimeSpan PauseRemaining { get; set; }

    [JsonPropertyName("ts")]
    public DateTime TimeStamp { get; set; }

    [JsonPropertyName("priority_groups")]
    public PriorityGroupState[]? PriorityGroups { get; set; }
}

/// <summary>
/// State information for a priority group on a pull consumer.
/// Mirrors <c>PriorityGroupState</c> in server/consumer.go.
/// </summary>
public sealed class PriorityGroupState
{
    [JsonPropertyName("group")]
    public string Group { get; set; } = string.Empty;

    [JsonPropertyName("pinned_client_id")]
    public string? PinnedClientId { get; set; }

    [JsonPropertyName("pinned_ts")]
    public DateTime PinnedTs { get; set; }
}

/// <summary>
/// Sequence information for consumer delivered/ack_floor positions.
/// Mirrors <c>SequenceInfo</c> in server/consumer.go.
/// </summary>
public sealed class SequenceInfo
{
    [JsonPropertyName("consumer_seq")]
    public ulong Consumer { get; set; }

    [JsonPropertyName("stream_seq")]
    public ulong Stream { get; set; }

    [JsonPropertyName("last_active")]
    public DateTime? Last { get; set; }
}

/// <summary>
/// Request to create or update a consumer.
/// Mirrors <c>CreateConsumerRequest</c> in server/consumer.go.
/// </summary>
public sealed class CreateConsumerRequest
{
    [JsonPropertyName("stream_name")]
    public string Stream { get; set; } = string.Empty;

    [JsonPropertyName("config")]
    public ConsumerConfig Config { get; set; } = new();

    [JsonPropertyName("action")]
    public ConsumerAction Action { get; set; }
}

/// <summary>
/// Specifies the intended action when creating a consumer.
/// Mirrors <c>ConsumerAction</c> in server/consumer.go.
/// </summary>
public enum ConsumerAction
{
    /// <summary>Create a new consumer or update if it already exists.</summary>
    CreateOrUpdate = 0,

    /// <summary>Create a new consumer; fail if it already exists.</summary>
    Create = 1,

    /// <summary>Update an existing consumer; fail if it does not exist.</summary>
    Update = 2,
}

/// <summary>
/// Response for a consumer deletion request.
/// Mirrors <c>ConsumerDeleteResponse</c> in server/consumer.go.
/// </summary>
public sealed class ConsumerDeleteResponse
{
    [JsonPropertyName("success")]
    public bool Success { get; set; }
}

/// <summary>
/// A pending pull request waiting in the wait queue.
/// Mirrors <c>waitingRequest</c> in server/consumer.go.
/// </summary>
public sealed class WaitingRequest
{
    public string Subject { get; set; } = string.Empty;
    public string? Reply { get; set; }

    /// <summary>Number of messages requested.</summary>
    public int N { get; set; }

    /// <summary>Number of messages delivered so far.</summary>
    public int D { get; set; }

    /// <summary>No-wait flag (1 = no wait).</summary>
    public int NoWait { get; set; }

    public DateTime? Expires { get; set; }

    /// <summary>Max byte limit for this batch.</summary>
    public int MaxBytes { get; set; }

    /// <summary>Bytes accumulated so far.</summary>
    public int B { get; set; }
}

/// <summary>
/// A circular wait queue for pending pull requests.
/// Mirrors <c>waitQueue</c> in server/consumer.go.
/// </summary>
public sealed class WaitQueue
{
    private readonly List<WaitingRequest> _reqs = new();
    private int _head;
    private int _tail;

    /// <summary>Number of pending requests in the queue.</summary>
    public int Len => _reqs.Count;

    /// <summary>Add a waiting request to the tail of the queue.</summary>
    public void Add(WaitingRequest req) =>
        throw new NotImplementedException("TODO: session 21");

    /// <summary>Peek at the head request without removing it.</summary>
    public WaitingRequest? Peek() =>
        throw new NotImplementedException("TODO: session 21");

    /// <summary>Remove and return the head request.</summary>
    public WaitingRequest? Pop() =>
        throw new NotImplementedException("TODO: session 21");

    /// <summary>Compact the internal backing list to reclaim removed slots.</summary>
    public void Compress() =>
        throw new NotImplementedException("TODO: session 21");

    /// <summary>Returns true if the queue is at capacity (head == tail when full).</summary>
    public bool IsFull(int max) =>
        throw new NotImplementedException("TODO: session 21");
}

/// <summary>
/// Cluster membership and leadership information for a stream or consumer.
/// Mirrors <c>ClusterInfo</c> in server/consumer.go and server/stream.go.
/// </summary>
public sealed class ClusterInfo
{
    [JsonPropertyName("name")]
    public string? Name { get; set; }

    [JsonPropertyName("leader")]
    public string? Leader { get; set; }

    [JsonPropertyName("replicas")]
    public PeerInfo[]? Replicas { get; set; }
}

/// <summary>
/// Information about a peer in a JetStream Raft group.
/// Mirrors <c>PeerInfo</c> in server/consumer.go and server/stream.go.
/// </summary>
public sealed class PeerInfo
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    [JsonPropertyName("current")]
    public bool Current { get; set; }

    [JsonPropertyName("offline")]
    public bool Offline { get; set; }

    [JsonPropertyName("active")]
    public TimeSpan Active { get; set; }

    [JsonPropertyName("lag")]
    public ulong Lag { get; set; }
}
