#nullable enable
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using IGP.UnitySDK.Models;
using IGP.UnitySDK.Network;
using IGP.UnitySDK.Protocol;
using IGP.UnitySDK.ThirdParty.Kcp2k;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;

namespace IGP.UnitySDK.Core
{
    /// <summary>
    /// Unity 版本 KCP 客户端（lowlevel IGP.UnitySDK.ThirdParty.Kcp2k.Kcp + UDP Socket）。
    ///
    /// 注意：本实现使用 length-prefixed JSON：
    /// [len:4 bytes big-endian] + [json bytes]
    /// 并通过 WS 获取 token 后在 KCP 上发送 kcp_handshake。
    /// </summary>
    public class IGPKcpClient : IDisposable
    {
        private const int HeaderLength = 4;
        private const int MaxDatagramSize = 1500;
        private const int MaxDatagramsPerTick = 256;
        private const int MaxKcpReceiveBufferBytes = 512 * 1024;
        private const int MaxPendingPingCount = 32;
        private const int MaxKcpPendingSegments = 1024;

        private readonly object stateLock = new object();
        private IGPReliableTransportOptions transportOptions = IGPReliableTransportOptions.Default;
        private IGPKcpFrameDecoder frameDecoder;
        private Socket? socket;
        private EndPoint? remoteEndPoint;
        private Kcp? kcp;
        private bool isDisposed = false;

        private readonly byte[] datagramBuffer = new byte[MaxDatagramSize];
        private byte[] kcpReceiveBuffer;

        private bool authenticated;
        private uint nextUpdate;

        // 心跳 + RTT 相关字段
        private IGPRTTStats rttStats = new IGPRTTStats();
        private Dictionary<int, PendingPing> pendingPings = new Dictionary<int, PendingPing>();
        private int pingSeq;
        private float lastPongTime;
        private float lastHeartbeatTime;
        private bool isAlive;
        private float heartbeatInterval = 1.0f;
        private bool heartbeatRunning;
        private bool handshakeDatagramLogged;
        private string handshakeTarget = string.Empty;
        private long diagnosticsQueuedFrames;
        private long diagnosticsQueuedFrameBytes;
        private long diagnosticsKcpSendFailures;
        private long diagnosticsOutputDatagrams;
        private long diagnosticsOutputBytes;
        private long diagnosticsReceiveDatagrams;
        private long diagnosticsReceiveBytes;
        private long diagnosticsReceiveLimitHits;
        private long diagnosticsSendErrors;
        private long diagnosticsSendWouldBlock;
        private long diagnosticsReceiveErrors;
        private long diagnosticsReceiveWouldBlock;
        private long diagnosticsKcpReceiveBufferGrows;
        private long diagnosticsKcpOversizeReceives;
        private long diagnosticsRttSamples;
        private long diagnosticsRttCongestedSamples;
        private long diagnosticsDroppedPendingPings;
        private long diagnosticsBackpressureDrops;
        private int diagnosticsLastPingQueueDepth;
        private int diagnosticsMaxPingQueueDepth;
        private float nextBackpressureErrorAt;

        private readonly struct PendingPing
        {
            public PendingPing(long clientTimestamp, int queueDepthAtSend)
            {
                ClientTimestamp = clientTimestamp;
                QueueDepthAtSend = queueDepthAtSend;
            }

            public long ClientTimestamp { get; }
            public int QueueDepthAtSend { get; }
        }

        public string? RoomId { get; private set; }
        public string? PlayerId { get; private set; }
        public bool IsConnected => socket != null && kcp != null && authenticated;
        
        /// <summary>
        /// RTT统计信息
        /// </summary>
        public IGPRTTStats RTTStats => rttStats;
        
        /// <summary>
        /// 连接是否活跃（基于心跳）
        /// </summary>
        public bool IsAlive => isAlive && IsConnected && 
            (Time.realtimeSinceStartup - lastPongTime) < heartbeatInterval * 3;
        
        /// <summary>
        /// 心跳间隔（秒）
        /// </summary>
        public float HeartbeatInterval
        {
            get => heartbeatInterval;
            set => heartbeatInterval = value;
        }

        public event Action<Message>? MessageReceived;
        public event Action<string>? ErrorOccurred;
        public event Action<bool>? ConnectionStateChanged;

