#nullable enable
using IGP.UnitySDK.Models;
using IGP.UnitySDK;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using IGP.UnitySDK.Abstractions;
using IGP.Multiplayer.Models;
using IGP.Multiplayer.Core;
using IGP.Multiplayer.Network;
using IGP.Multiplayer.Protocol;

namespace IGP.Multiplayer
{
    public partial class IGPMultiplayerRuntime
    {
        private IGPHostSessionDataPlaneDescriptor? pendingUnreliableDescriptor;
        private void ApplyKcpRuntimeSettings()
        {
            if (KcpClient != null)
            {
                KcpClient.MaxDatagramsPerTick = KcpMaxDatagramsPerTick;
                KcpClient.SendWindowSize = KcpSendWindowSize;
                KcpClient.ReceiveWindowSize = KcpReceiveWindowSize;
            }
        }

        internal async Task ConnectDataPlaneAsync(CancellationToken cancellationToken = default)
        {
            EnsureExtensionInitialized();

            LogSdkInfo(
                "realtime",
                $"event=connect mode={currentDataPlaneMode} " +
                $"transportAttached={reliableTransport != null} hostedSession={IsHostedSessionAttached} " +
                $"realtimeState={realtimeConnectionState} roomId={currentRoomId}");

            if (IsReliableConnected && currentDataPlaneMode != IGPDataPlaneMode.None && reliableTransport != null)
            {
                LogSdkDebug("realtime", "event=connect-skipped reason=already-ready");
                return;
            }

            if (!IsHostedSessionAttached)
            {
                throw new IGPSDKException(
                    "Room context is required before the realtime data plane can be connected",
                    IGPMultiplayerErrorCodes.ERR_ROOM_CONTEXT_REQUIRED);
            }

            if (string.IsNullOrWhiteSpace(currentRoomId))
            {
                throw new IGPSDKException("Room id is required before hosted data plane can be requested");
            }

            if (string.IsNullOrWhiteSpace(playerId))
            {
                throw new IGPSDKException("Player id is required before hosted data plane can be requested");
            }

            if (reliableTransport != null || currentDataPlaneMode != IGPDataPlaneMode.None)
            {
                ResetHostedDataPlaneState("reconnecting");
            }

            CancellationToken effectiveCancellationToken = cancellationToken.CanBeCanceled
                ? cancellationToken
                : RuntimeCancellationToken;
            await RequestAndAttachHostedDataPlaneAsync(
                currentRoomId,
                playerId,
                realtimeRoomGeneration,
                effectiveCancellationToken);
        }


        /// <summary>
        /// Internal helper to send P2P messages using the best available transport.
        /// </summary>
        internal bool TrySendP2PMessage(Message msg)
        {
            return TrySendP2PMessages(new[] { msg }, isControl: false) == IGPTransportSendResult.Accepted;
        }

        internal IGPTransportSendResult TrySendP2PMessages(IReadOnlyList<Message> messages, bool isControl)
        {
            var transport = reliableTransport;
            if (IsReliableConnected && transport != null)
            {
                return transport.TrySendMessages(messages, isControl);
            }

            if (realtimeConnectionState == IGPRealtimeConnectionState.RequestingDescriptor ||
                realtimeConnectionState == IGPRealtimeConnectionState.Connecting)
            {
                return IGPTransportSendResult.WouldBlock;
            }

            if (realtimeConnectionState == IGPRealtimeConnectionState.Ready)
            {
                _ = MarkHostedDataPlaneInterrupted(
                    string.IsNullOrWhiteSpace(currentHostedDataPlaneError)
                        ? "Connection interrupted: direct data connection is unavailable"
                        : currentHostedDataPlaneError);
            }

            return IGPTransportSendResult.Unavailable;
        }

        internal bool TrySendUnreliableP2PMessage(Message msg)
        {
            var transport = unreliableTransport;
            if (transport == null || !transport.IsConnected)
            {
                return false;
            }

            return transport.TrySendMessages(new[] { msg }, isControl: false) == IGPTransportSendResult.Accepted;
        }

