using Microsoft.AspNetCore.Http;
using NATS.Server.Subscriptions;

namespace NATS.Server.Monitoring;

/// <summary>
/// Handles /connz endpoint requests, returning detailed connection information.
/// Corresponds to Go server/monitor.go handleConnz function.
/// </summary>
public sealed class ConnzHandler(NatsServer server)
{
    public Connz HandleConnz(HttpContext ctx)
    {
        var opts = ParseQueryParams(ctx);
        var now = DateTime.UtcNow;

        var connInfos = new List<ConnInfo>();

        // If a specific CID is requested, search for that single connection
        // Go reference: monitor.go Connz() — CID fast path
        if (opts.Cid > 0)
        {
            return HandleSingleCid(opts, now);
        }

        // Collect open connections
        if (opts.State is ConnState.Open or ConnState.All)
        {
            var clients = server.GetClients().ToArray();
            connInfos.AddRange(clients.Select(c => BuildConnInfo(c, now, opts)));
        }

        // Collect closed connections from the ring buffer
        if (opts.State is ConnState.Closed or ConnState.All)
        {
            connInfos.AddRange(server.GetClosedClients().Select(c => BuildClosedConnInfo(c, now, opts)));
        }

        // Filter by MQTT client ID
        if (!string.IsNullOrEmpty(opts.MqttClient))
            connInfos = connInfos.Where(c => c.MqttClient == opts.MqttClient).ToList();

        if (!string.IsNullOrEmpty(opts.User))
            connInfos = connInfos.Where(c => c.AuthorizedUser == opts.User).ToList();

        if (!string.IsNullOrEmpty(opts.Account))
            connInfos = connInfos.Where(c => c.Account == opts.Account).ToList();

        if (!string.IsNullOrEmpty(opts.FilterSubject))
            connInfos = connInfos.Where(c => MatchesSubjectFilter(c, opts.FilterSubject)).ToList();

        // Validate sort options that require closed state
        if (opts.Sort is SortOpt.ByStop or SortOpt.ByReason && opts.State == ConnState.Open)
            opts.Sort = SortOpt.ByCid; // Fallback

        // Sort
        connInfos = opts.Sort switch
        {
            SortOpt.ByCid => connInfos.OrderBy(c => c.Cid).ToList(),
            SortOpt.ByStart => connInfos.OrderBy(c => c.Start).ToList(),
            SortOpt.BySubs => connInfos.OrderByDescending(c => c.NumSubs).ToList(),
            SortOpt.ByPending => connInfos.OrderByDescending(c => c.Pending).ToList(),
            SortOpt.ByMsgsTo => connInfos.OrderByDescending(c => c.OutMsgs).ToList(),
            SortOpt.ByMsgsFrom => connInfos.OrderByDescending(c => c.InMsgs).ToList(),
            SortOpt.ByBytesTo => connInfos.OrderByDescending(c => c.OutBytes).ToList(),
            SortOpt.ByBytesFrom => connInfos.OrderByDescending(c => c.InBytes).ToList(),
            SortOpt.ByLast => connInfos.OrderByDescending(c => c.LastActivity).ToList(),
            SortOpt.ByIdle => connInfos.OrderByDescending(c => now - c.LastActivity).ToList(),
            SortOpt.ByUptime => connInfos.OrderByDescending(c => now - c.Start).ToList(),
            SortOpt.ByStop => connInfos.OrderByDescending(c => c.Stop ?? DateTime.MinValue).ToList(),
            SortOpt.ByReason => connInfos.OrderBy(c => c.Reason).ToList(),
            SortOpt.ByRtt => connInfos.OrderBy(c => c.Rtt).ToList(),
            _ => connInfos.OrderBy(c => c.Cid).ToList(),
        };

        var total = connInfos.Count;
        var paged = connInfos.Skip(opts.Offset).Take(opts.Limit).ToArray();

        return new Connz
        {
            Id = server.ServerId,
            Now = now,
            NumConns = paged.Length,
            Total = total,
            Offset = opts.Offset,
            Limit = opts.Limit,
            Conns = paged,
        };
    }

