using System.Security.Cryptography;
using System.Security.Authentication;
using System.Text;
using System.Text.Json;
using NATS.Server.Auth;
using NATS.Server.Configuration;
using NATS.Server.Protocol;
using NATS.Server.Tls;

namespace NATS.Server;

public sealed class NatsOptions
{
    private static bool _allowUnknownTopLevelFields;
    private string _configDigest = string.Empty;

    public string Host { get; set; } = NatsProtocol.DefaultHost;
    public int Port { get; set; } = NatsProtocol.DefaultPort;
    public string? ServerName { get; set; }
    public int MaxPayload { get; set; } = 1024 * 1024;
    public int MaxControlLine { get; set; } = 4096;
    public int MaxConnections { get; set; } = NatsProtocol.DefaultMaxConnections;
    public long MaxPending { get; set; } = 64 * 1024 * 1024; // 64MB, matching Go MAX_PENDING_SIZE
    public TimeSpan WriteDeadline { get; set; } = NatsProtocol.DefaultFlushDeadline;
    public TimeSpan PingInterval { get; set; } = NatsProtocol.DefaultPingInterval;
    public int MaxPingsOut { get; set; } = NatsProtocol.DefaultPingMaxOut;

    // Go: opts.go — DisableShortFirstPing. When true, the first PING timer tick
    // is not suppressed by the FirstPongSent / 2-second grace period.
    // Useful in tests to ensure deterministic ping behavior.
    public bool DisableShortFirstPing { get; set; }

    // Subscription limits
    public int MaxSubs { get; set; }             // 0 = unlimited (per-connection)
    public int MaxSubTokens { get; set; }        // 0 = unlimited

    // Server tags (exposed via /varz)
    public Dictionary<string, string>? Tags { get; set; }

    // Account configuration
    public Dictionary<string, AccountConfig>? Accounts { get; set; }

    // Simple auth (single user)
    public string? Username { get; set; }
    public string? Password { get; set; }
    public string? Authorization { get; set; }

    // Multiple users/nkeys
    public IReadOnlyList<User>? Users { get; set; }
    public IReadOnlyList<NKeyUser>? NKeys { get; set; }

    // Default/fallback
    public string? NoAuthUser { get; set; }

    // Auth extensions
    public Auth.ExternalAuthOptions? ExternalAuth { get; set; }
    public Auth.ProxyAuthOptions? ProxyAuth { get; set; }

    // Auth timing
    public TimeSpan AuthTimeout { get; set; } = NatsProtocol.AuthTimeout;

    // Monitoring (0 = disabled; standard port is 8222)
    public int MonitorPort { get; set; }
    public string MonitorHost { get; set; } = "0.0.0.0";
    public string? MonitorBasePath { get; set; }
    // 0 = disabled
    public int MonitorHttpsPort { get; set; }

    // Lifecycle / lame-duck mode
    public TimeSpan LameDuckDuration { get; set; } = NatsProtocol.DefaultLameDuckDuration;
    public TimeSpan LameDuckGracePeriod { get; set; } = NatsProtocol.DefaultLameDuckGracePeriod;

    // File paths
    public string? PidFile { get; set; }
    public string? PortsFileDir { get; set; }
    public string? ConfigFile { get; set; }

    // Logging
    public string? LogFile { get; set; }
    public long LogSizeLimit { get; set; }
    public int LogMaxFiles { get; set; }
    public bool Debug { get; set; }
    public bool Trace { get; set; }
    public bool Logtime { get; set; } = true;
    public bool LogtimeUTC { get; set; }
    public bool Syslog { get; set; }
    public string? RemoteSyslog { get; set; }

    // Profiling (0 = disabled)
    public int ProfPort { get; set; }

