#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using UnityEngine;
using IGP.UnitySDK.Core;
using IGP.UnitySDK.Models;
using IGP.UnitySDK.Protocol;
using Newtonsoft.Json.Linq;

namespace IGP.UnitySDK.Network
{
    public class IGPNetwork : IIGPNetwork
    {
        private const string LogScope = "p2p";
        private readonly IGPRuntimeManager _manager;
        private readonly object _lock = new object();

        // Sessions: Key = Remote Player ID
        private enum SessionStatus { None, Requested, Connected }
        private readonly Dictionary<string, SessionStatus> _sessions = new Dictionary<string, SessionStatus>();
        
        // Remote Player RTT storage (Value = Server RTT in seconds)
        private readonly Dictionary<string, float> _remotePlayerServerRtt = new Dictionary<string, float>();
        private float _lastRttBroadcastTime = 0f;
        private const float RttBroadcastInterval = 2.0f; // Seconds
        private const uint RttMessageType = 1000; // Special type for RTT exchange
        private const string ArenaControlTypeReliableMessageAborted = "reliable-message-aborted";
        private IGPReliableTransportOptions _transportOptions = IGPReliableTransportOptions.Default;
        private IGPReliableMessageReassembler _reassembler;
        
        // Incoming Data Queue
        private struct IncomingPacket
        {
            public IGPPlayerID LocalPeer;
            public IGPGamePeer RemotePeer;
            public byte[] Data;
            public uint MessageType;
            public long Timestamp;
        }
        
        private readonly ConcurrentQueue<IncomingPacket> _packetQueue = new ConcurrentQueue<IncomingPacket>();
        
        // Blocked Message Types
        private readonly HashSet<uint> _blockedTypes = new HashSet<uint>();

        // Events (Unity Style)
        public readonly IGPSessionRequestEvent OnCreateSessionRequest = new IGPSessionRequestEvent();
        public readonly IGPSessionFailedEvent OnCreateSessionFailed = new IGPSessionFailedEvent();
        public readonly IGPRawSessionRequestEvent OnNetworkCreateRawSessionRequest = new IGPRawSessionRequestEvent();
        public readonly IGPRawSessionFailedEvent OnNetworkCreateRawSessionFailed = new IGPRawSessionFailedEvent();
        public readonly IGPDataReceivedEvent OnDataReceived = new IGPDataReceivedEvent();
        public readonly IGPPeerActivityEvent OnPeerActivity = new IGPPeerActivityEvent();
        public IGPLogLevel LogLevel { get; set; }
        public bool DebugLogging
        {
            get => LogLevel != IGPLogLevel.Off;
            set => LogLevel = value ? IGPLogLevel.Debug : IGPLogLevel.Off;
        }
        public bool RetainIncomingPacketsForPolling { get; set; } = true;

        public IGPNetwork(IGPRuntimeManager manager)
        {
            _manager = manager;
            _reassembler = new IGPReliableMessageReassembler(_transportOptions);
        }

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

            lock (_lock)
            {
                _transportOptions = options;
                _reassembler.Reset();
                _reassembler = new IGPReliableMessageReassembler(_transportOptions);
            }
        }

        public void Update()
        {
            // Periodically broadcast local RTT to other players
            if (_manager.IsP2PDataPlaneReady &&
                Time.time - _lastRttBroadcastTime > RttBroadcastInterval)
            {
                var stats = _manager.AppRTTStats;
                if (stats != null && stats.SampleCount > 0)
                {
                    float avgRtt = stats.AvgRTT;
                    byte[] rttData = BitConverter.GetBytes(avgRtt);
                    // Broadcast to everyone in room (targetId = "")
                    SendData(new IGPPlayerID(_manager.PlayerId), new IGPPlayerID(""), rttData, (uint)rttData.Length, RttMessageType);
                }
                _lastRttBroadcastTime = Time.time;
            }

            CleanupExpiredReliableMessages();
        }

        public void Dispose()
        {
            _reassembler.Reset();
        }

        #region Internal Message Handling