        internal bool IsP2PDataPlaneReady => IsReliableConnected && reliableTransport != null;
        internal bool IsP2PDataPlaneWritable => IsP2PDataPlaneReady && reliableTransport?.IsWritable == true;

        internal void SendP2PMessage(Message msg)
        {
            if (!TrySendP2PMessage(msg))
            {
                throw new IGPSDKException("Direct data connection failed to queue the message");
            }
        }


        private IGPSDKException MarkHostedDataPlaneInterrupted(
            string errorMessage,
            string errorCode = "HOSTED_DATA_PLANE_INTERRUPTED",
            IGPRealtimeTransport? transport = null,
            object? details = null)
        {
            IGPRealtimeTransport? interruptedTransport = transport ?? CurrentReliableTransport;
            if (realtimeConnectionState == IGPRealtimeConnectionState.Failed &&
                reliableTransport == null &&
                currentDataPlaneMode == IGPDataPlaneMode.None)
            {
                return new IGPSDKException(
                    string.IsNullOrWhiteSpace(currentHostedDataPlaneError)
                        ? errorMessage
                        : currentHostedDataPlaneError);
            }

            bool failureAlreadyReported = realtimeConnectionState == IGPRealtimeConnectionState.Failed;
            bool wasReady = realtimeConnectionState == IGPRealtimeConnectionState.Ready;
            InvalidateRealtimeDataPlane(
                "interrupted",
                errorMessage,
                IGPRealtimeConnectionState.Failed);

            if (!isDestroyed && !failureAlreadyReported && !runtimeCreationInProgress)
            {
                LogSdkError(
                    "hosted-data-plane",
                    $"event=interrupted code={FormatNetworkLogValue(errorCode)} transport={interruptedTransport?.ToString() ?? "-"} " +
                    $"error={FormatNetworkLogValue(errorMessage)}");
                PublishMultiplayerError(
                    errorCode,
                    errorMessage,
                    "hosted-data-plane",
                    interruptedTransport,
                    true,
                    details);
            }

            if (!isDestroyed && !failureAlreadyReported && !runtimeCreationInProgress && wasReady)
            {
                pendingConnectionRecoveryNotification = true;
                ConnectionInterrupted?.Invoke(new IGPMultiplayerConnectionChangedEvent(
                    IGPRealtimeConnectionState.Ready,
                    IGPRealtimeConnectionState.Failed,
                    false,
                    errorMessage));
            }

            return new IGPSDKException(errorMessage);
        }

        private void HandleReliableConnectionStateChanged(bool connected, IGPRealtimeTransport transport)
        {
            if (connected)
            {
                if (reliableTransport == null || currentDataPlaneMode == IGPDataPlaneMode.None)
                {
                    return;
                }

                if (transport == IGPRealtimeTransport.Tcp && TcpClient != null)
                {
                    LogSdkInfo("tcp", "connection", $"event=ready {TcpClient.DiagnosticsSummary}");
                }

                currentHostedDataPlaneStatus = "connected";
                currentHostedDataPlaneError = string.Empty;
                if (pendingUnreliableDescriptor != null)
                {
                    IGPHostSessionDataPlaneDescriptor descriptor = pendingUnreliableDescriptor;
                    pendingUnreliableDescriptor = null;
                    try
                    {
                        AttachUnreliableUdpLane(
                            descriptor,
                            currentHostedDataPlaneRoomId,
                            currentHostedDataPlanePlayerId,
                            reliableTransport?.NegotiatedEnvelopeVersion == IGPDataPlaneEnvelopeCodec.Version);
                    }
                    catch (Exception ex)
                    {
                        unreliableTransport?.Disconnect();
                        DetachNetworkTransportEvents(unreliableTransport);
                        unreliableTransport = null;
                        LogSdkWarning(
                            "udp-unreliable",
                            "connect",
                            $"event=unavailable error={FormatNetworkLogValue(ex.Message)}");
                        if (!runtimeCreationInProgress)
                        {
                            PublishMultiplayerError(
                                "UDP_DATA_PLANE_CONNECT_FAILED",
                                ex.Message,
                                "transport",
                                IGPRealtimeTransport.Udp,
                                false,
                                ex);
                        }
                    }
                }
                UpdateRealtimeConnectionState(IGPRealtimeConnectionState.Ready);
                return;
            }

            if (suppressReliableDisconnectHandling)
            {
                return;
            }

            if (reliableTransport != null && currentDataPlaneMode != IGPDataPlaneMode.None)
            {
                _ = MarkHostedDataPlaneInterrupted($"{transport} connection dropped", transport: transport);
            }
        }

