// Copyright 2012-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/client.go in the NATS server Go source.

using System.Text.Json.Serialization;
using ZB.MOM.NatsNet.Server.Auth;
using ZB.MOM.NatsNet.Server.Internal;
using ZB.MOM.NatsNet.Server.Internal.DataStructures;

namespace ZB.MOM.NatsNet.Server;

// ============================================================================
// Client connection kind (iota constants)
// ============================================================================

// Note: ClientKind is already declared in Internal/Subscription.cs; this file
// adds the remaining constants that were used only here.

/// <summary>
/// Extended client connection type (returned by <c>clientType()</c>).
/// Maps Go's NON_CLIENT / NATS / MQTT / WS iota.
/// </summary>
public enum ClientConnectionType
{
    /// <summary>Connection is not a CLIENT kind.</summary>
    NonClient = 0,
    /// <summary>Regular NATS client.</summary>
    Nats      = 1,
    /// <summary>MQTT client.</summary>
    Mqtt      = 2,
    /// <summary>WebSocket client.</summary>
    WebSocket = 3,
}

// ============================================================================
// Client protocol versions
// ============================================================================

/// <summary>
/// Wire protocol version negotiated in the CONNECT message.
/// </summary>
public static class ClientProtocol
{
    /// <summary>Original protocol (2009). Mirrors <c>ClientProtoZero</c>.</summary>
    public const int Zero = 0;
    /// <summary>Protocol that supports INFO updates. Mirrors <c>ClientProtoInfo</c>.</summary>
    public const int Info = 1;
}

// ============================================================================
// WriteTimeoutPolicy extension (enum defined in ServerOptionTypes.cs)
// ============================================================================

internal static class WriteTimeoutPolicyExtensions
{
    /// <summary>Mirrors Go <c>WriteTimeoutPolicy.String()</c>.</summary>
    public static string String(this WriteTimeoutPolicy p) => p switch
    {
        WriteTimeoutPolicy.Close => "close",
        WriteTimeoutPolicy.Retry => "retry",
        _                        => string.Empty,
    };

    /// <summary>
    /// Alias for existing call sites that use a descriptive name.
    /// </summary>
    public static string ToVarzString(this WriteTimeoutPolicy p) => p.String();
}

/// <summary>
/// Bit flag helpers for <see cref="ClientFlags"/>.
/// Mirrors Go <c>clientFlag.{set,clear,isSet,setIfNotSet}</c>.
/// </summary>
public static class ClientFlagExtensions
{
    public static ClientFlags Set(this ClientFlags current, ClientFlags bit) => current | bit;

    public static ClientFlags Clear(this ClientFlags current, ClientFlags bit) => current & ~bit;

    public static bool IsSet(this ClientFlags current, ClientFlags bit) => (current & bit) != 0;

    public static bool SetIfNotSet(ref ClientFlags current, ClientFlags bit)
    {
        if ((current & bit) != 0)
            return false;
        current |= bit;
        return true;
    }
}

/// <summary>
/// Bit flag helpers for <see cref="ReadCacheFlags"/>.
/// Mirrors Go <c>readCacheFlag.{set,clear,isSet}</c>.
/// </summary>
public static class ReadCacheFlagExtensions
{
    public static ReadCacheFlags Set(this ReadCacheFlags current, ReadCacheFlags bit) => current | bit;

    public static ReadCacheFlags Clear(this ReadCacheFlags current, ReadCacheFlags bit) => current & ~bit;

    public static bool IsSet(this ReadCacheFlags current, ReadCacheFlags bit) => (current & bit) != 0;
}

// ============================================================================
// ClientFlags
// ============================================================================

/// <summary>
/// Compact bitfield of boolean client state.
/// Mirrors Go <c>clientFlag</c> and its iota constants.
/// </summary>
[Flags]
public enum ClientFlags : ushort
{
    None                   = 0,
    ConnectReceived        = 1 << 0,
    InfoReceived           = 1 << 1,
    FirstPongSent          = 1 << 2,
    HandshakeComplete      = 1 << 3,
    FlushOutbound          = 1 << 4,
    NoReconnect            = 1 << 5,
    CloseConnection        = 1 << 6,
    ConnMarkedClosed       = 1 << 7,
    WriteLoopStarted       = 1 << 8,
    SkipFlushOnClose       = 1 << 9,
    ExpectConnect          = 1 << 10,
    ConnectProcessFinished = 1 << 11,
    CompressionNegotiated  = 1 << 12,
    DidTlsFirst            = 1 << 13,
    IsSlowConsumer         = 1 << 14,
    FirstPong              = 1 << 15,
}

