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

using System.Reflection;
using System.Net.Security;
using ZB.MOM.NatsNet.Server.Auth;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server;

// =============================================================================
// IReloadOption — mirrors Go `option` interface in reload.go
// =============================================================================

/// <summary>
/// Represents a hot-swappable configuration setting that can be applied to a
/// running server. Mirrors Go <c>option</c> interface in server/reload.go.
/// </summary>
public interface IReloadOption
{
    /// <summary>Apply this option to the running server.</summary>
    void Apply(NatsServer server);

    /// <summary>Returns true if this option requires reloading the logger.</summary>
    bool IsLoggingChange();

    /// <summary>
    /// Returns true if this option requires reloading the cached trace level.
    /// Clients store trace level separately.
    /// </summary>
    bool IsTraceLevelChange();

    /// <summary>Returns true if this option requires reloading authorization.</summary>
    bool IsAuthChange();

    /// <summary>Returns true if this option requires reloading TLS.</summary>
    bool IsTlsChange();

    /// <summary>Returns true if this option requires reloading cluster permissions.</summary>
    bool IsClusterPermsChange();

    /// <summary>
    /// Returns true if this option requires special handling for changes in
    /// cluster pool size or accounts list.
    /// </summary>
    bool IsClusterPoolSizeOrAccountsChange();

    /// <summary>
    /// Returns true if this option indicates a change in the server's JetStream config.
    /// Account changes are handled separately in reloadAuthorization.
    /// </summary>
    bool IsJetStreamChange();

    /// <summary>Returns true if this change requires publishing the server's statz.</summary>
    bool IsStatszChange();
}

// =============================================================================
// NoopReloadOption — mirrors Go `noopOption` struct in reload.go
// =============================================================================

/// <summary>
/// Base class providing no-op implementations for all <see cref="IReloadOption"/>
/// methods. Concrete option types override only the methods relevant to them.
/// Mirrors Go <c>noopOption</c> struct in server/reload.go.
/// </summary>
public abstract class NoopReloadOption : IReloadOption
{
    /// <inheritdoc/>
    public virtual void Apply(NatsServer server) => _ = server;

    /// <inheritdoc/>
    public virtual bool IsLoggingChange() => false;

    /// <inheritdoc/>
    public virtual bool IsTraceLevelChange() => false;

    /// <inheritdoc/>
    public virtual bool IsAuthChange() => false;

    /// <inheritdoc/>
    public virtual bool IsTlsChange() => false;

    /// <inheritdoc/>
    public virtual bool IsClusterPermsChange() => false;

    /// <inheritdoc/>
    public virtual bool IsClusterPoolSizeOrAccountsChange() => false;

    /// <inheritdoc/>
    public virtual bool IsJetStreamChange() => false;

    /// <inheritdoc/>
    public virtual bool IsStatszChange() => false;
}

// =============================================================================
// Intermediate base classes (mirrors Go loggingOption / traceLevelOption)
// =============================================================================

/// <summary>
/// Base for all logging-related reload options.
/// Mirrors Go <c>loggingOption</c> struct.
/// </summary>
internal abstract class LoggingReloadOption : NoopReloadOption
{
    public override bool IsLoggingChange() => true;
}

/// <summary>
/// Base for all trace-level reload options.
/// Mirrors Go <c>traceLevelOption</c> struct.
/// </summary>
internal abstract class TraceLevelReloadOption : LoggingReloadOption
{
    public override bool IsTraceLevelChange() => true;
}

/// <summary>
/// Base for all authorization-related reload options.
/// Mirrors Go <c>authOption</c> struct.
/// </summary>
internal abstract class AuthReloadOption : NoopReloadOption
{
    public override bool IsAuthChange() => true;
}

/// <summary>
/// Base for TLS reload options.
/// Mirrors Go <c>tlsOption</c> (as a base, not the concrete type).
/// </summary>
internal abstract class TlsBaseReloadOption : NoopReloadOption
{
    public override bool IsTlsChange() => true;
}

// =============================================================================
// Logging & Trace option types
// =============================================================================