        private async Task RequestAndAttachHostedDataPlaneAsync(
            string roomId,
            string attachPlayerId,
            int roomGeneration,
            CancellationToken cancellationToken)
        {
            var session = EnsureHostedSession();
            Exception? lastError = null;
            bool forceKcpFallback = false;
            string attemptedTcpToken = string.Empty;
            IGPReliableTransportPreference preference = ReliableTransportPreference;
            int attemptLimit = preference == IGPReliableTransportPreference.PreferTcp ? 2 : 1;
            for (var attempt = 1; attempt <= attemptLimit; attempt += 1)
            {
                IGPRealtimeTransport? attemptTransport = null;
                if (roomGeneration != realtimeRoomGeneration)
                {
                    throw new IGPSDKException("Realtime data-plane connection was superseded by a room change");
                }

                var attemptId = ++hostedDataPlaneAttemptId;
                UpdateRealtimeConnectionState(IGPRealtimeConnectionState.RequestingDescriptor);
                currentHostedDataPlaneStatus = attempt == 1
                    ? "reliable-token-requesting"
                    : $"kcp-fallback-token-requesting:{attempt}";
                currentHostedDataPlaneError = string.Empty;
                LogHostedDataPlaneEvent("request-token", attemptId);

                try
                {
                    var dataPlaneResponse = await session.RequestDataPlaneAsync(cancellationToken);
                    ThrowIfDataPlaneConnectionIsStale(roomGeneration);
                    if (dataPlaneResponse.DataPlane == null)
                    {
                        throw new IGPSDKException("Missing hosted data plane descriptor");
                    }
                    EnsureRuntimeResources();
                    if (forceKcpFallback &&
                        !string.IsNullOrWhiteSpace(attemptedTcpToken) &&
                        string.Equals(dataPlaneResponse.DataPlane.Token, attemptedTcpToken, StringComparison.Ordinal))
                    {
                        throw new IGPSDKException(
                            "KCP fallback descriptor reused the attempted TCP token",
                            IGPMultiplayerErrorCodes.ERR_TCP_DESCRIPTOR_INVALID);
                    }
                    IGPRealtimeTransport selectedTransport = SelectReliableTransport(
                        dataPlaneResponse.DataPlane,
                        preference,
                        forceKcpFallback);
                    attemptTransport = selectedTransport;
                    tcpFallbackEligible = preference == IGPReliableTransportPreference.PreferTcp &&
                                          !forceKcpFallback &&
                                          selectedTransport == IGPRealtimeTransport.Tcp;
                    currentHostedDataPlaneStatus = $"descriptor-received:{attempt}";
                    await AttachHostedDataPlaneAsync(
                        roomId,
                        attachPlayerId,
                        dataPlaneResponse,
                        selectedTransport,
                        attemptId,
                        roomGeneration);
                    if (selectedTransport == IGPRealtimeTransport.Tcp)
                    {
                        attemptedTcpToken = dataPlaneResponse.DataPlane.Token;
                    }
                    await WaitForHostedDataPlaneReadyAsync(
                        HostedDataPlaneReadyTimeout,
                        attemptId,
                        roomGeneration,
                        cancellationToken);
                    tcpFallbackEligible = false;
                    LogHostedDataPlaneEvent("handshake-ack", attemptId);
                    return;
                }
                catch (StaleHostedDataPlaneAttemptException)
                {
                    LogHostedDataPlaneEvent(
                        "stale-attempt-discarded",
                        attemptId,
                        level: IGPLogLevel.Debug);
                    throw new IGPSDKException("Realtime data-plane connection was superseded by a room change");
                }
                catch (OperationCanceledException)
                {
                    ThrowIfDataPlaneConnectionIsStale(roomGeneration);
                    const string error = "Hosted data plane request was canceled";
                    currentHostedDataPlaneStatus = "failed:canceled";
                    currentHostedDataPlaneError = error;
                    UpdateRealtimeConnectionState(IGPRealtimeConnectionState.Failed);
                    if (runtimeCreationInProgress)
                    {
                        LogSdkWarning("hosted-data-plane", $"event=request-canceled error={FormatNetworkLogValue(error)}");
                    }
                    else
                    {
                        LogSdkError("hosted-data-plane", $"event=request-canceled error={FormatNetworkLogValue(error)}");
                        PublishMultiplayerError(
                            "HOSTED_DATA_PLANE_CANCELED",
                            error,
                            "hosted-data-plane",
                            CurrentReliableTransport,
                            true);
                    }
                    throw;
                }
                catch (Exception ex)
                {
                    ThrowIfDataPlaneConnectionIsStale(roomGeneration);
                    lastError = ex;
                    bool tcpFallback = preference == IGPReliableTransportPreference.PreferTcp &&
                                       !forceKcpFallback &&
                                       attemptTransport == IGPRealtimeTransport.Tcp;
                    bool faultAlreadyPublished = lastPublishedTransportFaultAttemptId == attemptId;
                    string errorCode = ResolveHostedDataPlaneErrorCode(ex, attemptTransport);
                    ResetHostedDataPlaneState($"retrying:{attempt}", ex.Message);
                    if (tcpFallback)
                    {
                        if (!faultAlreadyPublished && !runtimeCreationInProgress)
                        {
                            PublishMultiplayerError(
                                errorCode,
                                ex.Message,
                                "hosted-data-plane",
                                attemptTransport,
                                false,
                                ex);
                        }
                        forceKcpFallback = true;
                        LogHostedDataPlaneEvent(
                            "fallback-scheduled",
                            attemptId,
                            ClassifyHostedDataPlaneException(ex),
                            ex.Message,
                            IGPLogLevel.Warning);
                        continue;
                    }

                    currentHostedDataPlaneStatus = "failed";
                    currentHostedDataPlaneError = ex.Message;
                    UpdateRealtimeConnectionState(IGPRealtimeConnectionState.Failed);
                    if (!faultAlreadyPublished && !runtimeCreationInProgress)
                    {
                        PublishMultiplayerError(
                            errorCode,
                            ex.Message,
                            "hosted-data-plane",
                            attemptTransport,
                            true,
                            ex);
                    }
                    if (runtimeCreationInProgress)
                    {
                        LogSdkWarning(
                            "hosted-data-plane",
                            $"event=attach-failed class={ClassifyHostedDataPlaneException(ex)} error={FormatNetworkLogValue(ex.Message)}");
                    }
                    else
                    {
                        LogSdkError(
                            "hosted-data-plane",
                            $"event=attach-failed class={ClassifyHostedDataPlaneException(ex)} error={FormatNetworkLogValue(ex.Message)}");
                    }
                    throw;
                }
            }

            throw lastError ?? new IGPSDKException("Hosted data plane attach failed");
        }