// ============================================================================
// ReadCacheFlags
// ============================================================================

/// <summary>
/// Bitfield for the read-cache loop state.
/// Mirrors Go <c>readCacheFlag</c>.
/// </summary>
[Flags]
public enum ReadCacheFlags : ushort
{
    None                = 0,
    HasMappings         = 1 << 0,
    SwitchToCompression = 1 << 1,
}

// ============================================================================
// ClosedState
// ============================================================================

/// <summary>
/// The reason a client connection was closed.
/// Mirrors Go <c>ClosedState</c>.
/// </summary>
public enum ClosedState
{
    ClientClosed = 1,
    AuthenticationTimeout,
    AuthenticationViolation,
    TlsHandshakeError,
    SlowConsumerPendingBytes,
    SlowConsumerWriteDeadline,
    WriteError,
    ReadError,
    ParseError,
    StaleConnection,
    ProtocolViolation,
    BadClientProtocolVersion,
    WrongPort,
    MaxAccountConnectionsExceeded,
    MaxConnectionsExceeded,
    MaxPayloadExceeded,
    MaxControlLineExceeded,
    MaxSubscriptionsExceeded,
    DuplicateRoute,
    RouteRemoved,
    ServerShutdown,
    AuthenticationExpired,
    WrongGateway,
    MissingAccount,
    Revocation,
    InternalClient,
    MsgHeaderViolation,
    NoRespondersRequiresHeaders,
    ClusterNameConflict,
    DuplicateRemoteLeafnodeConnection,
    DuplicateClientId,
    DuplicateServerName,
    MinimumVersionRequired,
    ClusterNamesIdentical,
    Kicked,
    ProxyNotTrusted,
    ProxyRequired,
}

// ============================================================================
// processMsgResults flags
// ============================================================================

/// <summary>
/// Flags passed to <c>ProcessMsgResults</c>.
/// Mirrors Go <c>pmrNoFlag</c> and the iota block.
/// </summary>
[Flags]
public enum PmrFlags
{
    None                        = 0,
    CollectQueueNames           = 1 << 0,
    IgnoreEmptyQueueFilter      = 1 << 1,
    AllowSendFromRouteToRoute   = 1 << 2,
    MsgImportedFromService      = 1 << 3,
}

// ============================================================================
// denyType
// ============================================================================

/// <summary>
/// Which permission side to apply deny-list merging to.
/// Mirrors Go <c>denyType</c>.
/// </summary>
internal enum DenyType
{
    Pub  = 1,
    Sub  = 2,
    Both = 3,
}

// ============================================================================
// ClientOptions  (wire-protocol CONNECT options)
// ============================================================================

/// <summary>
/// Options negotiated during the CONNECT handshake.
/// Mirrors Go <c>ClientOpts</c>.
/// </summary>
public sealed class ClientOptions
{
    [JsonPropertyName("echo")]         public bool   Echo         { get; set; }
    [JsonPropertyName("verbose")]      public bool   Verbose      { get; set; }
    [JsonPropertyName("pedantic")]     public bool   Pedantic     { get; set; }
    [JsonPropertyName("tls_required")] public bool   TlsRequired  { get; set; }
    [JsonPropertyName("nkey")]         public string Nkey         { get; set; } = string.Empty;
    [JsonPropertyName("jwt")]          public string Jwt          { get; set; } = string.Empty;
    [JsonPropertyName("sig")]          public string Sig          { get; set; } = string.Empty;
    [JsonPropertyName("auth_token")]   public string Token        { get; set; } = string.Empty;
    [JsonPropertyName("user")]         public string Username     { get; set; } = string.Empty;
    [JsonPropertyName("pass")]         public string Password     { get; set; } = string.Empty;
    [JsonPropertyName("name")]         public string Name         { get; set; } = string.Empty;
    [JsonPropertyName("lang")]         public string Lang         { get; set; } = string.Empty;
    [JsonPropertyName("version")]      public string Version      { get; set; } = string.Empty;
    [JsonPropertyName("protocol")]     public int    Protocol     { get; set; }
    [JsonPropertyName("account")]      public string Account      { get; set; } = string.Empty;
    [JsonPropertyName("new_account")]  public bool   AccountNew   { get; set; }
    [JsonPropertyName("headers")]      public bool   Headers      { get; set; }
    [JsonPropertyName("no_responders")]public bool   NoResponders { get; set; }

