using System.Text.Json.Serialization;

namespace NATS.Server.Monitoring;

/// <summary>
/// Connection information response. Corresponds to Go server/monitor.go Connz struct.
/// </summary>
public sealed class Connz
{
    [JsonPropertyName("server_id")]
    public string Id { get; set; } = "";

    [JsonPropertyName("now")]
    public DateTime Now { get; set; }

    [JsonPropertyName("num_connections")]
    public int NumConns { get; set; }

    [JsonPropertyName("total")]
    public int Total { get; set; }

    [JsonPropertyName("offset")]
    public int Offset { get; set; }

    [JsonPropertyName("limit")]
    public int Limit { get; set; }

    [JsonPropertyName("connections")]
    public ConnInfo[] Conns { get; set; } = [];
}

/// <summary>
/// Detailed information on a per-connection basis.
/// Corresponds to Go server/monitor.go ConnInfo struct.
/// </summary>
public sealed class ConnInfo
{
    [JsonPropertyName("cid")]
    public ulong Cid { get; set; }

    [JsonPropertyName("kind")]
    public string Kind { get; set; } = "";

    [JsonPropertyName("type")]
    public string Type { get; set; } = "";

    [JsonPropertyName("ip")]
    public string Ip { get; set; } = "";

    [JsonPropertyName("port")]
    public int Port { get; set; }

    [JsonPropertyName("start")]
    public DateTime Start { get; set; }

    [JsonPropertyName("last_activity")]
    public DateTime LastActivity { get; set; }

    [JsonPropertyName("stop")]
    public DateTime? Stop { get; set; }

    [JsonPropertyName("reason")]
    public string Reason { get; set; } = "";

    [JsonPropertyName("rtt")]
    public string Rtt { get; set; } = "";

    [JsonPropertyName("uptime")]
    public string Uptime { get; set; } = "";

    [JsonPropertyName("idle")]
    public string Idle { get; set; } = "";

    [JsonPropertyName("pending_bytes")]
    public int Pending { get; set; }

    [JsonPropertyName("in_msgs")]
    public long InMsgs { get; set; }

    [JsonPropertyName("out_msgs")]
    public long OutMsgs { get; set; }

    [JsonPropertyName("in_bytes")]
    public long InBytes { get; set; }

    [JsonPropertyName("out_bytes")]
    public long OutBytes { get; set; }

    [JsonPropertyName("stalls")]
    public long Stalls { get; set; }

    [JsonPropertyName("subscriptions")]
    public uint NumSubs { get; set; }

    [JsonPropertyName("subscriptions_list")]
    public string[] Subs { get; set; } = [];

    [JsonPropertyName("subscriptions_list_detail")]
    public SubDetail[] SubsDetail { get; set; } = [];

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

    [JsonPropertyName("lang")]
    public string Lang { get; set; } = "";

    [JsonPropertyName("version")]
    public string Version { get; set; } = "";

    [JsonPropertyName("authorized_user")]
    public string AuthorizedUser { get; set; } = "";

    [JsonPropertyName("account")]
    public string Account { get; set; } = "";

    [JsonPropertyName("tls_version")]
    public string TlsVersion { get; set; } = "";

    [JsonPropertyName("tls_cipher_suite")]
    public string TlsCipherSuite { get; set; } = "";

    [JsonPropertyName("tls_peer_cert_subject")]
    public string TlsPeerCertSubject { get; set; } = "";

    [JsonPropertyName("tls_peer_certs")]
    public TLSPeerCert[] TlsPeerCerts { get; set; } = [];

    [JsonPropertyName("tls_first")]
    public bool TlsFirst { get; set; }

    [JsonPropertyName("mqtt_client")]
    public string MqttClient { get; set; } = "";

    [JsonPropertyName("jwt")]
    public string Jwt { get; set; } = "";

    [JsonPropertyName("issuer_key")]
    public string IssuerKey { get; set; } = "";

    [JsonPropertyName("name_tag")]
    public string NameTag { get; set; } = "";

    [JsonPropertyName("tags")]
    public string[] Tags { get; set; } = [];