        private void ThrowIfDataPlaneConnectionIsStale(int roomGeneration)
        {
            if (roomGeneration != realtimeRoomGeneration)
            {
                throw new StaleHostedDataPlaneAttemptException();
            }
        }

        private string ClassifyHostedDataPlaneException(Exception ex)
        {
            if (ex is TimeoutException)
            {
                return "ReliableHandshakeTimeout";
            }

            if (ex is SocketException)
            {
                return "ReliableSocketInitFailed";
            }

            if (ex is IGPSDKException sdkException)
            {
                if (string.Equals(sdkException.ErrorCode, IGPMultiplayerErrorCodes.ERR_UNAUTHORIZED, StringComparison.OrdinalIgnoreCase) ||
                    string.Equals(sdkException.ErrorCode, IGPMultiplayerErrorCodes.ERR_FORBIDDEN, StringComparison.OrdinalIgnoreCase))
                {
                    return "ReliableTokenInvalid";
                }

                if (!string.IsNullOrWhiteSpace(sdkException.ErrorCode)) return sdkException.ErrorCode;
            }

            return "HostedDataPlaneAttachFailed";
        }

        private static string ResolveHostedDataPlaneErrorCode(
            Exception exception,
            IGPRealtimeTransport? transport)
        {
            if (exception is IGPSDKException sdkException &&
                !string.IsNullOrWhiteSpace(sdkException.ErrorCode))
            {
                return sdkException.ErrorCode!;
            }
            if (transport == IGPRealtimeTransport.Tcp)
            {
                if (exception is TimeoutException)
                {
                    return IGPMultiplayerErrorCodes.ERR_TCP_HANDSHAKE_TIMEOUT;
                }
                if (exception is SocketException)
                {
                    return IGPMultiplayerErrorCodes.ERR_TCP_CONNECT_FAILED;
                }
            }
            return "HOSTED_DATA_PLANE_ATTACH_FAILED";
        }

