using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.Logging;
using NATS.Server.Protocol;

namespace NATS.Server.Monitoring;

/// <summary>
/// Handles building the Varz response from server state and process metrics.
/// Corresponds to Go server/monitor.go handleVarz function.
/// </summary>
public sealed class VarzHandler : IDisposable
{
    private readonly NatsServer _server;
    private readonly NatsOptions _options;
    private readonly ILogger<VarzHandler> _logger;
    private readonly SemaphoreSlim _varzMu = new(1, 1);
    private readonly object _cpuSampleSync = new();
    private readonly Timer _cpuSampleTimer;
    private DateTime _lastCpuSampleTime;
    private TimeSpan _lastCpuUsage;
    private double _cachedCpuPercent;

    public VarzHandler(NatsServer server, NatsOptions options, ILoggerFactory loggerFactory)
    {
        _server = server;
        _options = options;
        _logger = loggerFactory.CreateLogger<VarzHandler>();
        using var proc = Process.GetCurrentProcess();
        _lastCpuSampleTime = DateTime.UtcNow;
        _lastCpuUsage = proc.TotalProcessorTime;
        _cpuSampleTimer = new Timer(_ => SampleCpuUsage(), null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
    }

    public async Task<Varz> HandleVarzAsync(CancellationToken ct = default)
    {
        await _varzMu.WaitAsync(ct);
        try
        {
            using var proc = Process.GetCurrentProcess();
            var now = DateTime.UtcNow;
            var uptime = now - _server.StartTime;
            var stats = _server.Stats;

            var cachedCpuPercent = GetCachedCpuPercent();

            // Load the TLS certificate to report its expiry date in /varz.
            // Corresponds to Go server/monitor.go handleVarz populating TLSCertExpiry.
            DateTime? tlsCertExpiry = null;
            if (_options.HasTls && !string.IsNullOrEmpty(_options.TlsCert))
            {
                try
                {
                    using var cert = X509CertificateLoader.LoadCertificateFromFile(_options.TlsCert);
                    tlsCertExpiry = cert.NotAfter;
                }
                catch
                {
                    // cert load failure — leave field as default
                }
            }

            return new Varz
            {
                Id = _server.ServerId,
                Name = _server.ServerName,
                Version = NatsProtocol.Version,
                Proto = NatsProtocol.ProtoVersion,
                GoVersion = $"dotnet {RuntimeInformation.FrameworkDescription}",
                Host = _options.Host,
                Port = _options.Port,
                HttpHost = _options.MonitorHost,
                HttpPort = _options.MonitorPort,
                HttpBasePath = _options.MonitorBasePath ?? "",
                HttpsPort = _options.MonitorHttpsPort,
                TlsRequired = _options.HasTls && !_options.AllowNonTls,
                TlsVerify = _options.HasTls && _options.TlsVerify,
                TlsTimeout = _options.HasTls ? _options.TlsTimeout.TotalSeconds : 0,
                TlsCertNotAfter = tlsCertExpiry ?? default,
                TlsOcspPeerVerify = _options.OcspPeerVerify,
                MaxConnections = _options.MaxConnections,
                MaxPayload = _options.MaxPayload,
                MaxControlLine = _options.MaxControlLine,
                MaxPending = _options.MaxPending,
                WriteDeadline = (long)_options.WriteDeadline.TotalNanoseconds,
                MaxPingsOut = _options.MaxPingsOut,
                PingInterval = (long)_options.PingInterval.TotalNanoseconds,
                Start = _server.StartTime,
                Now = now,
                Uptime = FormatUptime(uptime),
                Mem = proc.WorkingSet64,
                Cpu = Math.Round(cachedCpuPercent, 2),
                Cores = Environment.ProcessorCount,
                MaxProcs = ThreadPool.ThreadCount,
                Connections = _server.ClientCount,
                TotalConnections = (ulong)Interlocked.Read(ref stats.TotalConnections),
                InMsgs = Interlocked.Read(ref stats.InMsgs),
                OutMsgs = Interlocked.Read(ref stats.OutMsgs),
                InBytes = Interlocked.Read(ref stats.InBytes),
                OutBytes = Interlocked.Read(ref stats.OutBytes),
                SlowConsumers = Interlocked.Read(ref stats.SlowConsumers),
                SlowConsumerStats = new SlowConsumersStats
                {
                    Clients = (ulong)Interlocked.Read(ref stats.SlowConsumerClients),
                    Routes = (ulong)Interlocked.Read(ref stats.SlowConsumerRoutes),
                    Gateways = (ulong)Interlocked.Read(ref stats.SlowConsumerGateways),
                    Leafs = (ulong)Interlocked.Read(ref stats.SlowConsumerLeafs),
                },
                StaleConnections = Interlocked.Read(ref stats.StaleConnections),
                StaleConnectionStatsDetail = new StaleConnectionStats
                {
                    Clients = (ulong)Interlocked.Read(ref stats.StaleConnectionClients),
                    Routes = (ulong)Interlocked.Read(ref stats.StaleConnectionRoutes),
                    Gateways = (ulong)Interlocked.Read(ref stats.StaleConnectionGateways),
                    Leafs = (ulong)Interlocked.Read(ref stats.StaleConnectionLeafs),
                },
                Subscriptions = _server.SubList.Count,
                ConfigLoadTime = _server.StartTime,
                HttpReqStats = stats.HttpReqStats.ToDictionary(kv => kv.Key, kv => (ulong)kv.Value),
                Mqtt = BuildMqttVarz(),
                JetStream = new JetStreamVarz
                {
                    Config = new JetStreamConfig
                    {
                        MaxMemory = _options.JetStream?.MaxMemoryStore ?? 0,
                        MaxStorage = _options.JetStream?.MaxFileStore ?? 0,
                        StoreDir = _options.JetStream?.StoreDir ?? string.Empty,
                    },
                    Stats = new JetStreamStats
                    {
                        Accounts = _options.JetStream is null ? 0 : 1,
                        HaAssets = _server.JetStreamStreams,
                        Streams = _server.JetStreamStreams,
                        Consumers = _server.JetStreamConsumers,
                        Api = new JetStreamApiStats
                        {
                            Total = (ulong)Math.Max(Interlocked.Read(ref stats.JetStreamApiTotal), 0),
                            Errors = (ulong)Math.Max(Interlocked.Read(ref stats.JetStreamApiErrors), 0),
                        },
                    },
                },
            };
        }
        finally
        {
            _varzMu.Release();
        }
    }

    public void Dispose()
    {
        _cpuSampleTimer.Dispose();
        _varzMu.Dispose();
    }

    private void SampleCpuUsage()
    {
        try
        {
            using var proc = Process.GetCurrentProcess();
            var now = DateTime.UtcNow;
            lock (_cpuSampleSync)
            {
                var currentCpu = proc.TotalProcessorTime;
                var elapsed = now - _lastCpuSampleTime;
                if (elapsed.TotalMilliseconds <= 0)
                    return;

                _cachedCpuPercent = (currentCpu - _lastCpuUsage).TotalMilliseconds
                    / elapsed.TotalMilliseconds / Environment.ProcessorCount * 100.0;
                _lastCpuSampleTime = now;
                _lastCpuUsage = currentCpu;
            }
        }
        catch (Exception ex)
        {
            _logger.LogDebug(ex, "CPU usage sampling failed; retaining last cached value");
        }
    }

    private double GetCachedCpuPercent()
    {
        lock (_cpuSampleSync)
        {
            return _cachedCpuPercent;
        }
    }

    private MqttOptsVarz BuildMqttVarz()
    {
        var mqtt = _options.Mqtt;
        if (mqtt is null)
            return new MqttOptsVarz();

        return new MqttOptsVarz
        {
            Host = mqtt.Host,
            Port = mqtt.Port,
            NoAuthUser = mqtt.NoAuthUser ?? "",
            AuthTimeout = mqtt.AuthTimeout,
            TlsMap = mqtt.TlsMap,
            TlsTimeout = mqtt.TlsTimeout,
            TlsPinnedCerts = mqtt.TlsPinnedCerts?.ToArray() ?? [],
            JsDomain = mqtt.JsDomain ?? "",
            AckWait = (long)mqtt.AckWait.TotalNanoseconds,
            MaxAckPending = mqtt.MaxAckPending,
        };
    }

    /// <summary>
    /// Formats a TimeSpan as a human-readable uptime string matching Go server format.
    /// </summary>
    private static string FormatUptime(TimeSpan ts)
    {
        if (ts.TotalDays >= 1)
            return $"{(int)ts.TotalDays}d{ts.Hours}h{ts.Minutes}m{ts.Seconds}s";
        if (ts.TotalHours >= 1)
            return $"{(int)ts.TotalHours}h{ts.Minutes}m{ts.Seconds}s";
        if (ts.TotalMinutes >= 1)
            return $"{(int)ts.TotalMinutes}m{ts.Seconds}s";
        return $"{(int)ts.TotalSeconds}s";
    }
}