    [JsonPropertyName("proxy")]
    public ProxyInfo? Proxy { get; set; }
}

/// <summary>
/// Proxy metadata for proxied client connections.
/// Corresponds to Go server/monitor.go ProxyInfo.
/// </summary>
public sealed class ProxyInfo
{
    [JsonPropertyName("key")]
    public string Key { get; set; } = "";
}

/// <summary>
/// TLS peer certificate detail for /connz parity with Go monitor.go.
/// </summary>
public sealed class TLSPeerCert
{
    [JsonPropertyName("subject")]
    public string Subject { get; set; } = "";

    [JsonPropertyName("subject_pk_sha256")]
    public string SubjectPKISha256 { get; set; } = "";

    [JsonPropertyName("cert_sha256")]
    public string CertSha256 { get; set; } = "";
}

/// <summary>
/// Subscription detail information.
/// Corresponds to Go server/monitor.go SubDetail struct.
/// </summary>
public sealed class SubDetail
{
    [JsonPropertyName("account")]
    public string Account { get; set; } = "";

    [JsonPropertyName("subject")]
    public string Subject { get; set; } = "";

    [JsonPropertyName("qgroup")]
    public string Queue { get; set; } = "";

    [JsonPropertyName("sid")]
    public string Sid { get; set; } = "";

    [JsonPropertyName("msgs")]
    public long Msgs { get; set; }

    [JsonPropertyName("max")]
    public long Max { get; set; }

    [JsonPropertyName("cid")]
    public ulong Cid { get; set; }
}

// ---------------------------------------------------------------------------
// Task 84 — account-scoped filter API (Gap 10.2)
// ---------------------------------------------------------------------------

/// <summary>
/// Lightweight connection info record used by the account-scoped filter API.
/// Decoupled from ConnInfo so filter logic can be tested without a running server.
/// Go reference: monitor.go ConnInfo (subset of fields relevant to account filtering).
/// </summary>
public sealed record ConnzConnectionInfo(
    ulong ClientId,
    string RemoteAddress,
    string? AccountName,
    string? Name,
    DateTime ConnectedAt,
    long InMsgs,
    long OutMsgs,
    long InBytes,
    long OutBytes);

/// <summary>
/// Paginated result returned by ConnzFilter.ApplyFilters.
/// </summary>
public sealed record ConnzFilterResult(
    IReadOnlyList<ConnzConnectionInfo> Connections,
    int Total,
    int Offset,
    int Limit);

/// <summary>
/// Query-string options for the account-scoped filter API.
/// Parses the ?acc=, ?state=, ?offset=, ?limit=, ?sort=, and ?desc= parameters.
/// Go reference: monitor.go ConnzOptions / Connz().
/// </summary>
public sealed class ConnzFilterOptions
{
    public string? AccountFilter { get; init; }

    /// <summary>"open", "closed", or "any" (default: "open")</summary>
    public string? StateFilter { get; init; }

    public int Offset { get; init; }

    public int Limit { get; init; } = 1024;

    /// <summary>
    /// Sort field for connection listing. Default: ConnzSortOption.ConnectionId.
    /// Go reference: monitor.go ConnzOptions.SortBy.
    /// </summary>
    public ConnzSortOption SortBy { get; init; } = ConnzSortOption.ConnectionId;

    /// <summary>
    /// When true, reverses the natural sort direction for the chosen SortBy option.
    /// Go reference: monitor.go ConnzOptions.SortBy (descending variant).
    /// </summary>
    public bool SortDescending { get; init; }