    // Routes and Leaf Nodes only
    [JsonPropertyName("import")]         public SubjectPermission? Import        { get; set; }
    [JsonPropertyName("export")]         public SubjectPermission? Export        { get; set; }
    [JsonPropertyName("remote_account")] public string             RemoteAccount { get; set; } = string.Empty;
    [JsonPropertyName("proxy_sig")]      public string             ProxySig      { get; set; } = string.Empty;

    /// <summary>Default options for external clients.</summary>
    public static ClientOptions Default   => new() { Verbose = true, Pedantic = true, Echo = true };

    /// <summary>Default options for internal server clients.</summary>
    public static ClientOptions Internal  => new() { Verbose = false, Pedantic = false, Echo = false };
}

// ============================================================================
// ClientInfo  — lightweight metadata sent in server events
// ============================================================================

/// <summary>
/// Client metadata included in server monitoring events.
/// Mirrors Go <c>ClientInfo</c>.
/// </summary>
public sealed class ClientInfo
{
    public string    Server      { get; set; } = string.Empty;
    public string    Start       { get; set; } = string.Empty;
    public string    Host        { get; set; } = string.Empty;
    public ulong     Id          { get; set; }
    public string    Account     { get; set; } = string.Empty;
    public string    ServiceName { get; set; } = string.Empty;
    public string    User        { get; set; } = string.Empty;
    public string    Name        { get; set; } = string.Empty;
    public string    Lang        { get; set; } = string.Empty;
    public string    Version     { get; set; } = string.Empty;
    public string    Jwt         { get; set; } = string.Empty;
    public string    IssuerKey   { get; set; } = string.Empty;
    public string    NameTag     { get; set; } = string.Empty;
    public List<string> Tags     { get; set; } = [];
    public string    Kind        { get; set; } = string.Empty;
    public string    ClientType  { get; set; } = string.Empty;
    public string?   MqttId      { get; set; }
    public bool      Stop        { get; set; }
    public bool      Restart     { get; set; }
    public bool      Disconnect  { get; set; }
    public string[]? Cluster     { get; set; }
    public List<string> Alternates { get; set; } = [];
    public bool      Service     { get; set; }

    /// <summary>
    /// Round-trip time to the client.
    /// Mirrors Go <c>RTT time.Duration</c> in events.go.
    /// Added here to support <see cref="ServiceLatency.NATSTotalTime"/>.
    /// </summary>
    public TimeSpan  Rtt         { get; set; }

    /// <summary>
    /// Returns the service account for this client info payload.
    /// Mirrors Go <c>ClientInfo.serviceAccount()</c>.
    /// </summary>
    public string ServiceAccount() =>
        string.IsNullOrWhiteSpace(ServiceName) ? Account : ServiceName;

    // =========================================================================
    // Group C: ClientInfo projection methods
    // Mirrors Go methods on *ClientInfo in server/events.go.
    // =========================================================================

    /// <summary>
    /// Returns the minimum amount of ClientInfo needed for assignment snapshots
    /// (account, service, cluster only).
    /// Mirrors Go <c>(ci *ClientInfo) forAssignmentSnap() *ClientInfo</c> in server/events.go.
    /// </summary>
    public ClientInfo ForAssignmentSnap() => new()
    {
        Account     = Account,
        ServiceName = ServiceName,
        Cluster     = Cluster,
    };