        public IGPKcpClient()
        {
            frameDecoder = new IGPKcpFrameDecoder(transportOptions);
            kcpReceiveBuffer = new byte[transportOptions.KcpFrameMaxBytes + HeaderLength];
        }

        internal void ApplyTransportOptions(IGPReliableTransportOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            lock (stateLock)
            {
                transportOptions = options;
                frameDecoder = new IGPKcpFrameDecoder(transportOptions);
                kcpReceiveBuffer = new byte[transportOptions.KcpFrameMaxBytes + HeaderLength];
            }
        }

        public void Connect(string host, int port, string roomId, string playerId, string token)
        {
            if (socket != null && kcp != null)
            {
                return;
            }

            Disconnect();

            RoomId = roomId;
            PlayerId = playerId;
            authenticated = false;
            frameDecoder.Reset();
            
            // 初始化心跳和RTT
            rttStats = new IGPRTTStats();
            pendingPings = new Dictionary<int, PendingPing>();
            pingSeq = 0;
            lastPongTime = Time.realtimeSinceStartup;
            lastHeartbeatTime = Time.realtimeSinceStartup;
            isAlive = true;
            heartbeatRunning = false;
            handshakeDatagramLogged = false;
            handshakeTarget = string.Empty;

            try
            {
                var ip = ResolveIP(host);
                remoteEndPoint = new IPEndPoint(ip, port);
                handshakeTarget = remoteEndPoint.ToString() ?? $"{host}:{port}";

                socket = new Socket(ip.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
                {
                    Blocking = false
                };
                socket.Connect(remoteEndPoint);

                uint conv = CreateRandomConv();
                kcp = new Kcp(conv, (buffer, size) =>
                {
                    try
                    {
                        var sent = socket?.Send(buffer, 0, size, SocketFlags.None) ?? 0;
                        diagnosticsOutputDatagrams++;
                        diagnosticsOutputBytes += sent;
                        IGPNetworkDiagnostics.AddOutDatagram(sent);
                        if (!authenticated && !handshakeDatagramLogged)
                        {
                            handshakeDatagramLogged = true;
                            Debug.Log(
                                $"[IGP KCP] Handshake datagram sent to {handshakeTarget} " +
                                $"(bytes={sent}, room={roomId}, player={playerId})");
                        }
                    }
                    catch (SocketException ex)
                    {
                        diagnosticsSendErrors++;
                        if (ex.SocketErrorCode == SocketError.WouldBlock)
                        {
                            diagnosticsSendWouldBlock++;
                        }
                        ErrorOccurred?.Invoke($"KCP output send failed: {ex.Message}");
                    }
                    catch (Exception ex)
                    {
                        diagnosticsSendErrors++;
                        ErrorOccurred?.Invoke($"KCP output send failed: {ex.Message}");
                    }
                });

                kcp.SetNoDelay(1, 10, 2, nocwnd: true);
                kcp.SetWindowSize(128, 128);
                kcp.SetInterval(10);

                var now = NowMs();
                nextUpdate = now;

                // kcp handshake (framed)
                var handshake = new
                {
                    type = "kcp_handshake",
                    token = token
                };
                Debug.Log(
                    $"[IGP KCP] Queueing handshake for {handshakeTarget} " +
                    $"(room={roomId}, player={playerId}, token={FormatTokenForLog(token)})");
                SendRawJson(handshake);
            }
            catch (Exception ex)
            {
                ErrorOccurred?.Invoke($"KCP connect failed: {ex.Message}");
                Disconnect();
            }
        }

        public void Disconnect()
        {
            DisconnectInternal(suppressDisposedGuard: false);
        }

        private void DisconnectInternal(bool suppressDisposedGuard)
        {
            lock (stateLock)
            {
                if (isDisposed && !suppressDisposedGuard) return;

                authenticated = false;
                heartbeatRunning = false;
                RoomId = null;
                PlayerId = null;
                frameDecoder.Reset();
                pendingPings?.Clear();
                handshakeDatagramLogged = false;
                handshakeTarget = string.Empty;
                nextBackpressureErrorAt = 0f;

                try
                {
                    socket?.Close(0);  // 立即关闭，不等待
                }
                catch
                {
                    // ignore
                }

                socket = null;
                remoteEndPoint = null;
                kcp = null;
            }

            ConnectionStateChanged?.Invoke(false);
        }

        public void Dispose()
        {
            lock (stateLock)
            {
                if (isDisposed) return;
                isDisposed = true;
            }

            DisconnectInternal(suppressDisposedGuard: true);
        }

        /// <summary>
        /// 由 IGPRuntimeManager 在 Update() 中驱动。
        /// </summary>
        public void Tick()
        {
            if (socket == null || kcp == null)
            {
                return;
            }

            uint now = NowMs();

            // receive datagrams and feed into kcp
            try
            {
                int receivedThisTick = 0;
                while (socket.Poll(0, SelectMode.SelectRead) && receivedThisTick < MaxDatagramsPerTick)
                {
                    int received = socket.Receive(datagramBuffer, 0, datagramBuffer.Length, SocketFlags.None);
                    if (received > 0)
                    {
                        receivedThisTick++;
                        diagnosticsReceiveDatagrams++;
                        diagnosticsReceiveBytes += received;
                        IGPNetworkDiagnostics.AddRecvDatagram(received);
                        kcp.Input(datagramBuffer, 0, received);
                    }
                    else
                    {
                        break;
                    }
                }

                if (receivedThisTick >= MaxDatagramsPerTick && socket.Poll(0, SelectMode.SelectRead))
                {
                    diagnosticsReceiveLimitHits++;
                    IGPNetworkDiagnostics.MarkRecvHit64();
                }
            }
            catch (SocketException ex)
            {
                diagnosticsReceiveErrors++;
                if (ex.SocketErrorCode == SocketError.WouldBlock)
                {
                    diagnosticsReceiveWouldBlock++;
                }
                // ignore intermittent poll/receive errors
            }
            catch (Exception ex)
            {
                diagnosticsReceiveErrors++;
                ErrorOccurred?.Invoke($"KCP receive failed: {ex.Message}");
            }

            if (now < nextUpdate)
            {
                // still need to drain receive queue if any (rare)
                DrainKcpReceive();
                return;
            }

            try
            {
                kcp.Update(now);
                DrainKcpReceive();
                nextUpdate = kcp.Check(now);
                
                // 自动心跳逻辑
                if (heartbeatRunning && authenticated)
                {
                    float currentTime = Time.realtimeSinceStartup;
                    if (currentTime - lastHeartbeatTime >= heartbeatInterval)
                    {
                        SendPing();
                        lastHeartbeatTime = currentTime;
                    }
                }
            }
            catch (Exception ex)
            {
                ErrorOccurred?.Invoke($"KCP tick failed: {ex.Message}");
            }
        }

        private void DrainKcpReceive()
        {
            if (kcp == null) return;

            while (true)
            {
                int n = kcp.Receive(kcpReceiveBuffer, kcpReceiveBuffer.Length);
                if (n == -3)
                {
                    int requiredBytes = kcp.PeekSize();
                    if (requiredBytes > kcpReceiveBuffer.Length && requiredBytes <= MaxKcpReceiveBufferBytes)
                    {
                        kcpReceiveBuffer = new byte[requiredBytes];
                        diagnosticsKcpReceiveBufferGrows++;
                        continue;
                    }

                    diagnosticsKcpOversizeReceives++;
                    ErrorOccurred?.Invoke(
                        requiredBytes > 0
                            ? $"KCP receive message exceeds supported buffer size: {requiredBytes} bytes"
                            : "KCP receive message is larger than the current receive buffer");
                    break;
                }

                if (n <= 0)
                {
                    break;
                }

                var frames = frameDecoder.Append(kcpReceiveBuffer, 0, n);
                foreach (var frame in frames)
                {
                    HandlePayload(frame);
                }
            }
        }

        public void SendMessage(Message message)
        {
            _ = TrySendMessage(message);
        }

        public bool TrySendMessage(Message message)
        {
            if (!IsConnected || !authenticated || string.IsNullOrEmpty(RoomId) || string.IsNullOrEmpty(PlayerId))
            {
                return false;
            }

            message.roomId ??= RoomId;
            message.playerId ??= PlayerId;

            if (TryEncodeBinaryDataPlaneMessage(message, out var binaryPayload))
            {
                return SendFrame(binaryPayload);
            }

            var json = JsonConvert.SerializeObject(message);
            return SendFrame(Encoding.UTF8.GetBytes(json));
        }

        /// <summary>
        /// 启动自动心跳（同时测量RTT）
        /// </summary>
        /// <param name="interval">心跳间隔（秒），默认1秒</param>
        public void StartHeartbeat(float interval = 1.0f)
        {
            heartbeatInterval = interval;
            heartbeatRunning = true;
            lastHeartbeatTime = Time.realtimeSinceStartup;
            Debug.Log($"[IGP KCP] Heartbeat started (interval: {interval}s)");
        }
        
        /// <summary>
        /// 停止自动心跳
        /// </summary>
        public void StopHeartbeat()
        {
            heartbeatRunning = false;
            Debug.Log("[IGP KCP] Heartbeat stopped");
        }
        
        /// <summary>
        /// 发送Ping（手动或由心跳自动调用）
        /// </summary>
        public void SendPing()
        {
            if (!IsConnected || !authenticated) return;
            
            long timestamp = System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            TrimPendingPings(timestamp);
            int seq = pingSeq++;
            int queueDepthAtSend = Math.Max(0, WaitSnd);
            
            pendingPings[seq] = new PendingPing(timestamp, queueDepthAtSend);
            diagnosticsLastPingQueueDepth = queueDepthAtSend;
            if (queueDepthAtSend > diagnosticsMaxPingQueueDepth)
            {
                diagnosticsMaxPingQueueDepth = queueDepthAtSend;
            }
            
            SendPing(timestamp, seq);
        }

        private void TrimPendingPings(long nowMs)
        {
            long maxAgeMs = Math.Max(5000L, (long)(heartbeatInterval * 4000f));
            long expireBefore = nowMs - maxAgeMs;
            List<int>? expired = null;
            foreach (var item in pendingPings)
            {
                if (item.Value.ClientTimestamp >= expireBefore)
                {
                    continue;
                }

                expired ??= new List<int>();
                expired.Add(item.Key);
            }

            if (expired != null)
            {
                foreach (var seq in expired)
                {
                    if (pendingPings.Remove(seq))
                    {
                        diagnosticsDroppedPendingPings++;
                    }
                }
            }

            while (pendingPings.Count >= MaxPendingPingCount)
            {
                if (!RemoveOldestPendingPing())
                {
                    break;
                }

                diagnosticsDroppedPendingPings++;
            }
        }

        private bool RemoveOldestPendingPing()
        {
            if (pendingPings.Count == 0)
            {
                return false;
            }

            int oldestSeq = 0;
            long oldestTimestamp = long.MaxValue;
            foreach (var item in pendingPings)
            {
                if (item.Value.ClientTimestamp >= oldestTimestamp)
                {
                    continue;
                }

                oldestTimestamp = item.Value.ClientTimestamp;
                oldestSeq = item.Key;
            }

            return pendingPings.Remove(oldestSeq);
        }
        
        /// <summary>
        /// 发送Ping（内部方法）
        /// </summary>
        private void SendPing(long clientTimestamp, int seq)
        {
            if (!IsConnected) return;

            var ping = new Message
            {
                type = "ping",
                roomId = RoomId ?? string.Empty,
                playerId = PlayerId ?? string.Empty,
                reliable = true,
                content = new Dictionary<string, object>
                {
                    ["clientTimestamp"] = clientTimestamp,
                    ["seq"] = seq,
                }
            };
            if (!TrySendMessage(ping) && pendingPings.Remove(seq))
            {
                diagnosticsDroppedPendingPings++;
            }
        }

        private void SendRawJson(object obj)
        {
            var json = JsonConvert.SerializeObject(obj);
            _ = SendFrame(Encoding.UTF8.GetBytes(json));
        }

        private bool SendFrame(byte[] payload)
        {
            if (kcp == null)
            {
                return false;
            }

            if (kcp.WaitSnd >= MaxKcpPendingSegments)
            {
                diagnosticsBackpressureDrops++;
                if (Time.realtimeSinceStartup >= nextBackpressureErrorAt)
                {
                    ErrorOccurred?.Invoke(
                        $"KCP send queue is congested: WaitSnd={kcp.WaitSnd}, limit={MaxKcpPendingSegments}");
                    nextBackpressureErrorAt = Time.realtimeSinceStartup + 1f;
                }

                return false;
            }

            var frame = IGPKcpFrameCodec.EncodeFrame(payload, transportOptions);
            int result = kcp.Send(frame, 0, frame.Length);
            if (result == 0)
            {
                diagnosticsQueuedFrames++;
                diagnosticsQueuedFrameBytes += frame.Length;
                return true;
            }
            else
            {
                diagnosticsKcpSendFailures++;
                return false;
            }
        }

        internal string ConsumeDiagnosticsSummary()
        {
            var localKcp = kcp;
            var localSocket = socket;

            long queuedFrames = diagnosticsQueuedFrames;
            long queuedFrameBytes = diagnosticsQueuedFrameBytes;
            long kcpSendFailures = diagnosticsKcpSendFailures;
            long outDatagrams = diagnosticsOutputDatagrams;
            long outBytes = diagnosticsOutputBytes;
            long recvDatagrams = diagnosticsReceiveDatagrams;
            long recvBytes = diagnosticsReceiveBytes;
            long receiveLimitHits = diagnosticsReceiveLimitHits;
            long sendErrors = diagnosticsSendErrors;
            long sendWouldBlock = diagnosticsSendWouldBlock;
            long receiveErrors = diagnosticsReceiveErrors;
            long receiveWouldBlock = diagnosticsReceiveWouldBlock;
            long kcpReceiveBufferGrows = diagnosticsKcpReceiveBufferGrows;
            long kcpOversizeReceives = diagnosticsKcpOversizeReceives;
            long rttSamples = diagnosticsRttSamples;
            long rttCongestedSamples = diagnosticsRttCongestedSamples;
            long droppedPendingPings = diagnosticsDroppedPendingPings;
            long backpressureDrops = diagnosticsBackpressureDrops;
            int lastPingQueueDepth = diagnosticsLastPingQueueDepth;
            int maxPingQueueDepth = diagnosticsMaxPingQueueDepth;
            int pendingPingCount = pendingPings.Count;

            diagnosticsQueuedFrames = 0;
            diagnosticsQueuedFrameBytes = 0;
            diagnosticsKcpSendFailures = 0;
            diagnosticsOutputDatagrams = 0;
            diagnosticsOutputBytes = 0;
            diagnosticsReceiveDatagrams = 0;
            diagnosticsReceiveBytes = 0;
            diagnosticsReceiveLimitHits = 0;
            diagnosticsSendErrors = 0;
            diagnosticsSendWouldBlock = 0;
            diagnosticsReceiveErrors = 0;
            diagnosticsReceiveWouldBlock = 0;
            diagnosticsKcpReceiveBufferGrows = 0;
            diagnosticsKcpOversizeReceives = 0;
            diagnosticsRttSamples = 0;
            diagnosticsRttCongestedSamples = 0;
            diagnosticsDroppedPendingPings = 0;
            diagnosticsBackpressureDrops = 0;
            diagnosticsMaxPingQueueDepth = diagnosticsLastPingQueueDepth;

            int socketAvailable = -1;
            if (localSocket != null)
            {
                try
                {
                    socketAvailable = localSocket.Available;
                }
                catch
                {
                    socketAvailable = -1;
                }
            }

            string interval =
                $"queuedFrames={queuedFrames} queuedKB={FormatKilobytes(queuedFrameBytes)} " +
                $"outPkt={outDatagrams} outKB={FormatKilobytes(outBytes)} " +
                $"recvPkt={recvDatagrams} recvKB={FormatKilobytes(recvBytes)} " +
                $"hit64={receiveLimitHits} socketAvailable={socketAvailable} " +
                $"sendErr={sendErrors} sendWouldBlock={sendWouldBlock} " +
                $"recvErr={receiveErrors} recvWouldBlock={receiveWouldBlock} " +
                $"kcpSendFail={kcpSendFailures} recvBufGrow={kcpReceiveBufferGrows} " +
                $"recvOversize={kcpOversizeReceives} recvLimit={MaxDatagramsPerTick} " +
                $"sendBackpressureDrop={backpressureDrops} " +
                $"rttLastMs={FormatMilliseconds(rttStats.LastRTT)} " +
                $"rttAvgMs={FormatMilliseconds(rttStats.AvgRTT)} " +
                $"rttRawAvgMs={FormatMilliseconds(rttStats.RawAvgRTT)} " +
                $"rttSamples={rttSamples} rttCongested={rttCongestedSamples}/{rttStats.CongestedSampleCount} " +
                $"pingPending={pendingPingCount} pingQueueDepth={lastPingQueueDepth}(peak{maxPingQueueDepth}) " +
                $"pingDrop={droppedPendingPings}";

            if (localKcp == null)
            {
                return $"kcpReady=false {interval}";
            }

            return
                $"kcpReady=true WaitSnd={localKcp.WaitSnd} " +
                $"sndQueue={localKcp.snd_queue.Count} sndBuf={localKcp.snd_buf.Count} " +
                $"rmtWnd={localKcp.rmt_wnd} sndUna={localKcp.snd_una} sndNxt={localKcp.snd_nxt} " +
                $"rcvQueue={localKcp.rcv_queue.Count} rcvBuf={localKcp.rcv_buf.Count} " +
                $"kcpState={localKcp.state} {interval}";
        }

        internal int WaitSnd => kcp?.WaitSnd ?? -1;

        private void HandlePayload(byte[] payload)
        {
            if (TryHandleBinaryEnvelope(payload))
            {
                return;
            }

            var json = Encoding.UTF8.GetString(payload);
            if (string.IsNullOrWhiteSpace(json))
            {
                return;
            }

            try
            {
                var obj = JObject.Parse(json);
                var t = obj.Value<string>("type");
                
                if (t == "kcp_handshake_ack")
                {
                    if (!authenticated)
                    {
                        authenticated = true;
                        Debug.Log("[IGP KCP] Handshake acknowledged");
                        ConnectionStateChanged?.Invoke(true);
                    }
                }
                else if (t == "pong")
                {
                    // 处理pong响应，计算RTT
                    HandlePong(obj);
                }
            }
            catch
            {
                // ignore type check errors
            }

            try
            {
                var message = JsonConvert.DeserializeObject<Message>(json);
                if (message != null)
                {
                    MessageReceived?.Invoke(message);
                }
            }
            catch (Exception ex)
            {
                ErrorOccurred?.Invoke($"Failed to parse KCP message: {ex.Message}");
            }
        }

        private bool TryEncodeBinaryDataPlaneMessage(Message message, out byte[] payload)
        {
            payload = Array.Empty<byte>();
            if (!string.Equals(message.type, "p2p_data", StringComparison.Ordinal) || message.content == null)
            {
                return false;
            }

            P2PMessagePayload? dataPayload;
            if (message.content is P2PMessagePayload typedPayload)
            {
                dataPayload = typedPayload;
            }
            else if (message.content is JObject jobj)
            {
                dataPayload = jobj.ToObject<P2PMessagePayload>();
            }
            else
            {
                dataPayload = JsonConvert.DeserializeObject<P2PMessagePayload>(
                    JsonConvert.SerializeObject(message.content));
            }

            if (dataPayload == null ||
                string.IsNullOrWhiteSpace(dataPayload.data) ||
                HasReliableChunkMetadata(dataPayload))
            {
                return false;
            }

            byte[] rawPayload = Convert.FromBase64String(dataPayload.data);
            var envelope = new IGPKcpBinaryEnvelope(
                version: 1,
                flags: 0,
                messageType: dataPayload.messageType,
                targetKind: string.IsNullOrWhiteSpace(dataPayload.targetId)
                    ? IGPKcpTargetKind.Broadcast
                    : IGPKcpTargetKind.Player,
                senderPlayerId: !string.IsNullOrWhiteSpace(dataPayload.senderId)
                    ? dataPayload.senderId
                    : message.playerId ?? PlayerId ?? string.Empty,
                targetPlayerId: dataPayload.targetId ?? message.targetPlayerId ?? string.Empty,
                payload: rawPayload);

            payload = IGPKcpBinaryEnvelopeCodec.Encode(envelope, transportOptions);
            return true;
        }

        private bool TryHandleBinaryEnvelope(byte[] payload)
        {
            if (payload == null || payload.Length < 15 || payload[0] != 1)
            {
                return false;
            }

            try
            {
                var envelope = IGPKcpBinaryEnvelopeCodec.Decode(payload);
                MessageReceived?.Invoke(new Message
                {
                    type = "p2p_data",
                    roomId = RoomId ?? string.Empty,
                    playerId = envelope.SenderPlayerId,
                    targetPlayerId = envelope.TargetPlayerId,
                    reliable = true,
                    content = new P2PMessagePayload
                    {
                        senderId = envelope.SenderPlayerId,
                        targetId = envelope.TargetPlayerId,
                        data = Convert.ToBase64String(envelope.Payload),
                        messageType = envelope.MessageType,
                        reliable = true
                    }
                });
                return true;
            }
            catch (Exception ex)
            {
                ErrorOccurred?.Invoke($"Failed to parse KCP binary envelope: {ex.Message}");
                return true;
            }
        }

        private static bool HasReliableChunkMetadata(P2PMessagePayload dataPayload)
        {
            return !string.IsNullOrWhiteSpace(dataPayload.reliableMessageId) ||
                   dataPayload.reliableChunkIndex.HasValue ||
                   dataPayload.reliableChunkCount.HasValue ||
                   dataPayload.reliableTotalBytes.HasValue ||
                   dataPayload.reliableMessageType.HasValue;
        }
        
        /// <summary>
        /// 处理Pong响应，计算RTT
        /// </summary>
        private void HandlePong(JObject pongMessage)
        {
            try
            {
                var content = pongMessage["content"] as JObject;
                if (content == null || content.Count == 0)
                {
                    return;
                }
                
                // 从content中获取seq和clientTimestamp
                var seqToken = content["seq"];
                var timestampToken = content["clientTimestamp"];
                
                if (seqToken == null || timestampToken == null)
                {
                    return;
                }
                
                int seq = seqToken.Type == JTokenType.Float 
                    ? (int)seqToken.Value<double>() 
                    : seqToken.Value<int>();
                    
                long clientTimestamp = timestampToken.Type == JTokenType.Float
                    ? (long)timestampToken.Value<double>()
                    : timestampToken.Value<long>();
                
                if (!pendingPings.TryGetValue(seq, out var pendingPing))
                {
                    return;
                }
                
                pendingPings.Remove(seq);
                
                // 计算RTT（使用本地记录的客户端时间戳，避免被响应内容污染）
                long now = System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                float rtt = (now - pendingPing.ClientTimestamp) / 1000f; // 转换为秒
                bool sampleIsCongested = pendingPing.QueueDepthAtSend > 0;

                rttStats.AddSample(rtt, sampleIsCongested);
                diagnosticsRttSamples++;
                if (sampleIsCongested)
                {
                    diagnosticsRttCongestedSamples++;
                }
                
                // 更新心跳状态（保活）
                lastPongTime = Time.realtimeSinceStartup;
                isAlive = true;
            }
            catch (Exception ex)
            {
                Debug.LogWarning($"[IGP KCP] Failed to handle pong: {ex.Message}");
            }
        }

        private static uint NowMs()
        {
            // realtimeSinceStartup is monotonic in Unity.
            return (uint)(Time.realtimeSinceStartupAsDouble * 1000.0);
        }

        private static IPAddress ResolveIP(string host)
        {
            if (IPAddress.TryParse(host, out var ip))
            {
                return ip;
            }

            var addresses = Dns.GetHostAddresses(host);
            if (addresses.Length == 0)
            {
                throw new InvalidOperationException($"Cannot resolve host: {host}");
            }

            foreach (var addr in addresses)
            {
                if (addr.AddressFamily == AddressFamily.InterNetwork)
                {
                    return addr;
                }
            }

            return addresses[0];
        }

        private static uint CreateRandomConv()
        {
            Span<byte> b = stackalloc byte[4];
            RandomNumberGenerator.Fill(b);
            return BinaryPrimitives.ReadUInt32LittleEndian(b);
        }

        private static string FormatTokenForLog(string token)
        {
            if (string.IsNullOrWhiteSpace(token))
            {
                return "<empty>";
            }

            if (token.Length <= 8)
            {
                return token;
            }

            return $"{token[..4]}...{token[^4..]}";
        }

        private static string FormatKilobytes(long bytes)
        {
            return (bytes / 1024.0).ToString("F1", CultureInfo.InvariantCulture);
        }

        private static string FormatMilliseconds(float seconds)
        {
            return (seconds * 1000f).ToString("F1", CultureInfo.InvariantCulture);
        }
    }
}