    /// <summary>
    /// Parses a raw query string into a ConnzFilterOptions instance.
    /// </summary>
    public static ConnzFilterOptions Parse(string? queryString)
    {
        if (string.IsNullOrEmpty(queryString))
            return new ConnzFilterOptions();

        var qs = queryString.TrimStart('?');

        string? accountFilter = null;
        string? stateFilter = null;
        int offset = 0;
        int limit = 1024;
        var sortBy = ConnzSortOption.ConnectionId;
        bool sortDescending = false;

        foreach (var pair in qs.Split('&', StringSplitOptions.RemoveEmptyEntries))
        {
            var eqIdx = pair.IndexOf('=');
            if (eqIdx < 0) continue;

            var key = Uri.UnescapeDataString(pair[..eqIdx]).ToLowerInvariant();
            var value = Uri.UnescapeDataString(pair[(eqIdx + 1)..]);

            switch (key)
            {
                case "acc":
                    accountFilter = value;
                    break;
                case "state":
                    stateFilter = value.ToLowerInvariant();
                    break;
                case "offset" when int.TryParse(value, out var o):
                    offset = o;
                    break;
                case "limit" when int.TryParse(value, out var l):
                    limit = l;
                    break;
                case "sort":
                    sortBy = ConnzSorter.Parse(value);
                    break;
                case "desc" when value is "1" or "true":
                    sortDescending = true;
                    break;
            }
        }

        return new ConnzFilterOptions
        {
            AccountFilter = accountFilter,
            StateFilter = stateFilter,
            Offset = offset,
            Limit = limit,
            SortBy = sortBy,
            SortDescending = sortDescending,
        };
    }
}

/// <summary>
/// Pure filtering helper for ConnzConnectionInfo collections.
/// Provides account-scoped filtering and pagination mirroring the Go server /connz ?acc= behaviour.
/// Go reference: monitor.go Connz() — account filter branch.
/// </summary>
public static class ConnzFilter
{
    /// <summary>
    /// Filters connections to only those whose AccountName matches accountName
    /// using a case-insensitive ordinal comparison.
    /// </summary>
    public static IReadOnlyList<ConnzConnectionInfo> FilterByAccount(
        IEnumerable<ConnzConnectionInfo> connections,
        string accountName) =>
        connections
            .Where(c => string.Equals(c.AccountName, accountName, StringComparison.OrdinalIgnoreCase))
            .ToList();

    /// <summary>
    /// Applies all filters specified in options and returns a paginated result.
    /// </summary>
    public static ConnzFilterResult ApplyFilters(
        IEnumerable<ConnzConnectionInfo> connections,
        ConnzFilterOptions options)
    {
        IEnumerable<ConnzConnectionInfo> filtered = connections;

        if (!string.IsNullOrEmpty(options.AccountFilter))
            filtered = filtered.Where(c =>
                string.Equals(c.AccountName, options.AccountFilter, StringComparison.OrdinalIgnoreCase));

        var list = filtered.ToList();
        var total = list.Count;
        var paged = list.Skip(options.Offset).Take(options.Limit).ToList();

        return new ConnzFilterResult(paged, total, options.Offset, options.Limit);
    }
}

// ---------------------------------------------------------------------------
// Task 85 — sort options for /connz (Gap 10.3)
// ---------------------------------------------------------------------------

/// <summary>
/// Public sort option enum for the /connz endpoint with human-readable names.
/// Go reference: server/monitor_sort_opts.go SortOpt constants.
/// </summary>
public enum ConnzSortOption
{
    /// <summary>Sort by connection ID (CID). Default.</summary>
    ConnectionId,

    /// <summary>Sort by connection start time (ascending by default).</summary>
    Start,

    /// <summary>Sort by bytes sent to the client (OutBytes), descending by default.</summary>
    BytesTo,

    /// <summary>Sort by bytes received from the client (InBytes), descending by default.</summary>
    BytesFrom,

    /// <summary>Sort by messages sent to the client (OutMsgs), descending by default.</summary>
    MsgsTo,

    /// <summary>Sort by messages received from the client (InMsgs), descending by default.</summary>
    MsgsFrom,

    /// <summary>Sort by subscription count, descending by default.</summary>
    Subscriptions,

    /// <summary>Sort by pending bytes, descending by default.</summary>
    Pending,

    /// <summary>Sort by connection duration (longest first), descending by default.</summary>
    Uptime,

    /// <summary>Sort by idle time (most idle first), descending by default.</summary>
    Idle,

    /// <summary>Sort by last activity time, descending by default.</summary>
    LastActivity,
}