    // Extended options for Go parity
    public string? ClientAdvertise { get; set; }
    public bool TraceVerbose { get; set; }
    public int MaxTracedMsgLen { get; set; }
    public bool DisableSublistCache { get; set; }
    public int ConnectErrorReports { get; set; } = NatsProtocol.DefaultConnectErrorReports;
    public int ReconnectErrorReports { get; set; } = NatsProtocol.DefaultReconnectErrorReports;
    public bool NoHeaderSupport { get; set; }
    public int MaxClosedClients { get; set; } = NatsProtocol.DefaultMaxClosedClients;
    public bool NoSystemAccount { get; set; }
    public string? SystemAccount { get; set; }

    // Tracks which fields were set via CLI flags (for reload precedence)
    public HashSet<string> InCmdLine { get; } = [];

    // TLS
    public string? TlsCert { get; set; }
    public string? TlsKey { get; set; }
    public string? TlsCaCert { get; set; }
    public bool TlsVerify { get; set; }
    public bool TlsMap { get; set; }
    public TimeSpan TlsTimeout { get; set; } = NatsProtocol.TlsTimeout;
    public bool TlsHandshakeFirst { get; set; }
    public TimeSpan TlsHandshakeFirstFallback { get; set; } = NatsProtocol.DefaultTlsHandshakeFirstFallbackDelay;
    public bool AllowNonTls { get; set; }
    public long TlsRateLimit { get; set; }
    public HashSet<string>? TlsPinnedCerts { get; set; }
    public SslProtocols TlsMinVersion { get; set; } = SslProtocols.Tls12;

    // OCSP stapling and peer verification
    public OcspConfig? OcspConfig { get; set; }
    public bool OcspPeerVerify { get; set; }

    // JWT / Operator mode
    public string[]? TrustedKeys { get; set; }
    public Auth.Jwt.IAccountResolver? AccountResolver { get; set; }

    // Per-subsystem log level overrides (namespace -> level)
    public Dictionary<string, string>? LogOverrides { get; set; }

    // Subject mapping / transforms (source pattern -> destination template)
    public Dictionary<string, string>? SubjectMappings { get; set; }

    // MQTT configuration (parsed from config, no listener yet)
    public MqttOptions? Mqtt { get; set; }

    // Cluster and JetStream settings
    public ClusterOptions? Cluster { get; set; }
    public GatewayOptions? Gateway { get; set; }
    public LeafNodeOptions? LeafNode { get; set; }
    public JetStreamOptions? JetStream { get; set; }

    public bool HasTls => TlsCert != null && TlsKey != null;

    // WebSocket
    public WebSocketOptions WebSocket { get; set; } = new();

    public static void NoErrOnUnknownFields(bool noError)
    {
        _allowUnknownTopLevelFields = noError;
    }

    internal static bool AllowUnknownTopLevelFields => _allowUnknownTopLevelFields;