        private Task AttachHostedDataPlaneAsync(
            string roomId,
            string attachPlayerId,
            IGPHostSessionDataPlaneResponse dataPlaneResponse,
            IGPRealtimeTransport selectedTransport,
            int attemptId,
            int roomGeneration)
        {
            ThrowIfDataPlaneConnectionIsStale(roomGeneration);
            if (dataPlaneResponse.DataPlane == null)
            {
                currentHostedDataPlaneStatus = "failed:missing-descriptor";
                throw new IGPSDKException("Missing hosted data plane descriptor");
            }

            ApplyNegotiatedTransportOptions(
                dataPlaneResponse.DataPlane.reliableMessageMaxBytes,
                dataPlaneResponse.DataPlane.reliableChunkMaxBytes,
                dataPlaneResponse.DataPlane.kcpDataPlanePayloadMaxBytes,
                dataPlaneResponse.DataPlane.kcpFrameMaxBytes,
                selectedTransport,
                dataPlaneResponse.DataPlane.TcpFrameMaxBytes);

            switch (selectedTransport)
            {
                case IGPRealtimeTransport.Kcp:
                    AttachDirectKcpDataPlane(roomId, attachPlayerId, dataPlaneResponse.DataPlane, attemptId);
                    return Task.CompletedTask;
                case IGPRealtimeTransport.Tcp:
                    AttachDirectTcpDataPlane(roomId, attachPlayerId, dataPlaneResponse.DataPlane, attemptId);
                    return Task.CompletedTask;
                default:
                    currentHostedDataPlaneStatus = $"failed:unsupported-transport:{selectedTransport}";
                    throw new IGPSDKException($"Unsupported hosted reliable transport: {selectedTransport}");
            }
        }