/// <summary>
/// Provides parsing and sorting utilities for ConnInfo collections based on ConnzSortOption.
/// Go reference: server/monitor_sort_opts.go, server/monitor.go Connz().
/// </summary>
public static class ConnzSorter
{
    /// <summary>
    /// Parses a query-string sort parameter to ConnzSortOption.
    /// Returns ConnzSortOption.ConnectionId for null, empty, or unrecognised values.
    /// Go reference: server/monitor_sort_opts.go UnmarshalJSON.
    /// </summary>
    public static ConnzSortOption Parse(string? sortBy) =>
        sortBy?.ToLowerInvariant() switch
        {
            "start"      => ConnzSortOption.Start,
            "bytes_to"   => ConnzSortOption.BytesTo,
            "bytes_from" => ConnzSortOption.BytesFrom,
            "msgs_to"    => ConnzSortOption.MsgsTo,
            "msgs_from"  => ConnzSortOption.MsgsFrom,
            "subs"       => ConnzSortOption.Subscriptions,
            "pending"    => ConnzSortOption.Pending,
            "uptime"     => ConnzSortOption.Uptime,
            "idle"       => ConnzSortOption.Idle,
            "last"       => ConnzSortOption.LastActivity,
            _            => ConnzSortOption.ConnectionId,
        };

    /// <summary>
    /// Sorts a collection of ConnInfo records by the specified option and direction.
    /// Traffic/activity metrics default to descending (highest first); ConnectionId and Start
    /// default to ascending. Setting descending=true reverses the default.
    /// Go reference: monitor.go Connz() sort switch.
    /// </summary>
    public static IReadOnlyList<ConnInfo> Sort(
        IEnumerable<ConnInfo> connections,
        ConnzSortOption sortBy,
        bool descending = false)
    {
        var now = DateTime.UtcNow;

        return sortBy switch
        {
            ConnzSortOption.Start =>
                descending
                    ? [.. connections.OrderByDescending(c => c.Start)]
                    : [.. connections.OrderBy(c => c.Start)],

            ConnzSortOption.BytesTo =>
                descending
                    ? [.. connections.OrderBy(c => c.OutBytes)]
                    : [.. connections.OrderByDescending(c => c.OutBytes)],

            ConnzSortOption.BytesFrom =>
                descending
                    ? [.. connections.OrderBy(c => c.InBytes)]
                    : [.. connections.OrderByDescending(c => c.InBytes)],

            ConnzSortOption.MsgsTo =>
                descending
                    ? [.. connections.OrderBy(c => c.OutMsgs)]
                    : [.. connections.OrderByDescending(c => c.OutMsgs)],

            ConnzSortOption.MsgsFrom =>
                descending
                    ? [.. connections.OrderBy(c => c.InMsgs)]
                    : [.. connections.OrderByDescending(c => c.InMsgs)],

            ConnzSortOption.Subscriptions =>
                descending
                    ? [.. connections.OrderBy(c => c.NumSubs)]
                    : [.. connections.OrderByDescending(c => c.NumSubs)],

            ConnzSortOption.Pending =>
                descending
                    ? [.. connections.OrderBy(c => c.Pending)]
                    : [.. connections.OrderByDescending(c => c.Pending)],

            ConnzSortOption.Uptime =>
                descending
                    ? [.. connections.OrderBy(c => now - c.Start)]
                    : [.. connections.OrderByDescending(c => now - c.Start)],

            ConnzSortOption.Idle =>
                descending
                    ? [.. connections.OrderBy(c => now - c.LastActivity)]
                    : [.. connections.OrderByDescending(c => now - c.LastActivity)],

            ConnzSortOption.LastActivity =>
                descending
                    ? [.. connections.OrderBy(c => c.LastActivity)]
                    : [.. connections.OrderByDescending(c => c.LastActivity)],

            // ConnectionId and default: ascending by CID
            _ =>
                descending
                    ? [.. connections.OrderByDescending(c => c.Cid)]
                    : [.. connections.OrderBy(c => c.Cid)],
        };
    }
}

