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

using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json.Serialization;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server;

// ============================================================================
// GatewayzOptions — query options for the Gatewayz endpoint
// Mirrors Go <c>GatewayzOptions</c> struct in server/monitor.go.
// ============================================================================

/// <summary>
/// Options that control the output of a <c>Gatewayz</c> monitoring query.
/// Mirrors Go <c>GatewayzOptions</c> struct in server/monitor.go.
/// </summary>
public sealed class GatewayzOptions
{
    /// <summary>When non-empty, limits output to the gateway with this name. Mirrors Go <c>Name</c>.</summary>
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

    /// <summary>When true, includes accounts with their interest. Mirrors Go <c>Accounts</c>.</summary>
    [JsonPropertyName("accounts")]
    public bool Accounts { get; set; }

    /// <summary>Limits accounts to this specific name (implies <see cref="Accounts"/>). Mirrors Go <c>AccountName</c>.</summary>
    [JsonPropertyName("account_name")]
    public string AccountName { get; set; } = string.Empty;

    /// <summary>When true, subscription subjects are included in account results. Mirrors Go <c>AccountSubscriptions</c>.</summary>
    [JsonPropertyName("subscriptions")]
    public bool AccountSubscriptions { get; set; }

    /// <summary>When true, verbose subscription details are included. Mirrors Go <c>AccountSubscriptionsDetail</c>.</summary>
    [JsonPropertyName("subscriptions_detail")]
    public bool AccountSubscriptionsDetail { get; set; }
}

// ============================================================================
// Gatewayz — top-level gateway monitoring response
// Mirrors Go <c>Gatewayz</c> struct in server/monitor.go.
// ============================================================================

/// <summary>
/// Top-level response type for the <c>/gatewayz</c> monitoring endpoint.
/// Mirrors Go <c>Gatewayz</c> struct in server/monitor.go.
/// </summary>
public sealed class Gatewayz
{
    [JsonPropertyName("server_id")]
    public string Id { get; set; } = string.Empty;

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