/// <summary>
/// Reload option for the <c>trace</c> setting.
/// Mirrors Go <c>traceOption</c> struct in reload.go.
/// </summary>
internal sealed class TraceReloadOption : TraceLevelReloadOption
{
    private readonly bool _newValue;
    public TraceReloadOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: trace = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>trace_verbose</c> setting.
/// Mirrors Go <c>traceVerboseOption</c> struct in reload.go.
/// </summary>
internal sealed class TraceVerboseReloadOption : TraceLevelReloadOption
{
    private readonly bool _newValue;
    public TraceVerboseReloadOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: trace_verbose = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>trace_headers</c> setting.
/// Mirrors Go <c>traceHeadersOption</c> struct in reload.go.
/// </summary>
internal sealed class TraceHeadersReloadOption : TraceLevelReloadOption
{
    private readonly bool _newValue;
    public TraceHeadersReloadOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: trace_headers = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>debug</c> setting.
/// Mirrors Go <c>debugOption</c> struct in reload.go.
/// </summary>
internal sealed class DebugReloadOption : LoggingReloadOption
{
    private readonly bool _newValue;
    public DebugReloadOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        server.Noticef("Reloaded: debug = {0}", _newValue);
        // DEFERRED: session 13 — call server.ReloadDebugRaftNodes(_newValue)
    }
}

/// <summary>
/// Reload option for the <c>logtime</c> setting.
/// Mirrors Go <c>logtimeOption</c> struct in reload.go.
/// </summary>
internal sealed class LogtimeReloadOption : LoggingReloadOption
{
    private readonly bool _newValue;
    public LogtimeReloadOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: logtime = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>logtime_utc</c> setting.
/// Mirrors Go <c>logtimeUTCOption</c> struct in reload.go.
/// </summary>
internal sealed class LogtimeUTCOption : LoggingReloadOption
{
    private readonly bool _newValue;
    public LogtimeUTCOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: logtime_utc = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>log_file</c> setting.
/// Mirrors Go <c>logfileOption</c> struct in reload.go.
/// </summary>
internal sealed class LogfileOption : LoggingReloadOption
{
    private readonly string _newValue;
    public LogfileOption(string newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: log_file = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>syslog</c> setting.
/// Mirrors Go <c>syslogOption</c> struct in reload.go.
/// </summary>
internal sealed class SyslogReloadOption : LoggingReloadOption
{
    private readonly bool _newValue;
    public SyslogReloadOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: syslog = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>remote_syslog</c> setting.
/// Mirrors Go <c>remoteSyslogOption</c> struct in reload.go.
/// </summary>
internal sealed class RemoteSyslogReloadOption : LoggingReloadOption
{
    private readonly string _newValue;
    public RemoteSyslogReloadOption(string newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: remote_syslog = {0}", _newValue);
}

// =============================================================================
// TLS option types
// =============================================================================

/// <summary>
/// Reload option for the <c>tls</c> setting.
/// Mirrors Go <c>tlsOption</c> struct in reload.go.
/// The TLS config is stored as <c>object?</c> because the full
/// <c>TlsConfig</c> type is not yet ported.
/// DEFERRED: session 13 — replace object? with the ported TlsConfig type.
/// </summary>
internal sealed class TlsReloadOption : NoopReloadOption
{
    // DEFERRED: session 13 — replace object? with ported TlsConfig type
    private readonly object? _newValue;
    public TlsReloadOption(object? newValue) => _newValue = newValue;

    public override bool IsTlsChange() => true;

    public override void Apply(NatsServer server)
    {
        var message = _newValue is null ? "disabled" : "enabled";
        server.Noticef("Reloaded: tls = {0}", message);
        // DEFERRED: session 13 — update server.Info.TLSRequired / TLSVerify
    }
}

/// <summary>
/// Reload option for the TLS <c>timeout</c> setting.
/// Mirrors Go <c>tlsTimeoutOption</c> struct in reload.go.
/// </summary>
internal sealed class TlsTimeoutReloadOption : NoopReloadOption
{
    private readonly double _newValue;
    public TlsTimeoutReloadOption(double newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: tls timeout = {0}", _newValue);
}

/// <summary>
/// Reload option for the TLS <c>pinned_certs</c> setting.
/// Mirrors Go <c>tlsPinnedCertOption</c> struct in reload.go.
/// The pinned cert set is stored as <c>object?</c> pending the port
/// of the PinnedCertSet type.
/// DEFERRED: session 13 — replace object? with ported PinnedCertSet type.
/// </summary>
internal sealed class TlsPinnedCertReloadOption : NoopReloadOption
{
    // DEFERRED: session 13 — replace object? with ported PinnedCertSet type
    private readonly object? _newValue;
    public TlsPinnedCertReloadOption(object? newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: pinned_certs");
}

/// <summary>
/// Reload option for the TLS <c>handshake_first</c> setting.
/// Mirrors Go <c>tlsHandshakeFirst</c> struct in reload.go.
/// </summary>
internal sealed class TlsHandshakeFirstReloadOption : NoopReloadOption
{
    private readonly bool _newValue;
    public TlsHandshakeFirstReloadOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: Client TLS handshake first: {0}", _newValue);
}

/// <summary>
/// Reload option for the TLS <c>handshake_first_fallback</c> delay setting.
/// Mirrors Go <c>tlsHandshakeFirstFallback</c> struct in reload.go.
/// </summary>
internal sealed class TlsHandshakeFirstFallbackReloadOption : NoopReloadOption
{
    private readonly TimeSpan _newValue;
    public TlsHandshakeFirstFallbackReloadOption(TimeSpan newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: Client TLS handshake first fallback delay: {0}", _newValue);
}

// =============================================================================
// Authorization option types
// =============================================================================

/// <summary>
/// Reload option for the <c>username</c> authorization setting.
/// Mirrors Go <c>usernameOption</c> struct in reload.go.
/// </summary>
internal sealed class UsernameReloadOption : AuthReloadOption
{
    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: authorization username");
}

/// <summary>
/// Reload option for the <c>password</c> authorization setting.
/// Mirrors Go <c>passwordOption</c> struct in reload.go.
/// </summary>
internal sealed class PasswordReloadOption : AuthReloadOption
{
    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: authorization password");
}

/// <summary>
/// Reload option for the <c>token</c> authorization setting.
/// Mirrors Go <c>authorizationOption</c> struct in reload.go.
/// </summary>
internal sealed class AuthorizationReloadOption : AuthReloadOption
{
    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: authorization token");
}

/// <summary>
/// Reload option for the authorization <c>timeout</c> setting.
/// Note: this is a NoopReloadOption (not auth) because authorization
/// will be reloaded with options separately.
/// Mirrors Go <c>authTimeoutOption</c> struct in reload.go.
/// </summary>
internal sealed class AuthTimeoutReloadOption : NoopReloadOption
{
    private readonly double _newValue;
    public AuthTimeoutReloadOption(double newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: authorization timeout = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>tags</c> setting.
/// Mirrors Go <c>tagsOption</c> struct in reload.go.
/// </summary>
internal sealed class TagsReloadOption : NoopReloadOption
{
    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: tags");

    public override bool IsStatszChange() => true;
}

/// <summary>
/// Reload option for the <c>metadata</c> setting.
/// Mirrors Go <c>metadataOption</c> struct in reload.go.
/// </summary>
internal sealed class MetadataReloadOption : NoopReloadOption
{
    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: metadata");

    public override bool IsStatszChange() => true;
}

/// <summary>
/// Reload option for the authorization <c>users</c> setting.
/// Mirrors Go <c>usersOption</c> struct in reload.go.
/// </summary>
internal sealed class UsersReloadOption : AuthReloadOption
{
    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: authorization users");
}

/// <summary>
/// Reload option for the authorization <c>nkeys</c> setting.
/// Mirrors Go <c>nkeysOption</c> struct in reload.go.
/// </summary>
internal sealed class NkeysReloadOption : AuthReloadOption
{
    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: authorization nkey users");
}

/// <summary>
/// Reload option for the <c>accounts</c> setting.
/// Mirrors Go <c>accountsOption</c> struct in reload.go.
/// </summary>
internal sealed class AccountsReloadOption : AuthReloadOption
{
    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: accounts");
}

// =============================================================================
// Cluster option types
// =============================================================================

/// <summary>
/// Reload option for the <c>cluster</c> setting.
/// Stores cluster options as <c>object?</c> pending the port of <c>ClusterOpts</c>.
/// Mirrors Go <c>clusterOption</c> struct in reload.go.
/// DEFERRED: session 13 — replace object? with ported ClusterOpts type.
/// </summary>
internal sealed class ClusterReloadOption : AuthReloadOption
{
    // DEFERRED: session 13 — replace object? with ported ClusterOpts type
    private readonly object? _newValue;
    private readonly bool    _permsChanged;
    private bool    _poolSizeChanged;
    private readonly bool    _compressChanged;
    private string[] _accsAdded;
    private string[] _accsRemoved;

    public ClusterReloadOption(
        object? newValue,
        bool permsChanged,
        bool poolSizeChanged,
        bool compressChanged,
        string[] accsAdded,
        string[] accsRemoved)
    {
        _newValue        = newValue;
        _permsChanged    = permsChanged;
        _poolSizeChanged = poolSizeChanged;
        _compressChanged = compressChanged;
        _accsAdded       = accsAdded;
        _accsRemoved     = accsRemoved;
    }

    public override bool IsClusterPermsChange()
        => _permsChanged;

    public override bool IsClusterPoolSizeOrAccountsChange()
        => _poolSizeChanged || _accsAdded.Length > 0 || _accsRemoved.Length > 0;

    internal ClusterOpts? ClusterValue => _newValue as ClusterOpts;
    internal bool PoolSizeChanged => _poolSizeChanged;
    internal bool CompressChanged => _compressChanged;
    internal IReadOnlyList<string> AccountsAdded => _accsAdded;
    internal IReadOnlyList<string> AccountsRemoved => _accsRemoved;

    /// <summary>
    /// Computes pool/account deltas used by reload orchestration.
    /// Mirrors Go <c>clusterOption.diffPoolAndAccounts</c>.
    /// </summary>
    public void DiffPoolAndAccounts(ClusterOpts oldValue)
    {
        ArgumentNullException.ThrowIfNull(oldValue);

        if (_newValue is not ClusterOpts newValue)
        {
            _poolSizeChanged = false;
            _accsAdded = [];
            _accsRemoved = [];
            return;
        }

        _poolSizeChanged = newValue.PoolSize != oldValue.PoolSize;

        var oldAccounts = new HashSet<string>(oldValue.PinnedAccounts, StringComparer.Ordinal);
        var newAccounts = new HashSet<string>(newValue.PinnedAccounts, StringComparer.Ordinal);

        _accsAdded = newAccounts.Except(oldAccounts).ToArray();
        _accsRemoved = oldAccounts.Except(newAccounts).ToArray();
    }

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — full cluster apply logic (TLS, route info, compression)
        server.Noticef("Reloaded: cluster");
    }
}

/// <summary>
/// Reload option for the cluster <c>routes</c> setting.
/// Routes to add/remove are stored as <c>object[]</c> pending the port of URL handling.
/// Mirrors Go <c>routesOption</c> struct in reload.go.
/// DEFERRED: session 13 — replace object[] with Uri[] when route types are ported.
/// </summary>
internal sealed class RoutesReloadOption : NoopReloadOption
{
    // DEFERRED: session 13 — replace object[] with Uri[] when route URL types are ported
    private readonly object[] _add;
    private readonly object[] _remove;

    public RoutesReloadOption(object[] add, object[] remove)
    {
        _add    = add;
        _remove = remove;
    }

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — add/remove routes, update varzUpdateRouteURLs
        server.Noticef("Reloaded: cluster routes");
    }
}

// =============================================================================
// Connection limit & network option types
// =============================================================================

/// <summary>
/// Reload option for the <c>max_connections</c> setting.
/// Mirrors Go <c>maxConnOption</c> struct in reload.go.
/// </summary>
internal sealed class MaxConnReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public MaxConnReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — close random connections if over limit
        server.Noticef("Reloaded: max_connections = {0}", _newValue);
    }
}

/// <summary>
/// Reload option for the <c>pid_file</c> setting.
/// Mirrors Go <c>pidFileOption</c> struct in reload.go.
/// </summary>
internal sealed class PidFileReloadOption : NoopReloadOption
{
    private readonly string _newValue;
    public PidFileReloadOption(string newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        if (string.IsNullOrEmpty(_newValue))
            return;
        // DEFERRED: session 13 — call server.LogPid()
        server.Noticef("Reloaded: pid_file = {0}", _newValue);
    }
}

/// <summary>
/// Reload option for the <c>ports_file_dir</c> setting.
/// Mirrors Go <c>portsFileDirOption</c> struct in reload.go.
/// </summary>
internal sealed class PortsFileDirReloadOption : NoopReloadOption
{
    private readonly string _oldValue;
    private readonly string _newValue;

    public PortsFileDirReloadOption(string oldValue, string newValue)
    {
        _oldValue = oldValue;
        _newValue = newValue;
    }

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — call server.DeletePortsFile(_oldValue) and server.LogPorts()
        server.Noticef("Reloaded: ports_file_dir = {0}", _newValue);
    }
}

/// <summary>
/// Reload option for the <c>max_control_line</c> setting.
/// Mirrors Go <c>maxControlLineOption</c> struct in reload.go.
/// </summary>
internal sealed class MaxControlLineReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public MaxControlLineReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — update mcl on each connected client
        server.Noticef("Reloaded: max_control_line = {0}", _newValue);
    }
}

/// <summary>
/// Reload option for the <c>max_payload</c> setting.
/// Mirrors Go <c>maxPayloadOption</c> struct in reload.go.
/// </summary>
internal sealed class MaxPayloadReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public MaxPayloadReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — update server info and mpay on each client
        server.Noticef("Reloaded: max_payload = {0}", _newValue);
    }
}

/// <summary>
/// Reload option for the <c>ping_interval</c> setting.
/// Mirrors Go <c>pingIntervalOption</c> struct in reload.go.
/// </summary>
internal sealed class PingIntervalReloadOption : NoopReloadOption
{
    private readonly TimeSpan _newValue;
    public PingIntervalReloadOption(TimeSpan newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: ping_interval = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>ping_max</c> setting.
/// Mirrors Go <c>maxPingsOutOption</c> struct in reload.go.
/// </summary>
internal sealed class MaxPingsOutReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public MaxPingsOutReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: ping_max = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>write_deadline</c> setting.
/// Mirrors Go <c>writeDeadlineOption</c> struct in reload.go.
/// </summary>
internal sealed class WriteDeadlineReloadOption : NoopReloadOption
{
    private readonly TimeSpan _newValue;
    public WriteDeadlineReloadOption(TimeSpan newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: write_deadline = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>client_advertise</c> setting.
/// Mirrors Go <c>clientAdvertiseOption</c> struct in reload.go.
/// </summary>
internal sealed class ClientAdvertiseReloadOption : NoopReloadOption
{
    private readonly string _newValue;
    public ClientAdvertiseReloadOption(string newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — call server.SetInfoHostPort()
        server.Noticef("Reload: client_advertise = {0}", _newValue);
    }
}

// =============================================================================
// JetStream option type
// =============================================================================

/// <summary>
/// Reload option for the <c>jetstream</c> setting.
/// Mirrors Go <c>jetStreamOption</c> struct in reload.go.
/// </summary>
internal sealed class JetStreamReloadOption : NoopReloadOption
{
    private readonly bool _newValue;
    public JetStreamReloadOption(bool newValue) => _newValue = newValue;

    internal bool Value => _newValue;

    public override bool IsJetStreamChange() => true;
    public override bool IsStatszChange()    => true;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: JetStream");
}

// =============================================================================
// Miscellaneous option types
// =============================================================================

/// <summary>
/// Reload option for the <c>default_sentinel</c> setting.
/// Mirrors Go <c>defaultSentinelOption</c> struct in reload.go.
/// </summary>
internal sealed class DefaultSentinelReloadOption : NoopReloadOption
{
    private readonly string _newValue;
    public DefaultSentinelReloadOption(string newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: default_sentinel = {0}", _newValue);
}

/// <summary>
/// Reload option for the OCSP setting.
/// The new value is stored as <c>object?</c> pending the port of <c>OCSPConfig</c>.
/// Mirrors Go <c>ocspOption</c> struct in reload.go.
/// DEFERRED: session 13 — replace object? with ported OcspConfig type.
/// </summary>
internal sealed class OcspReloadOption : TlsBaseReloadOption
{
    // DEFERRED: session 13 — replace object? with ported OcspConfig type
    private readonly object? _newValue;
    public OcspReloadOption(object? newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: OCSP");
}

/// <summary>
/// Reload option for the OCSP response cache setting.
/// The new value is stored as <c>object?</c> pending the port of
/// <c>OCSPResponseCacheConfig</c>.
/// Mirrors Go <c>ocspResponseCacheOption</c> struct in reload.go.
/// DEFERRED: session 13 — replace object? with ported OcspResponseCacheConfig type.
/// </summary>
internal sealed class OcspResponseCacheReloadOption : TlsBaseReloadOption
{
    // DEFERRED: session 13 — replace object? with ported OcspResponseCacheConfig type
    private readonly object? _newValue;
    public OcspResponseCacheReloadOption(object? newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded OCSP peer cache");
}

/// <summary>
/// Reload option for the <c>connect_error_reports</c> setting.
/// Mirrors Go <c>connectErrorReports</c> struct in reload.go.
/// </summary>
internal sealed class ConnectErrorReportsReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public ConnectErrorReportsReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: connect_error_reports = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>reconnect_error_reports</c> setting.
/// Mirrors Go <c>reconnectErrorReports</c> struct in reload.go.
/// </summary>
internal sealed class ReconnectErrorReportsReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public ReconnectErrorReportsReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: reconnect_error_reports = {0}", _newValue);
}

/// <summary>
/// Reload option for the <c>max_traced_msg_len</c> setting.
/// Mirrors Go <c>maxTracedMsgLenOption</c> struct in reload.go.
/// </summary>
internal sealed class MaxTracedMsgLenReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public MaxTracedMsgLenReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — update server.Opts.MaxTracedMsgLen under lock
        server.Noticef("Reloaded: max_traced_msg_len = {0}", _newValue);
    }
}

// =============================================================================
// MQTT option types
// =============================================================================

/// <summary>
/// Reload option for the MQTT <c>ack_wait</c> setting.
/// Mirrors Go <c>mqttAckWaitReload</c> struct in reload.go.
/// </summary>
internal sealed class MqttAckWaitReloadOption : NoopReloadOption
{
    private readonly TimeSpan _newValue;
    public MqttAckWaitReloadOption(TimeSpan newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: MQTT ack_wait = {0}", _newValue);
}

/// <summary>
/// Reload option for the MQTT <c>max_ack_pending</c> setting.
/// Mirrors Go <c>mqttMaxAckPendingReload</c> struct in reload.go.
/// </summary>
internal sealed class MqttMaxAckPendingReloadOption : NoopReloadOption
{
    private readonly ushort _newValue;
    public MqttMaxAckPendingReloadOption(ushort newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — call server.MqttUpdateMaxAckPending(_newValue)
        server.Noticef("Reloaded: MQTT max_ack_pending = {0}", _newValue);
    }
}

/// <summary>
/// Reload option for the MQTT <c>stream_replicas</c> setting.
/// Mirrors Go <c>mqttStreamReplicasReload</c> struct in reload.go.
/// </summary>
internal sealed class MqttStreamReplicasReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public MqttStreamReplicasReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: MQTT stream_replicas = {0}", _newValue);
}

/// <summary>
/// Reload option for the MQTT <c>consumer_replicas</c> setting.
/// Mirrors Go <c>mqttConsumerReplicasReload</c> struct in reload.go.
/// </summary>
internal sealed class MqttConsumerReplicasReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public MqttConsumerReplicasReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: MQTT consumer_replicas = {0}", _newValue);
}

/// <summary>
/// Reload option for the MQTT <c>consumer_memory_storage</c> setting.
/// Mirrors Go <c>mqttConsumerMemoryStorageReload</c> struct in reload.go.
/// </summary>
internal sealed class MqttConsumerMemoryStorageReloadOption : NoopReloadOption
{
    private readonly bool _newValue;
    public MqttConsumerMemoryStorageReloadOption(bool newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: MQTT consumer_memory_storage = {0}", _newValue);
}

/// <summary>
/// Reload option for the MQTT <c>consumer_inactive_threshold</c> setting.
/// Mirrors Go <c>mqttInactiveThresholdReload</c> struct in reload.go.
/// </summary>
internal sealed class MqttInactiveThresholdReloadOption : NoopReloadOption
{
    private readonly TimeSpan _newValue;
    public MqttInactiveThresholdReloadOption(TimeSpan newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
        => server.Noticef("Reloaded: MQTT consumer_inactive_threshold = {0}", _newValue);
}

// =============================================================================
// Profiling option type
// =============================================================================

/// <summary>
/// Reload option for the <c>prof_block_rate</c> setting.
/// Mirrors Go <c>profBlockRateReload</c> struct in reload.go.
/// </summary>
internal sealed class ProfBlockRateReloadOption : NoopReloadOption
{
    private readonly int _newValue;
    public ProfBlockRateReloadOption(int newValue) => _newValue = newValue;

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — call server.SetBlockProfileRate(_newValue)
        server.Noticef("Reloaded: prof_block_rate = {0}", _newValue);
    }
}

// =============================================================================
// LeafNode option type
// =============================================================================

/// <summary>
/// Reload option for leaf-node settings (TLS handshake-first, compression, disabled).
/// Mirrors Go <c>leafNodeOption</c> struct in reload.go.
/// </summary>
internal sealed class LeafNodeReloadOption : NoopReloadOption
{
    private readonly bool _tlsFirstChanged;
    private readonly bool _compressionChanged;
    private readonly bool _disabledChanged;

    public LeafNodeReloadOption(bool tlsFirstChanged, bool compressionChanged, bool disabledChanged)
    {
        _tlsFirstChanged    = tlsFirstChanged;
        _compressionChanged = compressionChanged;
        _disabledChanged    = disabledChanged;
    }

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — full leaf-node apply logic from Go leafNodeOption.Apply()
        if (_tlsFirstChanged)
            server.Noticef("Reloaded: LeafNode TLS HandshakeFirst settings");
        if (_compressionChanged)
            server.Noticef("Reloaded: LeafNode compression settings");
        if (_disabledChanged)
            server.Noticef("Reloaded: LeafNode disabled/enabled state");
    }
}

// =============================================================================
// NoFastProducerStall option type
// =============================================================================

/// <summary>
/// Reload option for the <c>no_fast_producer_stall</c> setting.
/// Mirrors Go <c>noFastProdStallReload</c> struct in reload.go.
/// </summary>
internal sealed class NoFastProducerStallReloadOption : NoopReloadOption
{
    private readonly bool _noStall;
    public NoFastProducerStallReloadOption(bool noStall) => _noStall = noStall;

    public override void Apply(NatsServer server)
    {
        var not = _noStall ? "not " : string.Empty;
        server.Noticef("Reloaded: fast producers will {0}be stalled", not);
    }
}

// =============================================================================
// Proxies option type
// =============================================================================

/// <summary>
/// Reload option for the <c>proxies</c> trusted keys setting.
/// Mirrors Go <c>proxiesReload</c> struct in reload.go.
/// </summary>
internal sealed class ProxiesReloadOption : NoopReloadOption
{
    private readonly string[] _add;
    private readonly string[] _del;

    public ProxiesReloadOption(string[] add, string[] del)
    {
        _add = add;
        _del = del;
    }

    public override void Apply(NatsServer server)
    {
        // DEFERRED: session 13 — disconnect proxied clients for removed keys,
        // call server.ProcessProxiesTrustedKeys()
        if (_del.Length > 0)
            server.Noticef("Reloaded: proxies trusted keys {0} were removed", string.Join(", ", _del));
        if (_add.Length > 0)
            server.Noticef("Reloaded: proxies trusted keys {0} were added", string.Join(", ", _add));
    }
}

// =============================================================================
// ConfigReloader — stub for server/reload.go Reload() / ReloadOptions()
// =============================================================================

/// <summary>
/// Stub for the configuration reloader.
/// Full reload logic (diffOptions, applyOptions, recheckPinnedCerts) will be
/// implemented in a future session.
/// Mirrors Go <c>Server.Reload()</c> and <c>Server.ReloadOptions()</c> in
/// server/reload.go.
/// </summary>
internal sealed class ConfigReloader
{
    // DEFERRED: session 13 — full reload logic
    // Mirrors Go server.Reload() / server.ReloadOptions() in server/reload.go

    /// <summary>
    /// Stub: read and apply the server config file.
    /// Returns null on success; a non-null Exception describes the failure.
    /// </summary>
    public Exception? Reload(NatsServer server) => null;

    /// <summary>
    /// Applies bool-valued config precedence for reload:
    /// config-file explicit values first, then explicit command-line flags.
    /// Mirrors Go <c>applyBoolFlags</c>.
    /// </summary>
    public static void ApplyBoolFlags(ServerOptions newOptions, ServerOptions flagOptions)
    {
        ArgumentNullException.ThrowIfNull(newOptions);
        ArgumentNullException.ThrowIfNull(flagOptions);

        foreach (var (name, value) in newOptions.InConfig)
            SetBooleanMember(newOptions, name, value);

        foreach (var (name, value) in flagOptions.InCmdLine)
            SetBooleanMember(newOptions, name, value);
    }

    /// <summary>
    /// Sorts order-insensitive values in place prior to deep comparisons.
    /// Mirrors Go <c>imposeOrder</c>.
    /// </summary>
    public static Exception? ImposeOrder(object? value)
    {
        switch (value)
        {
            case List<Account> accounts:
                accounts.Sort((a, b) => string.CompareOrdinal(a.Name, b.Name));
                break;
            case List<User> users:
                users.Sort((a, b) => string.CompareOrdinal(a.Username, b.Username));
                break;
            case List<NkeyUser> nkeys:
                nkeys.Sort((a, b) => string.CompareOrdinal(a.Nkey, b.Nkey));
                break;
            case List<Uri> urls:
                urls.Sort((a, b) => string.CompareOrdinal(a.ToString(), b.ToString()));
                break;
            case List<string> strings:
                strings.Sort(StringComparer.Ordinal);
                break;
            case GatewayOpts gateway:
                gateway.Gateways.Sort((a, b) => string.CompareOrdinal(a.Name, b.Name));
                break;
            case WebsocketOpts websocket:
                websocket.AllowedOrigins.Sort(StringComparer.Ordinal);
                break;
            case null:
            case string:
            case bool:
            case byte:
            case ushort:
            case uint:
            case ulong:
            case int:
            case long:
            case TimeSpan:
            case float:
            case double:
            case LeafNodeOpts:
            case ClusterOpts:
            case SslServerAuthenticationOptions:
            case PinnedCertSet:
            case IAccountResolver:
            case MqttOpts:
            case Dictionary<string, string>:
            case JsLimitOpts:
            case StoreCipher:
            case OcspResponseCacheConfig:
            case ProxiesConfig:
            case WriteTimeoutPolicy:
            case AuthCalloutOpts:
            case JsTpmOpts:
                break;
            default:
                return new InvalidOperationException(
                    $"OnReload, sort or explicitly skip type: {value.GetType().FullName}");
        }

        return null;
    }

    /// <summary>
    /// Returns remote gateway configs copied for diffing, with TLS runtime fields stripped.
    /// Mirrors Go <c>copyRemoteGWConfigsWithoutTLSConfig</c>.
    /// </summary>
    public static List<RemoteGatewayOpts>? CopyRemoteGWConfigsWithoutTLSConfig(List<RemoteGatewayOpts>? current)
    {
        if (current is not { Count: > 0 })
            return null;

        var copied = new List<RemoteGatewayOpts>(current.Count);
        foreach (var config in current)
        {
            copied.Add(new RemoteGatewayOpts
            {
                Name = config.Name,
                TlsConfig = null,
                TlsConfigOpts = null,
                TlsTimeout = config.TlsTimeout,
                Urls = [.. config.Urls.Select(static u => new Uri(u.ToString(), UriKind.Absolute))],
            });
        }

        return copied;
    }

    /// <summary>
    /// Returns remote leaf-node configs copied for diffing, with runtime-mutated
    /// fields stripped or normalized.
    /// Mirrors Go <c>copyRemoteLNConfigForReloadCompare</c>.
    /// </summary>
    public static List<RemoteLeafOpts>? CopyRemoteLNConfigForReloadCompare(List<RemoteLeafOpts>? current)
    {
        if (current is not { Count: > 0 })
            return null;

        var copied = new List<RemoteLeafOpts>(current.Count);
        foreach (var config in current)
        {
            copied.Add(new RemoteLeafOpts
            {
                LocalAccount = config.LocalAccount,
                NoRandomize = config.NoRandomize,
                Urls = [.. config.Urls.Select(static u => new Uri(u.ToString(), UriKind.Absolute))],
                Credentials = config.Credentials,
                Nkey = config.Nkey,
                SignatureCb = config.SignatureCb,
                Tls = false,
                TlsConfig = null,
                TlsConfigOpts = null,
                TlsTimeout = config.TlsTimeout,
                TlsHandshakeFirst = false,
                Hub = config.Hub,
                DenyImports = [],
                DenyExports = [],
                FirstInfoTimeout = config.FirstInfoTimeout,
                Compression = new CompressionOpts(),
                Websocket = new RemoteLeafWebsocketOpts
                {
                    Compression = config.Websocket.Compression,
                    NoMasking = config.Websocket.NoMasking,
                },
                Proxy = new RemoteLeafProxyOpts
                {
                    Url = config.Proxy.Url,
                    Username = config.Proxy.Username,
                    Password = config.Proxy.Password,
                    Timeout = config.Proxy.Timeout,
                },
                JetStreamClusterMigrate = config.JetStreamClusterMigrate,
                JetStreamClusterMigrateDelay = config.JetStreamClusterMigrateDelay,
                LocalIsolation = config.LocalIsolation,
                RequestIsolation = config.RequestIsolation,
                Disabled = false,
            });
        }

        return copied;
    }

    /// <summary>
    /// Validates non-reloadable cluster settings and advertise syntax.
    /// Mirrors Go <c>validateClusterOpts</c>.
    /// </summary>
    public static Exception? ValidateClusterOpts(ClusterOpts oldValue, ClusterOpts newValue)
    {
        ArgumentNullException.ThrowIfNull(oldValue);
        ArgumentNullException.ThrowIfNull(newValue);

        if (!string.Equals(oldValue.Host, newValue.Host, StringComparison.Ordinal))
        {
            return new InvalidOperationException(
                $"config reload not supported for cluster host: old={oldValue.Host}, new={newValue.Host}");
        }

        if (oldValue.Port != newValue.Port)
        {
            return new InvalidOperationException(
                $"config reload not supported for cluster port: old={oldValue.Port}, new={newValue.Port}");
        }

        if (!string.IsNullOrEmpty(newValue.Advertise))
        {
            var (_, _, err) = ServerUtilities.ParseHostPort(newValue.Advertise, 0);
            if (err != null)
                return new InvalidOperationException(
                    $"invalid Cluster.Advertise value of {newValue.Advertise}, err={err.Message}", err);
        }

        return null;
    }

    /// <summary>
    /// Diffs old/new route lists and returns routes to add/remove.
    /// Mirrors Go <c>diffRoutes</c>.
    /// </summary>
    public static (List<Uri> Add, List<Uri> Remove) DiffRoutes(IReadOnlyList<Uri> oldRoutes, IReadOnlyList<Uri> newRoutes)
    {
        ArgumentNullException.ThrowIfNull(oldRoutes);
        ArgumentNullException.ThrowIfNull(newRoutes);

        var add = new List<Uri>();
        var remove = new List<Uri>();

        foreach (var oldRoute in oldRoutes)
        {
            if (!newRoutes.Any(newRoute => ServerUtilities.UrlsAreEqual(oldRoute, newRoute)))
                remove.Add(oldRoute);
        }

        foreach (var newRoute in newRoutes)
        {
            if (!oldRoutes.Any(oldRoute => ServerUtilities.UrlsAreEqual(oldRoute, newRoute)))
                add.Add(newRoute);
        }

        return (add, remove);
    }

    /// <summary>
    /// Diffs proxy trusted keys and returns added/removed key sets.
    /// Mirrors Go <c>diffProxiesTrustedKeys</c>.
    /// </summary>
    public static (List<string> Add, List<string> Del) DiffProxiesTrustedKeys(
        IReadOnlyList<ProxyConfig>? oldTrusted,
        IReadOnlyList<ProxyConfig>? newTrusted)
    {
        var oldList = oldTrusted ?? [];
        var newList = newTrusted ?? [];

        var add = new List<string>();
        var del = new List<string>();

        foreach (var oldProxy in oldList)
        {
            if (!newList.Any(np => string.Equals(np.Key, oldProxy.Key, StringComparison.Ordinal)))
                del.Add(oldProxy.Key);
        }

        foreach (var newProxy in newList)
        {
            if (!oldList.Any(op => string.Equals(op.Key, newProxy.Key, StringComparison.Ordinal)))
                add.Add(newProxy.Key);
        }

        return (add, del);
    }

    private static void SetBooleanMember(ServerOptions options, string path, bool value)
    {
        var segments = path.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (segments.Length == 0)
            return;

        object? target = options;
        for (int i = 0; i < segments.Length - 1; i++)
        {
            if (target == null)
                return;

            var segment = segments[i];
            var targetType = target.GetType();
            var prop = targetType.GetProperty(segment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            if (prop != null)
            {
                target = prop.GetValue(target);
                continue;
            }

            var field = targetType.GetField(segment, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            if (field != null)
            {
                target = field.GetValue(target);
                continue;
            }

            return;
        }

        if (target == null)
            return;

        var leaf = segments[^1];
        var leafType = target.GetType();
        var leafProperty = leafType.GetProperty(leaf, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        if (leafProperty?.PropertyType == typeof(bool) && leafProperty.CanWrite)
        {
            leafProperty.SetValue(target, value);
            return;
        }

        var leafField = leafType.GetField(leaf, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        if (leafField?.FieldType == typeof(bool))
            leafField.SetValue(target, value);
    }
}