    /// <summary>
    /// Handles a request for a single connection by CID.
    /// Go reference: monitor.go Connz() — CID-specific path.
    /// </summary>
    private Connz HandleSingleCid(ConnzOptions opts, DateTime now)
    {
        // Search open connections first
        var client = server.GetClients().FirstOrDefault(c => c.Id == opts.Cid);
        if (client != null)
        {
            var info = BuildConnInfo(client, now, opts);
            return new Connz
            {
                Id = server.ServerId,
                Now = now,
                NumConns = 1,
                Total = 1,
                Offset = 0,
                Limit = 1,
                Conns = [info],
            };
        }

        // Search closed connections ring buffer
        var closed = server.GetClosedClients().FirstOrDefault(c => c.Cid == opts.Cid);
        if (closed != null)
        {
            var info = BuildClosedConnInfo(closed, now, opts);
            return new Connz
            {
                Id = server.ServerId,
                Now = now,
                NumConns = 1,
                Total = 1,
                Offset = 0,
                Limit = 1,
                Conns = [info],
            };
        }

        // Not found — return empty result
        return new Connz
        {
            Id = server.ServerId,
            Now = now,
            NumConns = 0,
            Total = 0,
            Offset = 0,
            Limit = 0,
            Conns = [],
        };
    }

    private static ConnInfo BuildConnInfo(NatsClient client, DateTime now, ConnzOptions opts)
    {
        var info = new ConnInfo
        {
            Cid = client.Id,
            Kind = "Client",
            Type = "Client",
            Ip = client.RemoteIp ?? "",
            Port = client.RemotePort,
            Start = client.StartTime,
            LastActivity = client.LastActivity,
            Uptime = FormatDuration(now - client.StartTime),
            Idle = FormatDuration(now - client.LastActivity),
            InMsgs = Interlocked.Read(ref client.InMsgs),
            OutMsgs = Interlocked.Read(ref client.OutMsgs),
            InBytes = Interlocked.Read(ref client.InBytes),
            OutBytes = Interlocked.Read(ref client.OutBytes),
            NumSubs = (uint)client.Subscriptions.Count,
            Name = client.ClientOpts?.Name ?? "",
            Lang = client.ClientOpts?.Lang ?? "",
            Version = client.ClientOpts?.Version ?? "",
            AuthorizedUser = client.ClientOpts?.Username ?? "",
            Account = client.Account?.Name ?? "",
            Pending = (int)client.PendingBytes,
            Reason = client.CloseReason.ToReasonString(),
            TlsVersion = client.TlsState?.TlsVersion ?? "",
            TlsCipherSuite = client.TlsState?.CipherSuite ?? "",
            TlsPeerCertSubject = client.TlsState?.PeerCert?.Subject ?? "",
            JwtIssuerKey = string.IsNullOrEmpty(client.ClientOpts?.JWT) ? "" : "present",
            JwtTags = "",
            Proxy = client.ClientOpts?.Username?.StartsWith("proxy:", StringComparison.Ordinal) == true ? "true" : "",
            Rtt = FormatRtt(client.Rtt),
        };

        if (opts.Subscriptions)
        {
            info.Subs = client.Subscriptions.Values.Select(s => s.Subject).ToArray();
        }
        else if (!string.IsNullOrEmpty(opts.FilterSubject))
        {
            info.Subs = client.Subscriptions.Values.Select(s => s.Subject).ToArray();
        }

        if (opts.SubscriptionsDetail)
        {
            info.SubsDetail = client.Subscriptions.Values.Select(s => new SubDetail
            {
                Subject = s.Subject,
                Queue = s.Queue ?? "",
                Sid = s.Sid,
                Msgs = Interlocked.Read(ref s.MessageCount),
                Max = s.MaxMessages,
                Cid = client.Id,
            }).ToArray();
        }

        return info;
    }