    [JsonPropertyName("name")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Name { get; set; }

    [JsonPropertyName("host")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Host { get; set; }

    [JsonPropertyName("port")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public int Port { get; set; }

    [JsonPropertyName("outbound_gateways")]
    public Dictionary<string, RemoteGatewayz> OutboundGateways { get; set; } = new();

    [JsonPropertyName("inbound_gateways")]
    public Dictionary<string, List<RemoteGatewayz>> InboundGateways { get; set; } = new();
}

// ============================================================================
// RemoteGatewayz — information about a single remote gateway connection
// Mirrors Go <c>RemoteGatewayz</c> struct in server/monitor.go.
// ============================================================================

/// <summary>
/// Information about a single outbound or inbound gateway connection.
/// Mirrors Go <c>RemoteGatewayz</c> struct in server/monitor.go.
/// </summary>
public sealed class RemoteGatewayz
{
    /// <summary>True if the gateway was explicitly configured (not implicit). Mirrors Go <c>IsConfigured</c>.</summary>
    [JsonPropertyName("configured")]
    public bool IsConfigured { get; set; }

    /// <summary>Connection details. Mirrors Go <c>Connection *ConnInfo</c>.</summary>
    [JsonPropertyName("connection")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public ConnInfo? Connection { get; set; }

    /// <summary>Per-account interest information. Mirrors Go <c>Accounts []*AccountGatewayz</c>.</summary>
    [JsonPropertyName("accounts")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public List<AccountGatewayz>? Accounts { get; set; }
}

// ============================================================================
// AccountGatewayz — per-account interest mode on a gateway
// Mirrors Go <c>AccountGatewayz</c> struct in server/monitor.go.
// ============================================================================

/// <summary>
/// Per-account interest mode information for a gateway connection.
/// Mirrors Go <c>AccountGatewayz</c> struct in server/monitor.go.
/// </summary>
public sealed class AccountGatewayz
{
    [JsonPropertyName("name")]
    public string Name { get; set; } = string.Empty;

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

    [JsonPropertyName("no_interest_count")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public int NoInterestCount { get; set; }

    [JsonPropertyName("interest_only_threshold")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public int InterestOnlyThreshold { get; set; }

    [JsonPropertyName("num_subs")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public int TotalSubscriptions { get; set; }

    [JsonPropertyName("num_queue_subs")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
    public int NumQueueSubscriptions { get; set; }

    [JsonPropertyName("subscriptions_list")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public List<string>? Subs { get; set; }

    [JsonPropertyName("subscriptions_list_detail")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public List<SubDetail>? SubsDetail { get; set; }
}

// ============================================================================
// ExtImport — external account import detail for /accountz
// Mirrors Go <c>ExtImport</c> struct in server/monitor.go.
// ============================================================================

/// <summary>
/// External view of a service import entry, as returned by the <c>/accountz</c> endpoint.
/// Mirrors Go <c>ExtImport</c> struct in server/monitor.go.
/// Note: The JWT <c>Import</c> embedded struct fields are inlined here since the
/// nats.io/jwt library is not yet ported.
/// </summary>
public sealed class ExtImport
{
    /// <summary>Whether this import is invalid. Mirrors Go <c>Invalid bool</c>.</summary>
    [JsonPropertyName("invalid")]
    public bool Invalid { get; set; }

    /// <summary>Whether the requestor's client info is shared. Mirrors Go <c>Share bool</c>.</summary>
    [JsonPropertyName("share")]
    public bool Share { get; set; }

    /// <summary>Whether latency tracking is enabled. Mirrors Go <c>Tracking bool</c>.</summary>
    [JsonPropertyName("tracking")]
    public bool Tracking { get; set; }

    /// <summary>Headers used when latency is triggered by a header. Mirrors Go <c>TrackingHdr http.Header</c>.</summary>
    [JsonPropertyName("tracking_header")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public Dictionary<string, string[]>? TrackingHeader { get; set; }

    /// <summary>
    /// Latency configuration from the exporting account's JWT claim.
    /// Mirrors Go <c>Latency *jwt.ServiceLatency</c>.
    /// Sampling and subject are stored directly since jwt lib is not ported.
    /// </summary>
    [JsonPropertyName("latency")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public ExtServiceLatency? Latency { get; set; }

    /// <summary>First-leg latency measurement. Mirrors Go <c>M1 *ServiceLatency</c>.</summary>
    [JsonPropertyName("m1")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public ServiceLatency? M1 { get; set; }

    // Inlined jwt.Import fields.

    /// <summary>Subject of the imported service. Mirrors Go <c>jwt.Import.Subject</c>.</summary>
    [JsonPropertyName("subject")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Subject { get; set; }

    /// <summary>Account that exports the service. Mirrors Go <c>jwt.Import.Account</c>.</summary>
    [JsonPropertyName("account")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? Account { get; set; }

    /// <summary>Local subject used on the importing account. Mirrors Go <c>jwt.Import.LocalSubject</c>.</summary>
    [JsonPropertyName("local_subject")]
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string? LocalSubject { get; set; }
}

// ============================================================================
// ExtServiceLatency — external representation of service latency config
// Used by ExtImport in place of jwt.ServiceLatency.
// ============================================================================

/// <summary>
/// External representation of service latency configuration, used in <see cref="ExtImport"/>.
/// Mirrors Go <c>jwt.ServiceLatency</c> from nats.io/jwt/v2.
/// </summary>
public sealed class ExtServiceLatency
{
    [JsonPropertyName("sampling")]
    public int Sampling { get; set; }

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

// ============================================================================
// MonitorHelpers — standalone helper functions
// Mirrors standalone functions in server/monitor.go.
// ============================================================================

/// <summary>
/// Standalone helper functions used by the monitoring subsystem.
/// Mirrors package-level functions from <c>server/monitor.go</c>.
/// </summary>
internal static class MonitorHelpers
{
    // -------------------------------------------------------------------------
    // newSubsDetailList
    // -------------------------------------------------------------------------

    /// <summary>
    /// Builds a verbose subscription detail list for a client connection.
    /// Client must be locked by caller.
    /// Mirrors Go <c>newSubsDetailList</c>.
    /// </summary>
    internal static List<SubDetail> NewSubsDetailList(ClientConnection client)
    {
        var result = new List<SubDetail>(client.Subs?.Count ?? 0);
        if (client.Subs == null)
            return result;
        foreach (var sub in client.Subs.Values)
            result.Add(NewClientSubDetail(sub, client.Cid));
        return result;
    }

    // -------------------------------------------------------------------------
    // newSubsList
    // -------------------------------------------------------------------------

    /// <summary>
    /// Builds a plain subscription subject list for a client connection.
    /// Client must be locked by caller.
    /// Mirrors Go <c>newSubsList</c>.
    /// </summary>
    internal static List<string> NewSubsList(ClientConnection client)
    {
        var result = new List<string>(client.Subs?.Count ?? 0);
        if (client.Subs == null)
            return result;
        foreach (var sub in client.Subs.Values)
            result.Add(Encoding.UTF8.GetString(sub.Subject));
        return result;
    }

    // -------------------------------------------------------------------------
    // createProxyInfo
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns a <see cref="ProxyInfo"/> if the connection has a proxy key set, or <c>null</c>.
    /// Client lock must be held on entry.
    /// Mirrors Go <c>createProxyInfo</c>.
    /// </summary>
    internal static ProxyInfo? CreateProxyInfo(ClientConnection c)
    {
        if (string.IsNullOrEmpty(c.ProxyKey))
            return null;
        return new ProxyInfo { Key = c.ProxyKey };
    }

    // -------------------------------------------------------------------------
    // makePeerCerts
    // -------------------------------------------------------------------------

    /// <summary>
    /// Converts a list of X.509 peer certificates into <see cref="TlsPeerCert"/> summary records.
    /// Each record contains subject string, SPKI SHA-256 hex, and certificate SHA-256 hex.
    /// Mirrors Go <c>makePeerCerts</c>.
    /// </summary>
    internal static List<TlsPeerCert> MakePeerCerts(IReadOnlyList<X509Certificate2> peerCerts)
    {
        var result = new List<TlsPeerCert>(peerCerts.Count);
        foreach (var cert in peerCerts)
        {
            var spkiHash = SHA256.HashData(cert.PublicKey.ExportSubjectPublicKeyInfo());
            var certHash = SHA256.HashData(cert.RawData);
            result.Add(new TlsPeerCert
            {
                Subject          = cert.Subject,
                SubjectPkiSha256 = Convert.ToHexString(spkiHash).ToLowerInvariant(),
                CertSha256       = Convert.ToHexString(certHash).ToLowerInvariant(),
            });
        }
        return result;
    }

    // -------------------------------------------------------------------------
    // decodeBool
    // -------------------------------------------------------------------------

    /// <summary>
    /// Parses a boolean query-string parameter from an HTTP listener request.
    /// Writes a 400 status and returns an error if the value cannot be parsed.
    /// Mirrors Go <c>decodeBool</c>.
    /// </summary>
    internal static (bool Value, Exception? Error) DecodeBool(
        HttpListenerResponse response,
        System.Collections.Specialized.NameValueCollection query,
        string param)
    {
        var str = query[param] ?? string.Empty;
        if (str.Length == 0)
            return (false, null);
        if (bool.TryParse(str, out var val))
            return (val, null);
        if (str == "1") return (true, null);
        if (str == "0") return (false, null);
        var err = new FormatException($"Error decoding boolean for '{param}': {str}");
        response.StatusCode = 400;
        return (false, err);
    }

    // -------------------------------------------------------------------------
    // decodeUint64
    // -------------------------------------------------------------------------

    /// <summary>
    /// Parses a uint64 query-string parameter from an HTTP listener request.
    /// Mirrors Go <c>decodeUint64</c>.
    /// </summary>
    internal static (ulong Value, Exception? Error) DecodeUint64(
        HttpListenerResponse response,
        System.Collections.Specialized.NameValueCollection query,
        string param)
    {
        var str = query[param] ?? string.Empty;
        if (str.Length == 0)
            return (0, null);
        if (ulong.TryParse(str, out var val))
            return (val, null);
        var err = new FormatException($"Error decoding uint64 for '{param}': {str}");
        response.StatusCode = 400;
        return (0, err);
    }

    // -------------------------------------------------------------------------
    // decodeInt
    // -------------------------------------------------------------------------

    /// <summary>
    /// Parses an int query-string parameter from an HTTP listener request.
    /// Mirrors Go <c>decodeInt</c>.
    /// </summary>
    internal static (int Value, Exception? Error) DecodeInt(
        HttpListenerResponse response,
        System.Collections.Specialized.NameValueCollection query,
        string param)
    {
        var str = query[param] ?? string.Empty;
        if (str.Length == 0)
            return (0, null);
        if (int.TryParse(str, out var val))
            return (val, null);
        var err = new FormatException($"Error decoding int for '{param}': {str}");
        response.StatusCode = 400;
        return (0, err);
    }

    // -------------------------------------------------------------------------
    // decodeState
    // -------------------------------------------------------------------------

    /// <summary>
    /// Parses the connection-state filter query parameter.
    /// Mirrors Go <c>decodeState</c>.
    /// </summary>
    internal static (ConnState Value, Exception? Error) DecodeState(
        HttpListenerResponse response,
        System.Collections.Specialized.NameValueCollection query)
    {
        var str = query["state"] ?? string.Empty;
        if (str.Length == 0)
            return (ConnState.ConnOpen, null);
        switch (str.ToLowerInvariant())
        {
            case "open":   return (ConnState.ConnOpen, null);
            case "closed": return (ConnState.ConnClosed, null);
            case "any":
            case "all":    return (ConnState.ConnAll, null);
        }
        var err = new FormatException($"Error decoding state for {str}");
        response.StatusCode = 400;
        return (default, err);
    }

    // -------------------------------------------------------------------------
    // decodeSubs
    // -------------------------------------------------------------------------

    /// <summary>
    /// Parses the <c>subs</c> query parameter into <c>subs</c> and <c>subsDet</c> flags.
    /// Mirrors Go <c>decodeSubs</c>.
    /// </summary>
    internal static (bool Subs, bool SubsDetail, Exception? Error) DecodeSubs(
        HttpListenerResponse response,
        System.Collections.Specialized.NameValueCollection query)
    {
        var raw = query["subs"] ?? string.Empty;
        if (raw.Equals("detail", StringComparison.OrdinalIgnoreCase))
            return (false, true, null);
        var (subs, err) = DecodeBool(response, query, "subs");
        return (subs, false, err);
    }

    // -------------------------------------------------------------------------
    // newSubDetail
    // -------------------------------------------------------------------------

    /// <summary>
    /// Creates a <see cref="SubDetail"/> including account name from the owning client.
    /// Client must be locked on entry.
    /// Mirrors Go <c>newSubDetail</c>.
    /// </summary>
    internal static SubDetail NewSubDetail(Internal.Subscription sub, ClientConnection client)
    {
        var sd  = NewClientSubDetail(sub, client.Cid);
        var acc = client.Account() as Account;
        sd.Account    = acc?.GetName();
        sd.AccountTag = acc?.GetNameTag();
        return sd;
    }

    // -------------------------------------------------------------------------
    // newClientSubDetail
    // -------------------------------------------------------------------------

    /// <summary>
    /// Creates a <see cref="SubDetail"/> from a subscription (no account name).
    /// Mirrors Go <c>newClientSubDetail</c>.
    /// </summary>
    internal static SubDetail NewClientSubDetail(Internal.Subscription sub, ulong cid)
    {
        return new SubDetail
        {
            Subject = Encoding.UTF8.GetString(sub.Subject),
            Queue   = sub.Queue is { Length: > 0 }
                        ? Encoding.UTF8.GetString(sub.Queue)
                        : null,
            Sid     = sub.Sid is { Length: > 0 }
                        ? Encoding.UTF8.GetString(sub.Sid)
                        : string.Empty,
            Cid     = cid,
        };
    }

    // -------------------------------------------------------------------------
    // myUptime
    // -------------------------------------------------------------------------

    /// <summary>
    /// Formats a <see cref="TimeSpan"/> as a human-readable uptime string
    /// (e.g. <c>"2d3h14m5s"</c>, <c>"45m30s"</c>).
    /// Mirrors Go <c>myUptime</c>.
    /// </summary>
    internal static string MyUptime(TimeSpan d)
    {
        var tsecs = (long)d.TotalSeconds;
        var tmins = tsecs / 60;
        var thrs  = tmins / 60;
        var tdays = thrs  / 24;
        var tyrs  = tdays / 365;

        if (tyrs > 0)
            return $"{tyrs}y{tdays % 365}d{thrs % 24}h{tmins % 60}m{tsecs % 60}s";
        if (tdays > 0)
            return $"{tdays}d{thrs % 24}h{tmins % 60}m{tsecs % 60}s";
        if (thrs > 0)
            return $"{thrs}h{tmins % 60}m{tsecs % 60}s";
        if (tmins > 0)
            return $"{tmins}m{tsecs % 60}s";
        return $"{tsecs}s";
    }

    // -------------------------------------------------------------------------
    // tlsCertNotAfter
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns the expiry date of the first certificate in the given collection,
    /// or <see cref="DateTime.MinValue"/> if the collection is empty.
    /// Mirrors Go <c>tlsCertNotAfter</c>.
    /// </summary>
    internal static DateTime TlsCertNotAfter(X509CertificateCollection? certs)
    {
        if (certs == null || certs.Count == 0)
            return DateTime.MinValue;
        if (certs[0] is X509Certificate2 cert2)
            return cert2.NotAfter.ToUniversalTime();
        try
        {
            var parsed = new X509Certificate2(certs[0]);
            return parsed.NotAfter.ToUniversalTime();
        }
        catch
        {
            return DateTime.MinValue;
        }
    }

    // -------------------------------------------------------------------------
    // urlsToStrings
    // -------------------------------------------------------------------------

    /// <summary>
    /// Converts a list of <see cref="Uri"/> objects to their <c>Host:Port</c> string form.
    /// Mirrors Go <c>urlsToStrings</c>.
    /// </summary>
    internal static string[] UrlsToStrings(IReadOnlyList<Uri> urls)
    {
        var result = new string[urls.Count];
        for (int i = 0; i < urls.Count; i++)
            result[i] = urls[i].Authority; // "host:port"
        return result;
    }

    // -------------------------------------------------------------------------
    // getPinnedCertsAsSlice
    // -------------------------------------------------------------------------

    /// <summary>
    /// Converts a <see cref="PinnedCertSet"/> to a plain string array.
    /// Returns <c>null</c> if the set is empty.
    /// Mirrors Go <c>getPinnedCertsAsSlice</c>.
    /// </summary>
    internal static string[]? GetPinnedCertsAsSlice(PinnedCertSet? certs)
    {
        if (certs == null || certs.Count == 0)
            return null;
        var result = new string[certs.Count];
        certs.CopyTo(result);
        return result;
    }

    // -------------------------------------------------------------------------
    // getMonitorGWOptions
    // -------------------------------------------------------------------------

    /// <summary>
    /// Extracts gateway name filter and accounts flag from <see cref="GatewayzOptions"/>.
    /// When <c>AccountName</c> is set but <c>Accounts</c> is false, the accounts flag is
    /// implicitly promoted to true.
    /// Mirrors Go <c>getMonitorGWOptions</c>.
    /// </summary>
    internal static (string Name, bool Accounts) GetMonitorGWOptions(GatewayzOptions? opts)
    {
        if (opts == null)
            return (string.Empty, false);
        var name = opts.Name;
        var accs = opts.Accounts;
        if (!accs && !string.IsNullOrEmpty(opts.AccountName))
            accs = true;
        return (name, accs);
    }

    // -------------------------------------------------------------------------
    // createOutboundRemoteGatewayz
    // -------------------------------------------------------------------------

    /// <summary>
    /// Builds a <see cref="RemoteGatewayz"/> from an outbound gateway client connection.
    /// Client lock is acquired internally.
    /// Mirrors Go <c>createOutboundRemoteGatewayz</c>.
    /// Note: Per-account interest detail (outsim) requires a running gateway layer;
    /// account lists are empty in the current partial port.
    /// </summary>
    internal static (string Name, RemoteGatewayz? Rgw) CreateOutboundRemoteGatewayz(
        ClientConnection c,
        GatewayzOptions? opts,
        DateTime now,
        bool doAccs)
    {
        lock (c)
        {
            var name = c.Gateway?.Name;
            if (string.IsNullOrEmpty(name))
                return (string.Empty, null);

            var isConfigured = c.Gateway?.Cfg != null && !c.Gateway.Cfg.IsImplicit();
            var rgw = new RemoteGatewayz
            {
                IsConfigured = isConfigured,
                Connection   = new ConnInfo(),
                Accounts     = doAccs ? [] : null,
            };
            return (name, rgw);
        }
    }

    // -------------------------------------------------------------------------
    // createOutboundAccountsGatewayz
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns the per-account interest list for an outbound gateway connection.
    /// Mirrors Go <c>createOutboundAccountsGatewayz</c>.
    /// Note: Requires fully-ported gateway outsim state; returns empty list in current port.
    /// </summary>
    internal static List<AccountGatewayz> CreateOutboundAccountsGatewayz(
        GatewayzOptions? opts,
        ClientConnection c)
    {
        // outsim not yet ported — return empty.
        return [];
    }

    // -------------------------------------------------------------------------
    // createAccountOutboundGatewayz
    // -------------------------------------------------------------------------

    /// <summary>
    /// Creates an <see cref="AccountGatewayz"/> entry for a named outbound account.
    /// Mirrors Go <c>createAccountOutboundGatewayz</c>.
    /// Note: Requires fully-ported outsie state; returns Optimistic defaults.
    /// </summary>
    internal static AccountGatewayz CreateAccountOutboundGatewayz(
        GatewayzOptions? opts,
        string name,
        object? outsie)
    {
        // outsie not yet ported — return Optimistic defaults.
        return new AccountGatewayz
        {
            Name         = name,
            InterestMode = GatewayInterestMode.Optimistic.String(),
        };
    }

    // -------------------------------------------------------------------------
    // createInboundAccountsGatewayz
    // -------------------------------------------------------------------------

    /// <summary>
    /// Returns the per-account interest list for an inbound gateway connection.
    /// Mirrors Go <c>createInboundAccountsGatewayz</c>.
    /// Note: Requires fully-ported gateway insim state; returns empty list.
    /// </summary>
    internal static List<AccountGatewayz> CreateInboundAccountsGatewayz(
        GatewayzOptions? opts,
        ClientConnection c)
    {
        // insim not yet ported — return empty.
        return [];
    }

    // -------------------------------------------------------------------------
    // createInboundAccountGatewayz
    // -------------------------------------------------------------------------

    /// <summary>
    /// Creates an <see cref="AccountGatewayz"/> entry for a named inbound account.
    /// Mirrors Go <c>createInboundAccountGatewayz</c>.
    /// Note: Requires fully-ported insie state; returns Optimistic defaults.
    /// </summary>
    internal static AccountGatewayz CreateInboundAccountGatewayz(string name, object? insie)
    {
        // insie not yet ported — return Optimistic defaults.
        return new AccountGatewayz
        {
            Name         = name,
            InterestMode = GatewayInterestMode.Optimistic.String(),
        };
    }

    // -------------------------------------------------------------------------
    // ResponseHandler / handleResponse
    // -------------------------------------------------------------------------

    /// <summary>
    /// Writes a JSON (or JSONP) HTTP monitoring response with <c>200 OK</c>.
    /// Mirrors Go <c>ResponseHandler</c>.
    /// </summary>
    internal static void ResponseHandler(
        HttpListenerResponse response,
        HttpListenerRequest request,
        byte[] data)
    {
        HandleResponse(200, response, request, data);
    }

    /// <summary>
    /// Writes a JSON (or JSONP if the <c>callback</c> query param is set) HTTP monitoring response.
    /// Mirrors Go <c>handleResponse</c>.
    /// </summary>
    internal static void HandleResponse(
        int statusCode,
        HttpListenerResponse response,
        HttpListenerRequest request,
        byte[] data)
    {
        var callback = request.QueryString["callback"] ?? string.Empty;
        response.StatusCode = statusCode;

        if (callback.Length > 0)
        {
            response.ContentType = "application/javascript";
            var prefix = Encoding.UTF8.GetBytes($"{callback}(");
            var suffix = Encoding.UTF8.GetBytes(")");
            response.OutputStream.Write(prefix);
            response.OutputStream.Write(data);
            response.OutputStream.Write(suffix);
        }
        else
        {
            response.ContentType = "application/json";
            response.Headers["Access-Control-Allow-Origin"] = "*";
            response.OutputStream.Write(data);
        }
    }

    // -------------------------------------------------------------------------
    // newExtServiceLatency
    // -------------------------------------------------------------------------

    /// <summary>
    /// Converts an <see cref="InternalServiceLatency"/> to an <see cref="ExtServiceLatency"/>
    /// for the <c>/accountz</c> response. Returns <c>null</c> if input is <c>null</c>.
    /// Mirrors Go <c>newExtServiceLatency</c>.
    /// </summary>
    internal static ExtServiceLatency? NewExtServiceLatency(InternalServiceLatency? l)
    {
        if (l == null)
            return null;
        return new ExtServiceLatency
        {
            Sampling = l.Sampling,
            Results  = l.Subject,
        };
    }

    // -------------------------------------------------------------------------
    // newExtImport
    // -------------------------------------------------------------------------

    /// <summary>
    /// Converts a <see cref="ServiceImportEntry"/> to an <see cref="ExtImport"/>
    /// for the <c>/accountz</c> response.
    /// Mirrors Go <c>newExtImport</c>.
    /// </summary>
    internal static ExtImport NewExtImport(ServiceImportEntry? v)
    {
        if (v == null)
            return new ExtImport { Invalid = true };

        return new ExtImport
        {
            Invalid        = v.Invalid,
            Share          = v.Share,
            Tracking       = v.Tracking,
            TrackingHeader = v.TrackingHeader,
            Latency        = NewExtServiceLatency(v.Latency),
            M1             = v.M1,
            Subject        = v.To,
            Account        = v.Account?.Name,
            LocalSubject   = v.From,
        };
    }
}