    public static List<Uri> RoutesFromStr(string routesStr)
    {
        if (string.IsNullOrWhiteSpace(routesStr))
            return [];

        var routes = new List<Uri>();
        foreach (var route in routesStr.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
        {
            if (Uri.TryCreate(route, UriKind.Absolute, out var uri))
                routes.Add(uri);
        }

        return routes;
    }

    public NatsOptions Clone()
    {
        try
        {
            var json = JsonSerializer.Serialize(this);
            var clone = JsonSerializer.Deserialize<NatsOptions>(json) ?? new NatsOptions();
            clone.InCmdLine.Clear();
            foreach (var flag in InCmdLine)
                clone.InCmdLine.Add(flag);
            if (TlsPinnedCerts != null)
                clone.TlsPinnedCerts = [.. TlsPinnedCerts];
            return clone;
        }
        catch
        {
            var clone = new NatsOptions();
            CopyFrom(clone, this);
            clone.InCmdLine.Clear();
            foreach (var flag in InCmdLine)
                clone.InCmdLine.Add(flag);
            if (Tags != null)
                clone.Tags = new Dictionary<string, string>(Tags);
            if (SubjectMappings != null)
                clone.SubjectMappings = new Dictionary<string, string>(SubjectMappings);
            if (TlsPinnedCerts != null)
                clone.TlsPinnedCerts = [.. TlsPinnedCerts];
            return clone;
        }
    }

    public void ProcessConfigString(string data)
    {
        var parsed = ConfigProcessor.ProcessConfig(data);
        CopyFrom(this, parsed);
        _configDigest = ComputeDigest(data);
    }

    public string ConfigDigest() => _configDigest;

    private static void CopyFrom(NatsOptions destination, NatsOptions source)
    {
        foreach (var prop in typeof(NatsOptions).GetProperties())
        {
            if (!prop.CanRead || !prop.CanWrite)
                continue;
            prop.SetValue(destination, prop.GetValue(source));
        }
    }

    private static string ComputeDigest(string text)
    {
        var hash = SHA256.HashData(Encoding.UTF8.GetBytes(text));
        return Convert.ToHexString(hash).ToLowerInvariant();
    }
}

public sealed class JSLimitOpts
{
    public int MaxRequestBatch { get; set; }
    public int MaxAckPending { get; set; }
    public int MaxHAAssets { get; set; }
    public TimeSpan Duplicates { get; set; }
    public int MaxBatchInflightPerStream { get; set; }
    public int MaxBatchInflightTotal { get; set; }
    public int MaxBatchSize { get; set; }
    public TimeSpan MaxBatchTimeout { get; set; }
}

public sealed class AuthCallout
{
    public string Issuer { get; set; } = string.Empty;
    public string Account { get; set; } = string.Empty;
    public List<string> AuthUsers { get; set; } = [];
    public string XKey { get; set; } = string.Empty;
    public List<string> AllowedAccounts { get; set; } = [];
}

public sealed class ProxiesConfig
{
    public List<ProxyConfig> Trusted { get; set; } = [];
}

public sealed class ProxyConfig
{
    public string Key { get; set; } = string.Empty;
}

public sealed class Ports
{
    public List<string> Nats { get; set; } = [];
    public List<string> Monitoring { get; set; } = [];
    public List<string> Cluster { get; set; } = [];
    public List<string> Profile { get; set; } = [];
    public List<string> WebSocket { get; set; } = [];
    public List<string> LeafNodes { get; set; } = [];
}

public static class CompressionModes
{
    public const string Off = "off";
    public const string Accept = "accept";
    public const string S2Fast = "s2_fast";
    public const string S2Better = "s2_better";
    public const string S2Best = "s2_best";
    public const string S2Uncompressed = "s2_uncompressed";
    public const string S2Auto = "s2_auto";
}

public sealed class CompressionOpts
{
    public string Mode { get; set; } = CompressionModes.Off;
    public List<int> RTTThresholds { get; set; } = [10, 50, 100, 250];
}

public sealed class WebSocketOptions
{
    public string Host { get; set; } = "0.0.0.0";
    public int Port { get; set; } = -1;
    public string? Advertise { get; set; }
    public string? NoAuthUser { get; set; }
    public string? JwtCookie { get; set; }
    public string? UsernameCookie { get; set; }
    public string? PasswordCookie { get; set; }
    public string? TokenCookie { get; set; }
    public string? Username { get; set; }
    public string? Password { get; set; }
    public string? Token { get; set; }
    public TimeSpan AuthTimeout { get; set; } = TimeSpan.FromSeconds(2);
    public bool NoTls { get; set; }
    public string? TlsCert { get; set; }
    public string? TlsKey { get; set; }
    public bool SameOrigin { get; set; }
    public List<string>? AllowedOrigins { get; set; }
    public bool Compression { get; set; }
    public TimeSpan HandshakeTimeout { get; set; } = TimeSpan.FromSeconds(2);
    public TimeSpan? PingInterval { get; set; }
    public Dictionary<string, string>? Headers { get; set; }

    // Go websocket.go srvWebsocket.authOverride parity bit:
    // true when websocket auth options override top-level auth config.
    public bool AuthOverride { get; internal set; }
}