        /// <summary>
        /// Handles incoming P2P data messages pushed from IGPRuntimeManager.
        /// </summary>
        public void HandleIncomingP2P(object? content)
        {
            if (content == null) return;

            try
            {
                var p2pMsg = ConvertToP2PMessagePayload(content);

                if (p2pMsg == null) return;

                CleanupExpiredReliableMessages();

                bool isReliableChunk = IsReliableChunk(p2pMsg);
                string transportChannel = ResolveTransportChannel(p2pMsg);
                uint effectiveMessageType = p2pMsg.reliableMessageType ?? p2pMsg.messageType;
                if (ShouldLog(IGPLogLevel.Debug))
                {
                    LogDebug(
                        "recv",
                        $"event=message-in localPlayerId={FormatLogValue(_manager.PlayerId)} " +
                        $"sourcePlayerId={FormatLogValue(p2pMsg.senderId)} targetPlayerId={FormatLogValue(p2pMsg.targetId)} " +
                        $"messageType={effectiveMessageType} channel={transportChannel} " +
                        BuildIncomingPayloadLogSummary(p2pMsg));
                }

                if (TryHandleArenaControlMessage(p2pMsg, transportChannel))
                {
                    return;
                }

                // Handle RTT Exchange messages (Special internal type)
                if (!isReliableChunk && p2pMsg.messageType == RttMessageType && p2pMsg.data != null)
                {
                    try
                    {
                        var dataBytes = Convert.FromBase64String(p2pMsg.data);
                        if (dataBytes.Length >= 4)
                        {
                            float remoteServerRtt = BitConverter.ToSingle(dataBytes, 0);
                            lock (_lock)
                            {
                                _remotePlayerServerRtt[p2pMsg.senderId] = remoteServerRtt;
                            }
                        }
                    }
                    catch { /* ignore malformed rtt */ }
                    return;
                }

                // Check Blocked
                if (_blockedTypes.Contains(effectiveMessageType)) return;

                var remoteId = p2pMsg.senderId;
                var localId = _manager.PlayerId;

                lock (_lock)
                {
                    if (!_sessions.ContainsKey(remoteId))
                    {
                        // New Session Request
                        _sessions[remoteId] = SessionStatus.Requested;
                        LogInfo("session", $"event=requested remote={FormatLogValue(remoteId)}");
                        
                        // Fire Event
                        var request = new CreateSessionRequest
                        {
                            local_peer = new IGPPlayerID(localId),
                            remote_peer = new IGPPlayerID(remoteId),
                            user_data = ""
                        };

                        OnCreateSessionRequest.Invoke(request);
                    }
                    
                    if (_sessions[remoteId] == SessionStatus.Connected || _sessions[remoteId] == SessionStatus.Requested)
                    {
                        byte[]? dataBytes = TryDecodeIncomingPayload(
                            remoteId,
                            p2pMsg,
                            out var resolvedMessageType,
                            out var acceptedPayload);
                        if (acceptedPayload)
                        {
                            OnPeerActivity.Invoke(new IGPPeerActivity
                            {
                                remote_peer = new IGPPlayerID(remoteId),
                                message_type = resolvedMessageType,
                                transport_channel = transportChannel,
                                is_reliable_chunk = isReliableChunk,
                                reliable_chunk_index = p2pMsg.reliableChunkIndex ?? -1,
                                reliable_chunk_count = p2pMsg.reliableChunkCount ?? -1
                            });
                        }

                        if (dataBytes != null && dataBytes.Length > 0)
                        {
                            var packet = new IncomingPacket
                            {
                                LocalPeer = new IGPPlayerID(localId),
                                RemotePeer = new IGPGamePeer(new IGPPlayerID(remoteId), new IGPGameID(_manager.CurrentRoomId)), // Assume same game for p2p_data
                                Data = dataBytes,
                                MessageType = resolvedMessageType,
                                Timestamp = DateTime.Now.Ticks
                            };
                            if (RetainIncomingPacketsForPolling)
                            {
                                _packetQueue.Enqueue(packet);
                                if (ShouldLog(IGPLogLevel.Debug))
                                {
                                    LogDebug(
                                        "queue",
                                        $"event=packet-queued remote={FormatLogValue(remoteId)} messageType={resolvedMessageType} " +
                                        $"channel={transportChannel} bytes={dataBytes.Length} pending={_packetQueue.Count}");
                                }
                            }
                            else
                            {
                                if (ShouldLog(IGPLogLevel.Debug))
                                {
                                    LogDebug(
                                        "recv",
                                        $"event=packet-delivered mode=event-only remote={FormatLogValue(remoteId)} " +
                                        $"messageType={resolvedMessageType} channel={transportChannel} bytes={dataBytes.Length}");
                                }
                            }

                            // Trigger Data Received Event
                            OnDataReceived.Invoke(new IGPDataReceived
                            {
                                remote_peer = new IGPPlayerID(remoteId),
                                data = dataBytes,
                                message_type = resolvedMessageType
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogError(
                    "recv",
                    $"event=parse-failed type=p2p_data error={FormatLogValue(ex.Message)}");
            }
        }

        #endregion

        #region IIGPNetwork Implementation

        public IGPNetworkResult AcceptSessionRequest(IGPPlayerID local_peer, IGPPlayerID remote_peer)
        {
            lock (_lock)
            {
                if (_sessions.TryGetValue(remote_peer.id, out var status))
                {
                    if (status == SessionStatus.Connected)
                    {
                        return IGPNetworkResult.kSuccess;
                    }

                    _sessions[remote_peer.id] = SessionStatus.Connected;
                    LogInfo("session", $"event=accepted remote={FormatLogValue(remote_peer.id)}");
                    return IGPNetworkResult.kSuccess;
                }
            }
            return IGPNetworkResult.kErrorInvalidState; // Or kErrorUnknown
        }

        public IGPNetworkResult CloseSession(IGPPlayerID local_peer, IGPPlayerID remote_peer)
        {
            lock (_lock)
            {
                if (_sessions.ContainsKey(remote_peer.id))
                {
                    _sessions.Remove(remote_peer.id);
                    LogInfo("session", $"event=closed remote={FormatLogValue(remote_peer.id)}");
                    // Notify remote? 
                    // Usually CloseSession just closes local. Remote detects via events.
                    // For now, we just remove local state.
                    return IGPNetworkResult.kSuccess;
                }
            }
            return IGPNetworkResult.kErrorUnknown;
        }

        public IGPNetworkResult GetSessionState(IGPPlayerID remote_peer, out IGPNetworkSessionState session_state)
        {
            session_state = new IGPNetworkSessionState();
            session_state.remote_peer = remote_peer;
            
            lock (_lock)
            {
                if (_sessions.TryGetValue(remote_peer.id, out var status))
                {
                    session_state.is_connected = (status == SessionStatus.Connected);
                    session_state.is_writable = session_state.is_connected;
                    return IGPNetworkResult.kSuccess;
                }
            }
            return IGPNetworkResult.kErrorUnknown;
        }

        public IGPNetworkResult SendData(IGPPlayerID local_peer, IGPPlayerID remote_peer, byte[] data_buf, uint data_len, uint message_type = 0)
        {
            // If remote_peer.id is empty, it means broadcast to room
            return InternalSendData(
                local_peer,
                remote_peer.id,
                data_buf,
                data_len,
                message_type,
                allowFragmentation: false,
                standardTransportChannel: IGPP2PTransportChannels.Data);
        }

        internal IGPNetworkResult SendUnreliableData(IGPPlayerID local_peer, IGPPlayerID remote_peer, byte[] data_buf, uint data_len, uint message_type = 0)
        {
            if (data_buf == null || data_len == 0) return IGPNetworkResult.kErrorInvalidParam;
            if (data_len > int.MaxValue) return IGPNetworkResult.kErrorInvalidParam;
            int dataLength = (int)data_len;
            if (dataLength > data_buf.Length) return IGPNetworkResult.kErrorInvalidParam;

            int payloadLimit = _manager.UnreliableUdpPayloadMaxBytes;
            if (dataLength > payloadLimit)
            {
                LogWarning(
                    "send-unreliable",
                    $"event=payload-too-large bytes={data_len} limit={payloadLimit}");
                return IGPNetworkResult.kErrorInvalidParam;
            }

            if (!_manager.IsUnreliableUdpConnected)
            {
                LogWarning(
                    "send-unreliable",
                    $"event=send-failed reason=udp-not-ready sourcePlayerId={FormatLogValue(local_peer.id)} targetPlayerId={FormatLogValue(remote_peer.id)}");
                return IGPNetworkResult.kErrorNetworkError;
            }

            string targetId = remote_peer.id ?? string.Empty;
            if (!string.IsNullOrEmpty(targetId))
            {
                lock (_lock)
                {
                    if (!_sessions.ContainsKey(targetId))
                    {
                        _sessions[targetId] = SessionStatus.Connected;
                        LogInfo("session", $"event=auto-created target={FormatLogValue(targetId)} transport=udp-unreliable");
                    }
                }
            }

            var payloadBytes = new byte[dataLength];
            Buffer.BlockCopy(data_buf, 0, payloadBytes, 0, dataLength);
            var payload = new P2PMessagePayload
            {
                senderId = local_peer.id,
                targetId = targetId,
                data = Convert.ToBase64String(payloadBytes),
                messageType = message_type,
                transportChannel = IGPP2PTransportChannels.Data,
                reliable = false
            };

            var msg = new Message
            {
                type = "p2p_data",
                roomId = _manager.CurrentRoomId,
                playerId = local_peer.id,
                targetPlayerId = targetId,
                content = payload,
                reliable = false
            };

            bool sent = _manager.TrySendUnreliableP2PMessage(msg);
            if (!sent)
            {
                LogWarning(
                    "send-unreliable",
                    $"event=send-failed reason=udp-send-failed sourcePlayerId={FormatLogValue(local_peer.id)} " +
                    $"targetPlayerId={FormatLogValue(targetId)} messageType={message_type} bytes={dataLength}");
                return IGPNetworkResult.kErrorNetworkError;
            }

            if (ShouldLog(IGPLogLevel.Debug))
            {
                LogDebug(
                    "send-unreliable",
                    $"event=message-out sourcePlayerId={FormatLogValue(local_peer.id)} targetPlayerId={FormatLogValue(targetId)} " +
                    $"messageType={message_type} channel={IGPP2PTransportChannels.Data} bytes={dataLength} " +
                    BuildOutgoingPayloadLogSummary(data_buf, dataLength, false, null, null, null));
            }

            return IGPNetworkResult.kSuccess;
        }

        public IGPNetworkResult SendReliableData(IGPPlayerID local_peer, IGPPlayerID remote_peer, byte[] data_buf, uint data_len, uint message_type = 0)
        {
            // If remote_peer.id is empty, it means broadcast to room
            return InternalSendData(
                local_peer,
                remote_peer.id,
                data_buf,
                data_len,
                message_type,
                allowFragmentation: true,
                standardTransportChannel: IGPP2PTransportChannels.Data);
        }

        public IGPNetworkResult SendControlData(IGPPlayerID local_peer, IGPPlayerID remote_peer, byte[] data_buf, uint data_len, uint message_type = 0)
        {
            return InternalSendData(
                local_peer,
                remote_peer.id,
                data_buf,
                data_len,
                message_type,
                allowFragmentation: false,
                standardTransportChannel: IGPP2PTransportChannels.Control);
        }

        private IGPNetworkResult InternalSendData(
            IGPPlayerID local_peer,
            string targetId,
            byte[] data_buf,
            uint data_len,
            uint message_type,
            bool allowFragmentation,
            string standardTransportChannel)
        {
            if (data_buf == null || data_len == 0) return IGPNetworkResult.kErrorInvalidParam;
            if (data_len > int.MaxValue) return IGPNetworkResult.kErrorInvalidParam;
            int dataLength = (int)data_len;
            if (dataLength > data_buf.Length) return IGPNetworkResult.kErrorInvalidParam;

            if (allowFragmentation)
            {
                if (dataLength > _transportOptions.ReliableMessageMaxBytes)
                {
                    LogWarning(
                        "send",
                        $"event=payload-too-large mode=reliable bytes={data_len} limit={_transportOptions.ReliableMessageMaxBytes}");
                    return IGPNetworkResult.kErrorInvalidParam;
                }
            }
            else if (dataLength > _transportOptions.KcpDataPlanePayloadMaxBytes)
            {
                LogWarning(
                    "send",
                    $"event=payload-too-large mode=direct bytes={data_len} limit={_transportOptions.KcpDataPlanePayloadMaxBytes}");
                return IGPNetworkResult.kErrorInvalidParam;
            }

            // Check session for point-to-point (non-broadcast)
            if (!string.IsNullOrEmpty(targetId))
            {
                lock (_lock)
                {
                    // Auto-create session on send if not exists (Initiator)
                    if (!_sessions.ContainsKey(targetId))
                    {
                        _sessions[targetId] = SessionStatus.Connected; // Initiator is implicitly connected
                        LogInfo("session", $"event=auto-created target={FormatLogValue(targetId)}");
                    }
                }
            }

            try
            {
                int payloadCount = 0;
                string transportChannel = string.Empty;
                bool? outgoingReliable = null;
                string? reliableMessageId = null;
                int? reliableChunkIndex = null;
                int? reliableChunkCount = null;
                foreach (var payload in CreateOutgoingPayloads(
                             local_peer.id,
                             targetId,
                             data_buf,
                             dataLength,
                             message_type,
                             allowFragmentation,
                             standardTransportChannel))
                {
                    payloadCount += 1;
                    if (string.IsNullOrEmpty(transportChannel))
                    {
                        transportChannel = ResolveTransportChannel(payload);
                    }
                    if (!outgoingReliable.HasValue)
                    {
                        outgoingReliable = payload.reliable;
                        reliableMessageId = payload.reliableMessageId;
                        reliableChunkIndex = payload.reliableChunkIndex;
                        reliableChunkCount = payload.reliableChunkCount;
                    }

                    var msg = new Message
                    {
                        type = "p2p_data",
                        roomId = _manager.CurrentRoomId,
                        playerId = local_peer.id,
                        targetPlayerId = targetId,
                        content = payload,
                        reliable = true
                    };

                    _manager.SendP2PMessage(msg);
                }
                if (ShouldLog(IGPLogLevel.Debug))
                {
                    LogDebug(
                        "send",
                        $"event=message-out localPlayerId={FormatLogValue(_manager.PlayerId)} " +
                        $"sourcePlayerId={FormatLogValue(local_peer.id)} targetPlayerId={FormatLogValue(targetId)} " +
                        $"messageType={message_type} channel={transportChannel} bytes={dataLength} " +
                        $"fragmented={allowFragmentation && payloadCount > 1} payloads={payloadCount} " +
                        BuildOutgoingPayloadLogSummary(
                            data_buf,
                            dataLength,
                            outgoingReliable ?? true,
                            reliableMessageId,
                            reliableChunkIndex,
                            reliableChunkCount));
                }

                return IGPNetworkResult.kSuccess;
            }
            catch (Exception ex)
            {
                LogWarning(
                    "send",
                    $"event=send-failed localPlayerId={FormatLogValue(_manager.PlayerId)} " +
                    $"sourcePlayerId={FormatLogValue(local_peer.id)} targetPlayerId={FormatLogValue(targetId)} " +
                    $"messageType={message_type} bytes={dataLength} error={FormatLogValue(ex.Message)}");
                return IGPNetworkResult.kErrorNetworkError;
            }
        }

        public bool IsDataReady(out IGPPlayerID local_peer, out uint data_len, out uint message_type)
        {
            if (_packetQueue.TryPeek(out var packet))
            {
                local_peer = packet.LocalPeer;
                data_len = (uint)packet.Data.Length;
                message_type = packet.MessageType;
                return true;
            }

            local_peer = default;
            data_len = 0;
            message_type = 0;
            return false;
        }

        public IGPNetworkResult ReadData(IGPPlayerID local_peer, out IGPPlayerID remote_peer, byte[] data_buf, uint data_len, uint message_type = 0)
        {
            remote_peer = default;

            if (_packetQueue.TryDequeue(out var packet))
            {
                remote_peer = packet.RemotePeer.player_id;

                if (data_buf.Length < packet.Data.Length)
                {
                    // Buffer too small, data loss or partial read?
                    // We copy what fits.
                    Array.Copy(packet.Data, data_buf, data_buf.Length);
                    return IGPNetworkResult.kErrorInvalidParam; // Or partial success?
                }

                Array.Copy(packet.Data, data_buf, packet.Data.Length);
                return IGPNetworkResult.kSuccess;
            }

            return IGPNetworkResult.kErrorUnknown;
        }

        public IGPNetworkResult AcceptRawSessionRequest(IGPPlayerID local_peer, IGPGamePeer remote_game_peer)
        {
            return AcceptSessionRequest(local_peer, remote_game_peer.player_id);
        }

        public IGPNetworkResult ReadRawData(IGPPlayerID local_peer, out IGPGamePeer remote_game_peer, byte[] data_buf, uint data_len, uint message_type = 0)
        {
            if (_packetQueue.TryDequeue(out var packet))
            {
                remote_game_peer = packet.RemotePeer;

                if (data_buf.Length < packet.Data.Length)
                {
                    Array.Copy(packet.Data, data_buf, data_buf.Length);
                    return IGPNetworkResult.kErrorInvalidParam;
                }

                Array.Copy(packet.Data, data_buf, packet.Data.Length);
                return IGPNetworkResult.kSuccess;
            }

            remote_game_peer = default;
            return IGPNetworkResult.kErrorUnknown;
        }

        public IGPNetworkResult ResolveHostname(string domain, out List<string> ip_list)
        {
            ip_list = new List<string>();
            try
            {
                var ips = Dns.GetHostAddresses(domain);
                foreach (var ip in ips)
                {
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        ip_list.Add(ip.ToString());
                    }
                }
                return IGPNetworkResult.kSuccess;
            }
            catch
            {
                return IGPNetworkResult.kErrorUnknown;
            }
        }

        public IGPNetworkResult BlockMessageType(IGPPlayerID local_peer, uint message_type)
        {
            lock (_lock)
            {
                _blockedTypes.Add(message_type);
            }
            return IGPNetworkResult.kSuccess;
        }

        public IGPNetworkResult UnblockMessageType(IGPPlayerID local_peer, uint message_type)
        {
            lock (_lock)
            {
                _blockedTypes.Remove(message_type);
            }
            return IGPNetworkResult.kSuccess;
        }

        public uint GetPeerLatency(IGPPlayerID remote_peer)
        {
            float myRtt = _manager.AppRTTStats?.AvgRTT ?? 0f;
            float hisRtt = 0f;
            
            lock (_lock)
            {
                _remotePlayerServerRtt.TryGetValue(remote_peer.id, out hisRtt);
            }

            // Estimate E2E latency as sum of both RTTs
            // If hisRtt is 0, we fallback to just my RTT (better than nothing)
            return (uint)((myRtt + hisRtt) * 1000);
        }

        /// <summary>
        /// Gets list of all player IDs we have active sessions or RTT data for.
        /// </summary>
        public List<string> GetConnectedPeerIds()
        {
            var peers = new HashSet<string>();
            lock (_lock)
            {
                foreach (var id in _sessions.Keys) peers.Add(id);
                foreach (var id in _remotePlayerServerRtt.Keys) peers.Add(id);
            }
            return new List<string>(peers);
        }

        /// <summary>
        /// Resets all session states and clears packet queues.
        /// </summary>
        public void ResetSessionState()
        {
            lock (_lock)
            {
                _sessions.Clear();
                _remotePlayerServerRtt.Clear();
                _reassembler.Reset();
                while (_packetQueue.TryDequeue(out _)) { }
            }
            LogInfo("session", "event=reset");
        }

        #endregion

        private IEnumerable<P2PMessagePayload> CreateOutgoingPayloads(
            string senderId,
            string targetId,
            byte[] dataBuffer,
            int dataLength,
            uint messageType,
            bool allowFragmentation,
            string standardTransportChannel)
        {
            var payloadBytes = new byte[dataLength];
            Buffer.BlockCopy(dataBuffer, 0, payloadBytes, 0, dataLength);

            if (!allowFragmentation || dataLength <= _transportOptions.KcpDataPlanePayloadMaxBytes)
            {
                yield return CreateStandardPayload(senderId, targetId, payloadBytes, messageType, standardTransportChannel);
                yield break;
            }

            string messageId = Guid.NewGuid().ToString("N");
            var chunks = IGPReliableMessageFragmenter.Fragment(
                payloadBytes,
                messageType,
                targetId,
                _transportOptions,
                messageId);

            foreach (var chunk in chunks)
            {
                yield return new P2PMessagePayload
                {
                    senderId = senderId,
                    targetId = targetId,
                    data = Convert.ToBase64String(chunk.Payload),
                    messageType = messageType,
                    transportChannel = IGPP2PTransportChannels.Data,
                    reliable = true,
                    reliableMessageId = chunk.MessageId,
                    reliableChunkIndex = chunk.ChunkIndex,
                    reliableChunkCount = chunk.ChunkCount,
                    reliableTotalBytes = chunk.TotalBytes,
                    reliableMessageType = chunk.MessageType
                };
            }
        }

        private static P2PMessagePayload CreateStandardPayload(
            string senderId,
            string targetId,
            byte[] payloadBytes,
            uint messageType,
            string transportChannel)
        {
            return new P2PMessagePayload
            {
                senderId = senderId,
                targetId = targetId,
                data = Convert.ToBase64String(payloadBytes),
                messageType = messageType,
                transportChannel = transportChannel,
                reliable = true
            };
        }

        private static P2PMessagePayload? ConvertToP2PMessagePayload(object content)
        {
            if (content is JObject jobj)
            {
                return jobj.ToObject<P2PMessagePayload>();
            }

            return Newtonsoft.Json.JsonConvert.DeserializeObject<P2PMessagePayload>(
                Newtonsoft.Json.JsonConvert.SerializeObject(content));
        }

        private static bool IsReliableChunk(P2PMessagePayload payload)
        {
            return !string.IsNullOrWhiteSpace(payload.reliableMessageId) &&
                   payload.reliableChunkIndex.HasValue &&
                   payload.reliableChunkCount.HasValue &&
                   payload.reliableTotalBytes.HasValue &&
                   payload.reliableMessageType.HasValue;
        }

        private static string ResolveTransportChannel(P2PMessagePayload payload)
        {
            return IGPP2PTransportChannels.Resolve(payload.transportChannel, IsReliableChunk(payload));
        }

        private bool TryHandleArenaControlMessage(P2PMessagePayload payload, string transportChannel)
        {
            if (!string.Equals(transportChannel, IGPP2PTransportChannels.Control, StringComparison.Ordinal) ||
                string.IsNullOrWhiteSpace(payload.controlType))
            {
                return false;
            }

            switch (payload.controlType)
            {
                case ArenaControlTypeReliableMessageAborted:
                    string abortConnectionId = !string.IsNullOrWhiteSpace(payload.sourceSenderId)
                        ? payload.sourceSenderId
                        : payload.senderId;
                    bool reassemblyCleared = false;
                    if (!string.IsNullOrWhiteSpace(abortConnectionId) &&
                        !string.IsNullOrWhiteSpace(payload.reliableMessageId))
                    {
                        lock (_lock)
                        {
                            reassemblyCleared = _reassembler.Abort(abortConnectionId, payload.reliableMessageId!);
                        }
                    }

                    LogWarning(
                        "control",
                        "event=reliable-message-aborted " +
                        $"reason={FormatLogValue(payload.reason)} sourcePlayerId={FormatLogValue(abortConnectionId)} " +
                        $"failedTargetPlayerId={FormatLogValue(payload.failedTargetId)} messageId={FormatLogValue(payload.reliableMessageId)} " +
                        $"sequence={payload.transportSequence?.ToString() ?? "-"} reassemblyCleared={reassemblyCleared}");
                    return true;
                default:
                    LogWarning(
                        "control",
                        $"event=ignored type={FormatLogValue(payload.controlType)} reason={FormatLogValue(payload.reason)} " +
                        $"sourcePlayerId={FormatLogValue(payload.sourceSenderId)} failedTargetPlayerId={FormatLogValue(payload.failedTargetId)}");
                    return true;
            }
        }

        private byte[]? TryDecodeIncomingPayload(
            string remoteId,
            P2PMessagePayload payload,
            out uint resolvedMessageType,
            out bool acceptedPayload)
        {
            resolvedMessageType = payload.reliableMessageType ?? payload.messageType;
            acceptedPayload = false;
            if (string.IsNullOrWhiteSpace(payload.data))
            {
                return null;
            }

            if (!IsReliableChunk(payload))
            {
                resolvedMessageType = payload.messageType;
                var decoded = Convert.FromBase64String(payload.data);
                acceptedPayload = decoded.Length > 0;
                return decoded;
            }

            try
            {
                var chunk = new IGPReliableChunk(
                    payload.reliableMessageId!,
                    payload.reliableChunkIndex!.Value,
                    payload.reliableChunkCount!.Value,
                    payload.reliableTotalBytes!.Value,
                    payload.reliableMessageType!.Value,
                    payload.targetId ?? string.Empty,
                    Convert.FromBase64String(payload.data));

                var completed = _reassembler.AddChunk(remoteId, chunk, DateTimeOffset.UtcNow);
                acceptedPayload = true;
                if (completed == null)
                {
                    if (ShouldLog(IGPLogLevel.Debug))
                    {
                        LogDebug(
                            "reassembly",
                            $"event=chunk-in remote={FormatLogValue(remoteId)} messageId={FormatLogValue(chunk.MessageId)} " +
                            $"chunk={chunk.ChunkIndex + 1}/{chunk.ChunkCount}");
                    }

                    return null;
                }

                resolvedMessageType = completed.MessageType;
                uint completedMessageType = resolvedMessageType;
                if (ShouldLog(IGPLogLevel.Debug))
                {
                    LogDebug(
                        "reassembly",
                        $"event=complete remote={FormatLogValue(remoteId)} messageId={FormatLogValue(chunk.MessageId)} " +
                        $"messageType={completedMessageType} bytes={completed.Payload.Length}");
                }

                return completed.Payload;
            }
            catch (Exception ex)
            {
                LogWarning("reassembly", $"event=failed error={FormatLogValue(ex.Message)}");
                return null;
            }
        }

        private static string BuildIncomingPayloadLogSummary(P2PMessagePayload payload)
        {
            int base64Bytes = payload.data?.Length ?? 0;
            string payloadBytes = "-";
            string checksum = "-";

            if (!string.IsNullOrWhiteSpace(payload.data))
            {
                try
                {
                    var decoded = Convert.FromBase64String(payload.data);
                    payloadBytes = decoded.Length.ToString();
                    checksum = FormatChecksum(decoded, decoded.Length);
                }
                catch
                {
                    payloadBytes = "invalid-base64";
                }
            }

            return
                $"reliable={payload.reliable} payloadBytes={payloadBytes} base64Bytes={base64Bytes} checksum={checksum} " +
                $"reliableMessageId={FormatLogValue(payload.reliableMessageId)} " +
                $"chunkIndex={FormatOptionalInt(payload.reliableChunkIndex)} chunkCount={FormatOptionalInt(payload.reliableChunkCount)} " +
                $"reliableTotalBytes={FormatOptionalInt(payload.reliableTotalBytes)}";
        }

        private static string BuildOutgoingPayloadLogSummary(
            byte[] payload,
            int payloadBytes,
            bool reliable,
            string? reliableMessageId,
            int? reliableChunkIndex,
            int? reliableChunkCount)
        {
            return
                $"reliable={reliable} payloadBytes={payloadBytes} checksum={FormatChecksum(payload, payloadBytes)} " +
                $"reliableMessageId={FormatLogValue(reliableMessageId)} " +
                $"chunkIndex={FormatOptionalInt(reliableChunkIndex)} chunkCount={FormatOptionalInt(reliableChunkCount)}";
        }

        private static string FormatChecksum(byte[]? payload, int count)
        {
            return $"{Fnv1a32(payload, count):X8}";
        }

        private static uint Fnv1a32(byte[]? data, int count)
        {
            unchecked
            {
                const uint offset = 2166136261;
                const uint prime = 16777619;
                uint hash = offset;
                int safeCount = data == null ? 0 : Math.Min(count, data.Length);
                for (int i = 0; i < safeCount; i += 1)
                {
                    hash ^= data![i];
                    hash *= prime;
                }

                return hash;
            }
        }

        private static string FormatOptionalInt(int? value)
        {
            return value.HasValue ? value.Value.ToString() : "-";
        }

        private void CleanupExpiredReliableMessages()
        {
            var expired = _reassembler.RemoveExpired(DateTimeOffset.UtcNow);
            foreach (var item in expired)
            {
                LogWarning(
                    "reassembly",
                    $"event=timeout connection={FormatLogValue(item.ConnectionId)} messageId={FormatLogValue(item.MessageId)}");
            }
        }

        private void LogDebug(string path, string message)
        {
            IGPLog.Debug(LogScope, path, message);
        }

        private void LogInfo(string path, string message)
        {
            IGPLog.Info(LogScope, path, message);
        }

        private void LogWarning(string path, string message)
        {
            IGPLog.Warning(LogScope, path, message);
        }

        private void LogError(string path, string message)
        {
            IGPLog.Error(LogScope, path, message);
        }

        private bool ShouldLog(IGPLogLevel messageLevel)
        {
            return IGPLog.ShouldLog(messageLevel);
        }

        private static string FormatLogValue(string? value)
        {
            return IGPLog.FormatValue(value);
        }
    }
}