// ---------------------------------------------------------------------------
// Task 89 — closed connection reason tracking (Gap 10.7)
// ---------------------------------------------------------------------------

/// <summary>
/// Public enum representing the reason a client connection was closed.
/// Used in <see cref="ClosedClient.Reason"/> for /connz monitoring output.
/// Go reference: server/monitor.go ClosedState.String() — the set of reasons
/// surfaced to monitoring consumers.
/// </summary>
public enum ClosedReason
{
    Unknown,
    ClientClosed,
    ServerShutdown,
    AuthTimeout,
    AuthViolation,
    MaxConnectionsExceeded,
    SlowConsumer,
    WriteError,
    ReadError,
    ParseError,
    StaleConnection,
    MaxPayloadExceeded,
    MaxSubscriptionsExceeded,
    DuplicateRoute,
    AccountExpired,
    Revoked,
    ServerError,
    KickedByOperator,
}

/// <summary>
/// Helpers for converting <see cref="ClosedReason"/> to and from the Go-compatible
/// reason strings emitted by the reference server's /connz endpoint.
/// Go reference: server/monitor.go ClosedState.String().
/// </summary>
public static class ClosedReasonHelper
{
    /// <summary>
    /// Returns the Go-compatible human-readable string for the given reason.
    /// These strings match the Go server's monitor.go ClosedState.String() output
    /// so that tooling consuming the /connz endpoint sees identical values.
    /// </summary>
    public static string ToReasonString(ClosedReason reason) => reason switch
    {
        ClosedReason.ClientClosed           => "Client Closed",
        ClosedReason.ServerShutdown         => "Server Shutdown",
        ClosedReason.AuthTimeout            => "Authentication Timeout",
        ClosedReason.AuthViolation          => "Authentication Failure",
        ClosedReason.MaxConnectionsExceeded => "Maximum Connections Exceeded",
        ClosedReason.SlowConsumer           => "Slow Consumer (Pending Bytes)",
        ClosedReason.WriteError             => "Write Error",
        ClosedReason.ReadError              => "Read Error",
        ClosedReason.ParseError             => "Parse Error",
        ClosedReason.StaleConnection        => "Stale Connection",
        ClosedReason.MaxPayloadExceeded     => "Maximum Message Payload Exceeded",
        ClosedReason.MaxSubscriptionsExceeded => "Maximum Subscriptions Exceeded",
        ClosedReason.DuplicateRoute         => "Duplicate Route",
        ClosedReason.AccountExpired         => "Authentication Expired",
        ClosedReason.Revoked                => "Credentials Revoked",
        ClosedReason.ServerError            => "Internal Server Error",
        ClosedReason.KickedByOperator       => "Kicked",
        _                                   => "Unknown",
    };

    /// <summary>
    /// Parses a Go-compatible reason string back to a <see cref="ClosedReason"/>.
    /// Returns <see cref="ClosedReason.Unknown"/> for null, empty, or unrecognised values.
    /// </summary>
    public static ClosedReason FromReasonString(string? reason) => reason switch
    {
        "Client Closed"                    => ClosedReason.ClientClosed,
        "Server Shutdown"                  => ClosedReason.ServerShutdown,
        "Authentication Timeout"           => ClosedReason.AuthTimeout,
        "Authentication Failure"           => ClosedReason.AuthViolation,
        "Maximum Connections Exceeded"     => ClosedReason.MaxConnectionsExceeded,
        "Slow Consumer (Pending Bytes)"    => ClosedReason.SlowConsumer,
        "Write Error"                      => ClosedReason.WriteError,
        "Read Error"                       => ClosedReason.ReadError,
        "Parse Error"                      => ClosedReason.ParseError,
        "Stale Connection"                 => ClosedReason.StaleConnection,
        "Maximum Message Payload Exceeded" => ClosedReason.MaxPayloadExceeded,
        "Maximum Subscriptions Exceeded"   => ClosedReason.MaxSubscriptionsExceeded,
        "Duplicate Route"                  => ClosedReason.DuplicateRoute,
        "Authentication Expired"           => ClosedReason.AccountExpired,
        "Credentials Revoked"              => ClosedReason.Revoked,
        "Internal Server Error"            => ClosedReason.ServerError,
        "Kicked"                           => ClosedReason.KickedByOperator,
        _                                  => ClosedReason.Unknown,
    };