        private IGPRealtimeTransport SelectReliableTransport(
            IGPHostSessionDataPlaneDescriptor descriptor,
            IGPReliableTransportPreference preference,
            bool forceKcpFallback)
        {
            if (preference != IGPReliableTransportPreference.KcpOnly &&
                preference != IGPReliableTransportPreference.TcpOnly &&
                preference != IGPReliableTransportPreference.PreferTcp)
            {
                throw new IGPSDKException("Unsupported reliable transport preference");
            }

            if (!forceKcpFallback && preference != IGPReliableTransportPreference.KcpOnly)
            {
                bool tcpAvailable = !string.IsNullOrWhiteSpace(descriptor.TcpHost) &&
                                    descriptor.TcpPort > 0 &&
                                    descriptor.TcpFrameMaxBytes.HasValue;
                if (tcpAvailable) return IGPRealtimeTransport.Tcp;
                if (preference == IGPReliableTransportPreference.TcpOnly)
                {
                    throw new IGPSDKException(
                        "TCP data-plane endpoint is unavailable",
                        IGPMultiplayerErrorCodes.ERR_TCP_DATA_PLANE_UNAVAILABLE);
                }
            }

            if (descriptor.Mode != IGPHostedDataPlaneMode.DirectKcp ||
                string.IsNullOrWhiteSpace(descriptor.Host) ||
                descriptor.Port == 0 ||
                string.IsNullOrWhiteSpace(descriptor.Token))
            {
                throw new IGPSDKException("Invalid hosted KCP descriptor");
            }
            return IGPRealtimeTransport.Kcp;
        }

        private void AttachDirectKcpDataPlane(
            string roomId,
            string attachPlayerId,
            IGPHostSessionDataPlaneDescriptor descriptor,
            int attemptId)
        {
            if (KcpClient == null)
            {
                throw new IGPSDKException("IGP KCP client is not initialized");
            }

            reliableTransport?.Disconnect();
            DetachNetworkTransportEvents(reliableTransport);
            unreliableTransport?.Disconnect();
            DetachNetworkTransportEvents(unreliableTransport);
            reliableTransport = IGPNetworkTransportFactory.CreateKcp(KcpClient);
            AttachNetworkTransportEvents(reliableTransport);
            reliableTransport.Connect(new IGPNetworkTransportConfig
            {
                Host = descriptor.Host,
                Port = (int)descriptor.Port,
                Token = descriptor.Token,
                RoomId = roomId,
                PlayerId = attachPlayerId
            });
            pendingUnreliableDescriptor = descriptor;
            currentDataPlaneMode = IGPDataPlaneMode.DirectKcp;
            currentHostedDataPlaneHost = descriptor.Host ?? string.Empty;
            currentHostedDataPlanePort = descriptor.Port;
            currentHostedDataPlaneExpiresAtUnixSeconds = descriptor.ExpiresAtUnixSeconds;
            currentHostedDataPlaneRoomId = roomId;
            currentHostedDataPlanePlayerId = attachPlayerId;
            currentHostedDataPlaneStatus = "kcp-handshaking";
            currentHostedDataPlaneError = string.Empty;
            UpdateRealtimeConnectionState(IGPRealtimeConnectionState.Connecting);

            LogHostedDataPlaneEvent("handshake-sent", attemptId);
        }

        private void AttachDirectTcpDataPlane(
            string roomId,
            string attachPlayerId,
            IGPHostSessionDataPlaneDescriptor descriptor,
            int attemptId)
        {
            if (TcpClient == null)
            {
                throw new IGPSDKException("IGP TCP client is not initialized");
            }
            if (string.IsNullOrWhiteSpace(descriptor.TcpHost) ||
                descriptor.TcpPort == 0 ||
                !descriptor.TcpFrameMaxBytes.HasValue)
            {
                throw new IGPSDKException(
                    "TCP data-plane endpoint is unavailable",
                    IGPMultiplayerErrorCodes.ERR_TCP_DATA_PLANE_UNAVAILABLE);
            }

            reliableTransport?.Disconnect();
            DetachNetworkTransportEvents(reliableTransport);
            unreliableTransport?.Disconnect();
            DetachNetworkTransportEvents(unreliableTransport);
            reliableTransport = IGPNetworkTransportFactory.CreateTcp(TcpClient);
            AttachNetworkTransportEvents(reliableTransport);
            reliableTransport.Connect(new IGPNetworkTransportConfig
            {
                Host = descriptor.TcpHost,
                Port = checked((int)descriptor.TcpPort),
                Token = descriptor.Token,
                RoomId = roomId,
                PlayerId = attachPlayerId,
                FrameMaxBytes = descriptor.TcpFrameMaxBytes.Value,
            });
            pendingUnreliableDescriptor = descriptor;
            currentDataPlaneMode = IGPDataPlaneMode.DirectTcp;
            currentHostedDataPlaneHost = descriptor.TcpHost;
            currentHostedDataPlanePort = descriptor.TcpPort;
            currentHostedDataPlaneExpiresAtUnixSeconds = descriptor.ExpiresAtUnixSeconds;
            currentHostedDataPlaneRoomId = roomId;
            currentHostedDataPlanePlayerId = attachPlayerId;
            currentHostedDataPlaneStatus = "tcp-handshaking";
            currentHostedDataPlaneError = string.Empty;
            UpdateRealtimeConnectionState(IGPRealtimeConnectionState.Connecting);
            LogHostedDataPlaneEvent("handshake-sent", attemptId);
        }