    private static ConnInfo BuildClosedConnInfo(ClosedClient closed, DateTime now, ConnzOptions opts)
    {
        return new ConnInfo
        {
            Cid = closed.Cid,
            Kind = "Client",
            Type = "Client",
            Ip = closed.Ip,
            Port = closed.Port,
            Start = closed.Start,
            Stop = closed.Stop,
            LastActivity = closed.Stop,
            Uptime = FormatDuration(closed.Stop - closed.Start),
            Idle = FormatDuration(now - closed.Stop),
            InMsgs = closed.InMsgs,
            OutMsgs = closed.OutMsgs,
            InBytes = closed.InBytes,
            OutBytes = closed.OutBytes,
            NumSubs = closed.NumSubs,
            Name = closed.Name,
            Lang = closed.Lang,
            Version = closed.Version,
            AuthorizedUser = closed.AuthorizedUser,
            Account = closed.Account,
            Reason = closed.Reason,
            Rtt = FormatRtt(closed.Rtt),
            TlsVersion = closed.TlsVersion,
            TlsCipherSuite = closed.TlsCipherSuite,
            TlsPeerCertSubject = closed.TlsPeerCertSubject,
            MqttClient = closed.MqttClient,
            JwtIssuerKey = closed.JwtIssuerKey,
            JwtTags = closed.JwtTags,
            Proxy = closed.Proxy,
        };
    }

    private static ConnzOptions ParseQueryParams(HttpContext ctx)
    {
        var q = ctx.Request.Query;
        var opts = new ConnzOptions();

        if (q.TryGetValue("sort", out var sort))
        {
            opts.Sort = sort.ToString().ToLowerInvariant() switch
            {
                "cid" => SortOpt.ByCid,
                "start" => SortOpt.ByStart,
                "subs" => SortOpt.BySubs,
                "pending" => SortOpt.ByPending,
                "msgs_to" => SortOpt.ByMsgsTo,
                "msgs_from" => SortOpt.ByMsgsFrom,
                "bytes_to" => SortOpt.ByBytesTo,
                "bytes_from" => SortOpt.ByBytesFrom,
                "last" => SortOpt.ByLast,
                "idle" => SortOpt.ByIdle,
                "uptime" => SortOpt.ByUptime,
                "rtt" => SortOpt.ByRtt,
                "stop" => SortOpt.ByStop,
                "reason" => SortOpt.ByReason,
                _ => SortOpt.ByCid,
            };
        }

        if (q.TryGetValue("subs", out var subs))
        {
            if (subs == "detail")
                opts.SubscriptionsDetail = true;
            else
                opts.Subscriptions = true;
        }

        if (q.TryGetValue("state", out var state))
        {
            opts.State = state.ToString().ToLowerInvariant() switch
            {
                "open" => ConnState.Open,
                "closed" => ConnState.Closed,
                "all" => ConnState.All,
                _ => ConnState.Open,
            };
        }

        if (q.TryGetValue("offset", out var offset) && int.TryParse(offset, out var o))
            opts.Offset = o;

        if (q.TryGetValue("limit", out var limit) && int.TryParse(limit, out var l))
            opts.Limit = l;

        if (q.TryGetValue("cid", out var cid) && ulong.TryParse(cid, out var cidValue))
            opts.Cid = cidValue;

        if (q.TryGetValue("auth", out var auth))
            opts.Auth = auth.ToString().ToLowerInvariant() is "1" or "true";

        if (q.TryGetValue("mqtt_client", out var mqttClient))
            opts.MqttClient = mqttClient.ToString();

        if (q.TryGetValue("user", out var user))
            opts.User = user.ToString();
        if (q.TryGetValue("acc", out var account))
            opts.Account = account.ToString();
        if (q.TryGetValue("filter_subject", out var filterSubject))
            opts.FilterSubject = filterSubject.ToString();

        return opts;
    }

    private static bool MatchesSubjectFilter(ConnInfo info, string filterSubject)
    {
        // Go reference: monitor.go — matchLiteral(testSub, string(sub.subject))
        // The filter subject is the literal, the subscription subject is the pattern
        // (subscriptions may contain wildcards like orders.> that match the filter orders.new)
        if (info.Subs.Any(s => SubjectMatch.MatchLiteral(filterSubject, s)))
            return true;

        return info.SubsDetail.Any(s => SubjectMatch.MatchLiteral(filterSubject, s.Subject));
    }

    private static string FormatRtt(TimeSpan rtt)
    {
        if (rtt == TimeSpan.Zero) return "";
        if (rtt.TotalMilliseconds < 1)
            return $"{rtt.TotalMicroseconds:F3}\u00b5s";
        if (rtt.TotalSeconds < 1)
            return $"{rtt.TotalMilliseconds:F3}ms";
        return $"{rtt.TotalSeconds:F3}s";
    }

    private static string FormatDuration(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";
    }
}