    /// <summary>
    /// Returns a copy of this ClientInfo with JWT and issuer key stripped,
    /// suitable for raft proposals.
    /// Mirrors Go <c>(ci *ClientInfo) forProposal() *ClientInfo</c> in server/events.go.
    /// </summary>
    public ClientInfo? ForProposal()
    {
        var copy       = (ClientInfo)MemberwiseClone();
        copy.Jwt       = string.Empty;
        copy.IssuerKey = string.Empty;
        // Deep-copy the Tags list to avoid sharing.
        copy.Tags = [..Tags];
        copy.Alternates = [..Alternates];
        return copy;
    }

    /// <summary>
    /// Returns a copy of this ClientInfo with JWT stripped and Alternates nulled,
    /// suitable for JetStream advisory events.
    /// Mirrors Go <c>(ci *ClientInfo) forAdvisory() *ClientInfo</c> in server/events.go.
    /// </summary>
    public ClientInfo? ForAdvisory()
    {
        var copy       = (ClientInfo)MemberwiseClone();
        copy.Jwt       = string.Empty;
        copy.Alternates = [];
        copy.Tags      = [..Tags];
        return copy;
    }
}

// ============================================================================
// Internal permission structures (not public API)
// (Permissions, SubjectPermission, ResponsePermission are in Auth/AuthTypes.cs)
// ============================================================================

internal sealed class Perm
{
    public SubscriptionIndex? Allow { get; set; }
    public SubscriptionIndex? Deny  { get; set; }
}

internal sealed class ClientPermissions
{
    public int PcsZ;  // pub cache size (atomic)
    public int PRun;  // prune run count (atomic)
    public Perm Sub { get; } = new();
    public Perm Pub { get; } = new();
    public ResponsePermission? Resp     { get; set; }
    // Per-subject cache for permission checks.
    public Dictionary<string, bool> PCache { get; } = new(StringComparer.Ordinal);
}

internal sealed class MsgDeny
{
    public SubscriptionIndex?         Deny   { get; set; }
    public Dictionary<string, bool>   DCache { get; } = new(StringComparer.Ordinal);
}

internal sealed class RespEntry
{
    public DateTime Time { get; set; }
    public int      N    { get; set; }
}

internal readonly record struct OutboundChunk(byte[] Buffer, int Count);

// ============================================================================
// Buffer pool constants
// ============================================================================

internal static class NbPool
{
    internal const int SmallSize  =  512;
    internal const int MediumSize = 4096;
    internal const int LargeSize  = 65536;

    private static readonly System.Buffers.ArrayPool<byte> _pool =
        System.Buffers.ArrayPool<byte>.Create(LargeSize, 50);

    /// <summary>
    /// Returns a buffer best-effort sized to <paramref name="sz"/>.
    /// Mirrors Go <c>nbPoolGet</c>.
    /// </summary>
    public static byte[] Get(int sz)
    {
        int cap = sz <= SmallSize  ? SmallSize
                : sz <= MediumSize ? MediumSize
                : LargeSize;
        return _pool.Rent(cap);
    }

    /// <summary>
    /// Returns a buffer to the pool.
    /// Mirrors Go <c>nbPoolPut</c>.
    /// </summary>
    public static void Put(byte[] buf)
    {
        if (buf.Length == SmallSize || buf.Length == MediumSize || buf.Length == LargeSize)
            _pool.Return(buf);
        // Ignore wrong-sized frames (WebSocket/MQTT).
    }
}

// ============================================================================
// Route / gateway / leaf / websocket / mqtt stubs
// (These are filled in during sessions 14-16 and 22-23)
// ============================================================================

internal sealed class RouteTarget
{
    public Subscription? Sub { get; set; }
    public byte[]        Qs  { get; set; } = [];
}

// ============================================================================
// Static helper: IsInternalClient
// ============================================================================

/// <summary>
/// Client-kind classification helpers.
/// </summary>
public static class ClientKindHelpers
{
    /// <summary>
    /// Returns <c>true</c> if <paramref name="kind"/> is an internal server client.
    /// Mirrors Go <c>isInternalClient</c>.
    /// </summary>
    public static bool IsInternalClient(ClientKind kind) =>
        kind == ClientKind.System || kind == ClientKind.JetStream || kind == ClientKind.Account;
}