        private void AttachUnreliableUdpLane(
            IGPHostSessionDataPlaneDescriptor descriptor,
            string roomId,
            string attachPlayerId,
            bool useEnvelopeV1)
        {
            var client = UdpClient;
            if (client == null ||
                string.IsNullOrWhiteSpace(descriptor.unreliableUdpHost) ||
                descriptor.unreliableUdpPort == 0 ||
                string.IsNullOrWhiteSpace(descriptor.unreliableUdpToken) ||
                !descriptor.unreliableUdpPayloadMaxBytes.HasValue ||
                descriptor.unreliableUdpPayloadMaxBytes.Value <= 0)
            {
                return;
            }

            unreliableTransport?.Disconnect();
            DetachNetworkTransportEvents(unreliableTransport);
            unreliableTransport = IGPNetworkTransportFactory.CreateUdp(client);
            AttachNetworkTransportEvents(unreliableTransport);
            unreliableTransport.Connect(new IGPNetworkTransportConfig
            {
                Host = descriptor.unreliableUdpHost,
                Port = (int)descriptor.unreliableUdpPort,
                Token = descriptor.unreliableUdpToken,
                RoomId = roomId,
                PlayerId = attachPlayerId,
                PayloadMaxBytes = descriptor.unreliableUdpPayloadMaxBytes.Value,
                UseEnvelopeV1 = useEnvelopeV1
            });
        }

        private async Task WaitForHostedDataPlaneReadyAsync(
            TimeSpan timeout,
            int attemptId,
            int roomGeneration,
            CancellationToken cancellationToken = default)
        {
            var deadline = DateTime.UtcNow.Add(timeout);
            while (DateTime.UtcNow < deadline)
            {
                if (attemptId != hostedDataPlaneAttemptId || roomGeneration != realtimeRoomGeneration)
                {
                    throw new StaleHostedDataPlaneAttemptException();
                }

                if (IsReliableConnected)
                {
                    currentHostedDataPlaneStatus = "connected";
                    currentHostedDataPlaneError = string.Empty;
                    UpdateRealtimeConnectionState(IGPRealtimeConnectionState.Ready);
                    return;
                }

                if (string.Equals(currentHostedDataPlaneStatus, "error", StringComparison.OrdinalIgnoreCase) &&
                    !string.IsNullOrWhiteSpace(currentHostedDataPlaneError))
                {
                    throw new IGPSDKException(currentHostedDataPlaneError);
                }

                CancellationToken effectiveCancellationToken = cancellationToken.CanBeCanceled
                    ? cancellationToken
                    : RuntimeCancellationToken;
                await Task.Delay(100, effectiveCancellationToken);
            }

            throw new TimeoutException(BuildHostedDataPlaneTimeoutMessage(timeout));
        }

        private string BuildHostedDataPlaneTimeoutMessage(TimeSpan timeout)
        {
            var target = string.IsNullOrWhiteSpace(currentHostedDataPlaneHost) || currentHostedDataPlanePort == 0
                ? "the requested direct data endpoint"
                : $"{currentHostedDataPlaneHost}:{currentHostedDataPlanePort}";

            return
                $"Hosted data plane connection was not established within {timeout.TotalSeconds:F0}s; " +
                $"no response was received from {target}";
        }