    /// <summary>
    /// Returns true when the close was initiated by the client itself (not the server).
    /// Go reference: server/client.go closeConnection — client-side disconnect path.
    /// </summary>
    public static bool IsClientInitiated(ClosedReason reason) =>
        reason == ClosedReason.ClientClosed;

    /// <summary>
    /// Returns true when the close was caused by an authentication or authorisation failure.
    /// Go reference: server/auth.go getAuthErrClosedState.
    /// </summary>
    public static bool IsAuthRelated(ClosedReason reason) =>
        reason is ClosedReason.AuthTimeout
               or ClosedReason.AuthViolation
               or ClosedReason.AccountExpired
               or ClosedReason.Revoked;

    /// <summary>
    /// Returns true when the close was caused by a resource limit being exceeded.
    /// Go reference: server/client.go maxPayloadExceeded / maxSubscriptionsExceeded paths.
    /// </summary>
    public static bool IsResourceLimit(ClosedReason reason) =>
        reason is ClosedReason.MaxConnectionsExceeded
               or ClosedReason.MaxPayloadExceeded
               or ClosedReason.MaxSubscriptionsExceeded
               or ClosedReason.SlowConsumer;
}

// ---------------------------------------------------------------------------
// Internal types — used by ConnzHandler and existing sort logic
// ---------------------------------------------------------------------------

/// <summary>
/// Sort options for connection listing.
/// Corresponds to Go server/monitor_sort_opts.go SortOpt type.
/// </summary>
public enum SortOpt
{
    ByCid,
    ByStart,
    BySubs,
    ByPending,
    ByMsgsTo,
    ByMsgsFrom,
    ByBytesTo,
    ByBytesFrom,
    ByLast,
    ByIdle,
    ByUptime,
    ByRtt,
    ByStop,
    ByReason,
}

public static class SortOptExtensions
{
    /// <summary>
    /// Go parity for SortOpt.IsValid().
    /// </summary>
    public static bool IsValid(this SortOpt sort) =>
        sort is SortOpt.ByCid
            or SortOpt.ByStart
            or SortOpt.BySubs
            or SortOpt.ByPending
            or SortOpt.ByMsgsTo
            or SortOpt.ByMsgsFrom
            or SortOpt.ByBytesTo
            or SortOpt.ByBytesFrom
            or SortOpt.ByLast
            or SortOpt.ByIdle
            or SortOpt.ByUptime
            or SortOpt.ByRtt
            or SortOpt.ByStop
            or SortOpt.ByReason;
}

/// <summary>
/// Connection state filter.
/// Corresponds to Go server/monitor.go ConnState type.
/// </summary>
public enum ConnState
{
    Open,
    Closed,
    All,
}

/// <summary>
/// Options passed to Connz() for filtering and sorting.
/// Corresponds to Go server/monitor.go ConnzOptions struct.
/// </summary>
public sealed class ConnzOptions
{
    public SortOpt Sort { get; set; } = SortOpt.ByCid;

    public bool Subscriptions { get; set; }

    public bool SubscriptionsDetail { get; set; }

    public ConnState State { get; set; } = ConnState.Open;

    public string User { get; set; } = "";

    public string Account { get; set; } = "";

    public string FilterSubject { get; set; } = "";

    public string MqttClient { get; set; } = "";

    /// <summary>
    /// When non-zero, returns only the connection with this CID.
    /// Go reference: monitor.go ConnzOptions.CID.
    /// </summary>
    public ulong Cid { get; set; }

    /// <summary>
    /// Whether to include authorized user info.
    /// Go reference: monitor.go ConnzOptions.Username.
    /// </summary>
    public bool Auth { get; set; }

    public int Offset { get; set; }

    public int Limit { get; set; } = 1024;
}