        private void ResetHostedDataPlaneState(string status = "idle", string error = "")
        {
            LogSdkDebug(
                "hosted-data-plane",
                "reset",
                $"event=start targetStatus={FormatNetworkLogValue(status)} previousStatus={FormatNetworkLogValue(currentHostedDataPlaneStatus)} " +
                $"previousState={realtimeConnectionState} mode={currentDataPlaneMode} " +
                $"reliableTransport={CurrentReliableTransport?.ToString() ?? "-"} reliableConnected={IsReliableConnected} " +
                $"unreliableConnected={IsUnreliableUdpConnected} error={FormatNetworkLogValue(error)}");
            suppressReliableDisconnectHandling = true;
            try
            {
                reliableTransport?.Disconnect();
                unreliableTransport?.Disconnect();
            }
            finally
            {
                suppressReliableDisconnectHandling = false;
            }
            DetachNetworkTransportEvents(reliableTransport);
            DetachNetworkTransportEvents(unreliableTransport);
            reliableTransport = null;
            unreliableTransport = null;
            pendingUnreliableDescriptor = null;
            tcpFallbackEligible = false;
            currentDataPlaneMode = IGPDataPlaneMode.None;
            currentHostedDataPlaneHost = string.Empty;
            currentHostedDataPlanePort = 0;
            currentHostedDataPlaneExpiresAtUnixSeconds = 0;
            currentHostedDataPlaneRoomId = string.Empty;
            currentHostedDataPlanePlayerId = string.Empty;
            currentHostedDataPlaneStatus = status;
            currentHostedDataPlaneError = error;
            LogSdkDebug(
                "hosted-data-plane",
                "reset",
                $"event=complete targetStatus={FormatNetworkLogValue(status)} reliableTransport=- reliableConnected=false");
        }

        private void InvalidateRealtimeDataPlane(
            string status,
            string error = "",
            IGPRealtimeConnectionState state = IGPRealtimeConnectionState.Idle)
        {
            var previousState = realtimeConnectionState;
            var previousStatus = currentHostedDataPlaneStatus;
            var previousTransport = CurrentReliableTransport;
            bool wasReliableConnected = IsReliableConnected;
            realtimeRoomGeneration += 1;
            hostedDataPlaneAttemptId += 1;

            ResetHostedDataPlaneState(status, error);
            UpdateRealtimeConnectionState(state);
            LogSdkInfo(
                "hosted-data-plane",
                "invalidate",
                $"event=complete reason={FormatNetworkLogValue(status)} previousState={previousState} currentState={state} " +
                $"previousStatus={FormatNetworkLogValue(previousStatus)} previousTransport={previousTransport?.ToString() ?? "-"} " +
                $"wasReliableConnected={wasReliableConnected} roomGeneration={realtimeRoomGeneration} attemptId={hostedDataPlaneAttemptId} " +
                $"error={FormatNetworkLogValue(error)}");
        }

        private void ApplyNegotiatedTransportOptions(
            int? reliableMessageMaxBytes,
            int? reliableChunkMaxBytes,
            int? kcpDataPlanePayloadMaxBytes,
            int? kcpFrameMaxBytes,
            IGPRealtimeTransport reliableTransportKind,
            int? tcpFrameMaxBytes)
        {
            var negotiated = IGPReliableTransportOptionsNegotiation.Resolve(
                reliableMessageMaxBytes,
                reliableChunkMaxBytes,
                kcpDataPlanePayloadMaxBytes,
                kcpFrameMaxBytes);

            KcpClient?.ApplyTransportOptions(negotiated);
            Network?.ApplyTransportOptions(
                reliableTransportKind == IGPRealtimeTransport.Tcp && tcpFrameMaxBytes.HasValue
                    ? negotiated.ForReliableFrameLimit(tcpFrameMaxBytes.Value)
                    : negotiated);
        }
    }
}
