#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
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;
using UnityEngine.Events;
#if UNITY_EDITOR
using UnityEditor;
#endif
using IGP.UnitySDK.Abstractions;
using IGP.UnitySDK.Models;
using IGP.UnitySDK.Core;
using IGP.UnitySDK.Network;
using IGP.UnitySDK.Protocol;

namespace IGP.UnitySDK
{
    /// <summary>
    /// 游戏状态枚举（用于自适应心跳间隔）
    /// </summary>
    public enum GameState
    {
        /// <summary>空闲状态（大厅、菜单）</summary>
        Idle,
        /// <summary>正常游戏中</summary>
        InGame,
        /// <summary>激烈战斗中（需要精确RTT）</summary>
        InBattle
    }

    /// <summary>
    /// 当前数据面模式。
    /// </summary>
    public enum IGPDataPlaneMode
    {
        None = 0,
        DirectKcp = 1,
    }

    /// <summary>
    /// Hosted runtime connection state with explicit KCP data-plane phases.
    /// </summary>
    public enum IGPHostedConnectionState
    {
        Disconnected = 0,
        WebSocketConnecting = 1,
        WebSocketConnected = 2,
        KcpTokenRequesting = 3,
        KcpHandshaking = 4,
        KcpConnected = 5,
        ConnectionFailed = 7,
    }

    /// <summary>
    /// Unity事件定义
    /// </summary>
    [Serializable]
    public class IGPConnectionEvent : UnityEvent<bool> { }

    [Serializable]
    public class IGPMessageEvent : UnityEvent<string, object?> { }

    [Serializable]
    public class IGPErrorEvent : UnityEvent<string> { }

    [Serializable]
    public class IGPRoomEvent : UnityEvent<Room> { }

    [Serializable]
    public class IGPMapChangedEvent : UnityEvent<IGPMapChangeData> { }

    [Serializable]
    public class IGPOfflineMapLaunchRequestedEvent : UnityEvent<IGPDesktopOfflineMapLaunchRequestedEvent> { }

    [Serializable]
    public class IGPAuthorizationStateEvent : UnityEvent<IGPAuthorizationState> { }

    [Serializable]
    public class IGPAuthorizationFailedEvent : UnityEvent<string> { }

    [Serializable]
    public class IGPAntiAddictionStatusEvent : UnityEvent<IGPAntiAddictionStatus> { }

    /// <summary>
    /// 授权验证状态
    /// </summary>
    public enum IGPAuthorizationState
    {
        Pending = 0,
        AuthorizedOnline = 1,
        AuthorizedOffline = 2,
        Failed = 3,
        Skipped = 4,
    }

    [Serializable]
    public sealed class IGPAuthorizationFailureInfo
    {
        public string code = string.Empty;
        public string message = string.Empty;
    }

    /// <summary>
    /// 房主转移事件（from, to）
    /// </summary>
    [Serializable]
    public class IGPHostTransferEvent : UnityEvent<string, string> { }

    /// <summary>
    /// IGP Unity SDK 主组件
    /// </summary>
    public partial class IGPRuntimeManager : MonoBehaviour, IIGPRuntimeClient
    {
        private const string PreviousEnglishAuthorizationFailureFallbackTitle = "Network unavailable";
        private const string PreviousEnglishAuthorizationFailureFallbackMessage =
            "Network is currently unavailable. Please check your connection and try again.";
        private const string PreviousEnglishAuthorizationFailureFallbackHint =
            "Please check your network connection and retry authorization.";
        private const string PreviousEnglishAuthorizationFailureFallbackButtonText = "Retry";
        private const string LegacyAuthorizationFailureFallbackTitle = "Unable to verify game access";
        private const string LegacyAuthorizationFailureFallbackHint =
            "Open IGP Desktop, make sure you are signed in, and then try again.";
        private const string LegacyAuthorizationFailureFallbackButtonText = "OK";

        private static readonly TimeSpan HostedRealtimeReadyTimeout = TimeSpan.FromSeconds(10);
        private static readonly TimeSpan HostedDataPlaneReadyTimeout = TimeSpan.FromSeconds(5);
        private static readonly TimeSpan HostedDataPlaneDebounceWindow = TimeSpan.FromSeconds(3);
        private const string GameReadyStateKey = "gameReady";
        private static readonly TimeSpan[] HostedDataPlaneRetryDelays =
        {
            TimeSpan.FromMilliseconds(500),
            TimeSpan.FromSeconds(1),
            TimeSpan.FromSeconds(2),
        };
        private const int HostedDataPlaneAttachAttempts = 1 + 3;
        private const int MaxAuthorizationPipeResponseBytes = 1024 * 1024;
        private const float RttStatsOverlayRefreshIntervalSeconds = 0.5f;
        private const float RttStatsOverlayQueueAverageWindowSeconds = 1.0f;
        private const float RttStatsOverlayQueuePeakWindowSeconds = 5.0f;

        [Header("Global Configuration")]
        [SerializeField] private IGPConfig? config = null;

        [Header("Hosted Runtime Settings")]
        private string serverUrl = string.Empty;

        [Header("KCP Heartbeat Settings")]
        [Tooltip("KCP心跳间隔（秒）")]
        [SerializeField] private float kcpHeartbeatInterval = 1.0f;

        [Tooltip("是否启用自动KCP心跳")]
        [SerializeField] private bool enableKcpHeartbeat = true;

        [Tooltip("每次 KCP Tick 最多读取的 UDP datagram 数。提高后更容易排空突发积压，但积压存在时会增加单帧主线程耗时。")]
        [Min(1)]
        [SerializeField] private int kcpMaxDatagramsPerTick = IGPKcpClient.DefaultMaxDatagramsPerTick;

        [Tooltip("KCP send window size. Higher values allow more in-flight reliable data when the peer also advertises a compatible receive window.")]
        [Min(IGPKcpClient.MinWindowSize)]
        [SerializeField] private int kcpSendWindowSize = IGPKcpClient.DefaultWindowSize;

        [Tooltip("KCP receive window size advertised to peers.")]
        [Min(IGPKcpClient.MinWindowSize)]
        [SerializeField] private int kcpReceiveWindowSize = IGPKcpClient.DefaultWindowSize;

        [NonSerialized] private string playerId = string.Empty;
        [NonSerialized] private string playerName = string.Empty;
        // Legacy sample presentation fields kept for source compatibility.
        [NonSerialized] [HideInInspector] public string playerColor = "#FF0000";
        [NonSerialized] [HideInInspector] public string playerAvatar = "default";
        [NonSerialized] [HideInInspector] public int playerAvatarIndex = 0;

        [NonSerialized] private string currentRoomId = string.Empty;
        [NonSerialized] private string currentRoomCode = string.Empty;
        [NonSerialized] private string sessionToken = string.Empty;
        [NonSerialized] private Room roomData = new Room();
        [NonSerialized] private Player currentPlayerData = new Player();

        [Header("Events")]
        public IGPConnectionEvent onConnectionStateChanged = new IGPConnectionEvent();
        public IGPMessageEvent onMessageReceived = new IGPMessageEvent();
        public IGPErrorEvent onError = new IGPErrorEvent();
        public IGPAuthorizationStateEvent onAuthorizationStateChanged = new IGPAuthorizationStateEvent();
        public IGPAuthorizationFailedEvent onAuthorizationFailed = new IGPAuthorizationFailedEvent();
        public IGPAntiAddictionStatusEvent onAntiAddictionStateChanged = new IGPAntiAddictionStatusEvent();
        public IGPRoomEvent onRoomJoined = new IGPRoomEvent();
        public IGPRoomEvent onRoomUpdated = new IGPRoomEvent();
        public IGPRoomEvent onRoomLeft = new IGPRoomEvent();
        public IGPMapChangedEvent onMapChanged = new IGPMapChangedEvent();
        public IGPOfflineMapLaunchRequestedEvent onOfflineMapLaunchRequested = new IGPOfflineMapLaunchRequestedEvent();
        public UnityEvent onHeartbeatSent = new UnityEvent();

        // 队伍相关事件
        public IGPMessageEvent onTeamChanged = new IGPMessageEvent();
        public IGPMessageEvent onTeamInitialized = new IGPMessageEvent();
        public IGPMessageEvent onTeamStatusUpdated = new IGPMessageEvent();
        public IGPHostTransferEvent onHostTransferred = new IGPHostTransferEvent();

        internal event Action<IGPHostSessionRoomEvent>? HostedRoomEventReceived;

        /// <summary>
        /// Raised when the hosted realtime or data-plane connection changes phase.
        /// </summary>
        public event Action<IGPHostedConnectionState>? HostedConnectionStateChanged;

        // 内部状态
        private CancellationTokenSource? cancellationTokenSource;
        private Coroutine? heartbeatCoroutine;
        private bool isDestroyed = false;
        private bool initializeRequested = false;
        private bool sdkInitialized = false;
        private Task<bool>? sdkInitializationTask;
        private IGPLaunchOptions? pendingLaunchOptions;
        private IGPDesktopSessionClient? desktopSessionClient;
        private IGPHostSessionClient? hostSessionClient;
        private IGPDesktopUserContext? currentDesktopUserContext;
        private IGPDesktopCapabilitySet? currentDesktopCapabilities;
        private IGPDesktopUserProfile? currentDesktopUserProfile;
        private IGPAntiAddictionStatus? currentAntiAddictionStatus;
        private string desktopSessionLastErrorCode = string.Empty;
        private string desktopSessionLastErrorMessage = string.Empty;
        private string desktopSessionLastErrorCategory = string.Empty;
        private string desktopSessionLastChannelState = string.Empty;
        private string desktopSessionLastAttachState = string.Empty;
        private string desktopSessionLastAttachSource = string.Empty;
        // 自愈相关：下一次重试时间 + 防并发哨兵
        private DateTime? nextDesktopAttachRetryAtUtc;
        private bool desktopAttachRetryInFlight;
        private DateTime? nextAuthorizationRecoverAtUtc;
        private bool authorizationRecoverInFlight;
        private const double DesktopAttachRetryBackoffSeconds = 2.0;
        private const double AuthorizationRecoverBackoffSeconds = 2.0;
        private IGPAuthorizationState authorizationState = IGPAuthorizationState.Pending;
        private IGPAuthorizationFailureInfo? lastAuthorizationFailure;
        private bool isAuthorizationRequired;
        private string authorizationPipeName = string.Empty;
        private string authorizationBrokerToken = string.Empty;
        private string authorizationSessionToken = string.Empty;
        private string authorizationDeviceIdHash = string.Empty;
        private string authorizationOfflineLicensePublicKey = string.Empty;
        private string authorizationOfflineLicenseKeyId = string.Empty;
        private string authorizationOfflineLicenseAlgorithm = string.Empty;
        private DateTimeOffset? authorizationSessionExpiresAt;
        private int authorizationGameId;
        private bool authorizationFailureFallbackDismissed;
        private bool hasRttStatsOverlaySnapshot;
        private Core.IGPKcpTransportStats rttStatsOverlaySnapshot;
        private float nextRttStatsOverlayRefreshAt;
        private bool hasRttStatsOverlayQueueAverage;
        private int rttStatsOverlayQueueAverageWaitSnd;
        private int rttStatsOverlayQueueAverageSndQueue;
        private int rttStatsOverlayQueueAverageSndBuf;
        private bool hasRttStatsOverlayQueuePeak;
        private int rttStatsOverlayQueuePeakWaitSnd;
        private int rttStatsOverlayQueuePeakSndQueue;
        private int rttStatsOverlayQueuePeakSndBuf;
        private readonly Queue<RttStatsOverlayQueueSample> rttStatsOverlayQueueSamples = new Queue<RttStatsOverlayQueueSample>();
        private float rttStatsOverlayQueueLastSampleAt = -1f;
        private CancellationTokenSource? authorizationRefreshCts;
        private Task? authorizationRefreshTask;
        private IGPLogLevel appliedLogLevel = IGPLogLevel.Off;
        private string hostedConnectionStatus = "disconnected";
        private IGPHostedConnectionState hostedConnectionState = IGPHostedConnectionState.Disconnected;
        private string currentHostedSessionEndpoint = string.Empty;
        private string currentHostedSessionSecret = string.Empty;
        private IGPDataPlaneMode currentDataPlaneMode = IGPDataPlaneMode.None;
        private string currentHostedDataPlaneHost = string.Empty;
        private uint currentHostedDataPlanePort;
        private ulong currentHostedDataPlaneExpiresAtUnixMs;
        private string currentHostedDataPlaneRoomId = string.Empty;
        private string currentHostedDataPlanePlayerId = string.Empty;
        private string currentHostedDataPlaneStatus = "idle";
        private string currentHostedDataPlaneError = string.Empty;
        private IIGPDataPlaneTransport? dataPlaneTransport;
        private readonly object hostedDataPlaneAttachLock = new object();
        private Task? hostedDataPlaneAttachTask;
        private string hostedDataPlaneAttachRoomId = string.Empty;
        private string hostedDataPlaneAttachPlayerId = string.Empty;
        private DateTime hostedDataPlaneLastAttachStartedAtUtc = DateTime.MinValue;
        private int hostedDataPlaneAttemptId;
        private bool hostedDisconnectRequested;
        private bool hostedBootstrapRequestInFlight;
        private bool suppressKcpDisconnectHandling;
        // SDK客户端
        internal IGPHostedRealtimeClient? WebSocketClient { get; private set; }
        public IGPKcpClient? KcpClient { get; private set; }
        public IGPUnreliableUdpClient? UnreliableUdpClient { get; private set; }

        /// <summary>
        /// IGP Network 模块 (P2P 通信)
        /// </summary>
        public IGPNetwork? Network { get; private set; }

        private CancellationToken RuntimeCancellationToken => cancellationTokenSource?.Token ?? CancellationToken.None;


        /// <summary>
        /// 服务器URL
        /// </summary>
        public string ServerUrl
        {
            get => serverUrl;
            set => serverUrl = value ?? string.Empty;
        }

        /// <summary>
        /// 心跳间隔
        /// </summary>
        public float HeartbeatInterval => config != null ? config.heartbeatInterval : 30f;

        /// <summary>
        /// 当前运行实例是否已经完成初始化。
        /// </summary>
        public bool IsInitialized => sdkInitialized;

        /// <summary>
        /// 当前 SDK 连接的桌面端环境。
        /// </summary>
        public IGPSDKEnvironment SdkEnvironment => config != null ? config.sdkEnvironment : IGPSDKEnvironment.PROD;

        /// <summary>
        /// 是否自动连接
        /// </summary>
        public bool AutoConnect => (pendingLaunchOptions?.autoConnect ?? false) || (config != null && config.autoConnect);

        /// <summary>
        /// Current minimum SDK diagnostic log level.
        /// </summary>
        public IGPLogLevel LogLevel => config != null ? config.debugLogging : IGPLogLevel.Off;

        /// <summary>
        /// Whether SDK diagnostic logs are enabled.
        /// </summary>
        public bool DebugLogging => LogLevel != IGPLogLevel.Off;

        /// <summary>
        /// 当前配置资源。
        /// </summary>
        public IGPConfig? Config => config;

        /// <summary>
        /// 当前房间ID
        /// </summary>
        public string CurrentRoomId => currentRoomId;

        /// <summary>
        /// 当前房间代码
        /// </summary>
        public string CurrentRoomCode => currentRoomCode;

        /// <summary>
        /// 当前房间快照
        /// </summary>
        public Room CurrentRoomData => roomData;

        /// <summary>
        /// 当前玩家快照
        /// </summary>
        public Player CurrentPlayerData => currentPlayerData;

        /// <summary>
        /// 当前会话 Token
        /// </summary>
        public string SessionToken
        {
            get => sessionToken;
            set => sessionToken = value;
        }

        /// <summary>
        /// WebSocket连接状态
        /// </summary>
        public bool IsWebSocketConnected => WebSocketClient?.IsConnected ?? false;

        /// <summary>
        /// desktop 能力通道是否已附着
        /// </summary>
        public bool IsDesktopSessionAttached => desktopSessionClient?.IsAttached ?? false;

        /// <summary>
        /// 当前 desktop 登录用户上下文
        /// </summary>
        public IGPDesktopUserContext? CurrentDesktopUserContext => currentDesktopUserContext;

        /// <summary>
        /// 当前 desktop 能力集合
        /// </summary>
        public IGPDesktopCapabilitySet? CurrentDesktopCapabilities => currentDesktopCapabilities;

        /// <summary>
        /// desktop session 通道状态（desktop 端上报的 channelState，例如 reachable / connecting / failed）。
        /// 对齐 GameMaker bridge，用于诊断与 UI 展示。
        /// </summary>
        public string DesktopChannelState => desktopSessionClient?.CurrentSession?.ChannelState
            ?? desktopSessionLastChannelState
            ?? string.Empty;

        /// <summary>
        /// desktop session attach 校验状态（desktop 端上报的 attachState，例如 attached / rejected / attaching）。
        /// 对齐 GameMaker bridge，用于诊断与 UI 展示。
        /// </summary>
        public string DesktopAttachState => desktopSessionClient?.CurrentSession?.AttachState
            ?? desktopSessionLastAttachState
            ?? string.Empty;

        /// <summary>
        /// desktop session attach 来源（desktop 端上报的 attachSource，例如 launcher / auto-discovery）。
        /// 对齐 GameMaker bridge。
        /// </summary>
        public string DesktopAttachSource => desktopSessionClient?.CurrentSession?.AttachSource
            ?? desktopSessionLastAttachSource
            ?? string.Empty;

        /// <summary>
        /// 最近一次 desktop session 失败的错误分类（validation/authorization/channel/runtime）。
        /// 对齐 GameMaker bridge，用于驱动上层的重试或回退策略。
        /// </summary>
        public string DesktopSessionLastErrorCategory => desktopSessionLastErrorCategory ?? string.Empty;

        /// <summary>
        /// 当前 desktop 登录用户资料
        /// </summary>
        public IGPDesktopUserProfile? CurrentDesktopUserProfile => currentDesktopUserProfile;

        /// <summary>
        /// 当前防沉迷状态快照。
        /// </summary>
        public IGPAntiAddictionStatus? CurrentAntiAddictionStatus => currentAntiAddictionStatus;

        /// <summary>
        /// 当前授权验证状态
        /// </summary>
        public IGPAuthorizationState AuthorizationState => authorizationState;

        /// <summary>
        /// 当前是否已通过授权验证
        /// </summary>
        public bool IsAuthorized =>
            authorizationState == IGPAuthorizationState.AuthorizedOnline ||
            authorizationState == IGPAuthorizationState.AuthorizedOffline;

        /// <summary>
        /// 当前是否要求授权验证
        /// </summary>
        public bool IsAuthorizationRequired => isAuthorizationRequired;

        /// <summary>
        /// 最近一次授权验证失败信息
        /// </summary>
        public IGPAuthorizationFailureInfo? LastAuthorizationFailure => lastAuthorizationFailure;

        /// <summary>
        /// 宿主控制通道是否已附着
        /// </summary>
        public bool IsHostedSessionAttached => hostSessionClient?.IsAttached ?? false;

        /// <summary>
        /// 当前宿主控制通道端点。
        /// </summary>
        public string CurrentHostedSessionEndpoint => currentHostedSessionEndpoint;

        /// <summary>
        /// KCP连接状态
        /// </summary>
        public bool IsKcpConnected => dataPlaneTransport?.IsConnected ?? (KcpClient?.IsConnected ?? false);

        internal bool IsUnreliableUdpConnected => UnreliableUdpClient?.IsConnected ?? false;

        internal int UnreliableUdpPayloadMaxBytes => UnreliableUdpClient?.PayloadMaxBytes ?? IGPUnreliableUdpClient.DefaultPayloadMaxBytes;

        /// <summary>
        /// 当前数据面模式。
        /// </summary>
        public IGPDataPlaneMode CurrentDataPlaneMode => currentDataPlaneMode;

        /// <summary>
        /// 当前是否已附着数据面。
        /// </summary>
        public bool IsHostedDataPlaneAttached => currentDataPlaneMode != IGPDataPlaneMode.None;

        /// <summary>
        /// 当前数据面主机地址。
        /// </summary>
        public string CurrentHostedDataPlaneHost => currentHostedDataPlaneHost;

        /// <summary>
        /// 当前数据面端口。
        /// </summary>
        public uint CurrentHostedDataPlanePort => currentHostedDataPlanePort;

        /// <summary>
        /// 当前数据面附着状态。
        /// </summary>
        public string CurrentHostedDataPlaneStatus => currentHostedDataPlaneStatus;

        /// <summary>
        /// 当前数据面附着失败原因。
        /// </summary>
        public string CurrentHostedDataPlaneError => currentHostedDataPlaneError;

        /// <summary>
        /// Current hosted runtime connection state.
        /// </summary>
        public IGPHostedConnectionState CurrentHostedConnectionState => hostedConnectionState;

        /// <summary>
        /// 玩家ID
        /// </summary>
        public string PlayerId
        {
            get => string.IsNullOrEmpty(playerId) ? GeneratePlayerId() : playerId;
            set => playerId = value;
        }

        /// <summary>
        /// 玩家名称
        /// </summary>
        public string PlayerName
        {
            get => string.IsNullOrEmpty(playerName) ? "Player_" + UnityEngine.Random.Range(1000, 9999) : playerName;
            set => playerName = value;
        }

        /// <summary>
        /// Compatibility alias for app-level heartbeat ping/pong RTT stats.
        /// </summary>
        public Core.IGPRTTStats? KcpRTTStats => dataPlaneTransport?.RTTStats ?? KcpClient?.RTTStats;

        /// <summary>
        /// App-level ping/pong RTT stats. This is the preferred name for new diagnostics.
        /// </summary>
        public Core.IGPRTTStats? AppRTTStats => dataPlaneTransport?.RTTStats ?? KcpClient?.AppRTTStats;

        /// <summary>
        /// Underlying KCP ACK/queue state snapshot.
        /// </summary>
        public Core.IGPKcpTransportStats KcpTransportStats =>
            dataPlaneTransport?.TransportStats ??
            KcpClient?.TransportStats ??
            Core.IGPKcpTransportStats.Unavailable;

        /// <summary>
        /// Read-only health of the hosted KCP data plane.
        /// </summary>
        public Core.IGPKcpHealthState KcpHealthState =>
            KcpClient?.HealthState ?? Core.IGPKcpHealthState.Disconnected;

        /// <summary>
        /// Confirmed reason for the most recent KCP transport fault.
        /// </summary>
        public Core.IGPKcpFaultReason KcpLastFaultReason =>
            KcpClient?.LastFaultReason ?? Core.IGPKcpFaultReason.None;

        /// <summary>
        /// KCP连接是否活跃（基于心跳）
        /// </summary>
        public bool IsKcpAlive => dataPlaneTransport?.IsAlive ?? (KcpClient?.IsAlive ?? false);

        /// <summary>
        /// 每次 KCP Tick 最多读取的 UDP datagram 数。
        /// </summary>
        public int KcpMaxDatagramsPerTick
        {
            get => NormalizeKcpMaxDatagramsPerTick(kcpMaxDatagramsPerTick);
            set
            {
                kcpMaxDatagramsPerTick = NormalizeKcpMaxDatagramsPerTick(value);
                ApplyKcpRuntimeSettings();
            }
        }

        public int KcpSendWindowSize
        {
            get => NormalizeKcpWindowSize(kcpSendWindowSize);
            set
            {
                kcpSendWindowSize = NormalizeKcpWindowSize(value);
                ApplyKcpRuntimeSettings();
            }
        }

        public int KcpReceiveWindowSize
        {
            get => NormalizeKcpWindowSize(kcpReceiveWindowSize);
            set
            {
                kcpReceiveWindowSize = NormalizeKcpWindowSize(value);
                ApplyKcpRuntimeSettings();
            }
        }

        /// <summary>
        /// KCP心跳间隔（秒）
        /// </summary>
        public float KcpHeartbeatInterval
        {
            get => kcpHeartbeatInterval;
            set
            {
                kcpHeartbeatInterval = value;
                if (dataPlaneTransport != null)
                {
                    dataPlaneTransport.SetHeartbeatInterval(value);
                }
                else if (KcpClient != null && enableKcpHeartbeat)
                {
                    KcpClient.HeartbeatInterval = value;
                }
            }
        }

        /// <summary>
        /// 是否启用自动KCP心跳
        /// </summary>
        public bool EnableKcpHeartbeat
        {
            get => enableKcpHeartbeat;
            set
            {
                enableKcpHeartbeat = value;
                if (dataPlaneTransport != null)
                {
                    if (value) dataPlaneTransport.StartHeartbeat(kcpHeartbeatInterval);
                    else dataPlaneTransport.StopHeartbeat();
                }
                else if (KcpClient != null)
                {
                    if (value) KcpClient.StartHeartbeat(kcpHeartbeatInterval);
                    else KcpClient.StopHeartbeat();
                }
            }
        }

        /// <summary>
        /// 是否显示网络诊断浮层（OnGUI）。
        /// </summary>
        public bool ShowNetworkDiagnostics => config != null && config.showNetworkDiagnostics;

        /// <summary>
        /// 授权验证失败时是否显示 SDK 自带的保底提示。
        /// </summary>
        public bool ShowAuthorizationFailureFallback =>
            config == null || config.showAuthorizationFailureFallback;

        /// <summary>
        /// 授权验证失败保底提示标题。
        /// </summary>
        public string AuthorizationFailureFallbackTitle =>
            config?.authorizationFailureFallbackTitle ?? IGPConfig.DefaultAuthorizationFailureFallbackTitle;

        /// <summary>
        /// 授权验证失败保底提示正文。留空时使用 SDK 默认文案。
        /// </summary>
        public string AuthorizationFailureFallbackMessage =>
            config?.authorizationFailureFallbackMessage ?? IGPConfig.DefaultAuthorizationFailureFallbackMessage;

        /// <summary>
        /// 授权验证失败保底提示引导语。
        /// </summary>
        public string AuthorizationFailureFallbackHint =>
            config?.authorizationFailureFallbackHint ?? IGPConfig.DefaultAuthorizationFailureFallbackHint;

        /// <summary>
        /// 授权验证失败保底提示按钮文案。
        /// </summary>
        public string AuthorizationFailureFallbackButtonText =>
            config?.authorizationFailureFallbackButtonText ?? IGPConfig.DefaultAuthorizationFailureFallbackButtonText;

        /// <summary>
        /// 授权验证失败保底提示退出游戏按钮文案。
        /// </summary>
        public string AuthorizationFailureFallbackQuitButtonText =>
            config?.authorizationFailureFallbackQuitButtonText ?? IGPConfig.DefaultAuthorizationFailureFallbackQuitButtonText;

        /// <summary>
        /// 当前是否正在显示授权验证失败保底提示。
        /// </summary>
        public bool IsAuthorizationFailureFallbackVisible =>
            ShowAuthorizationFailureFallback &&
            !authorizationFailureFallbackDismissed &&
            authorizationState == IGPAuthorizationState.Failed &&
            !string.IsNullOrWhiteSpace(GetAuthorizationFailureDisplayMessage());

        private bool DesktopSessionAutoAttach => config == null || config.desktopSessionAutoAttach;

        private string DesktopPipeEndpoint => IGPDesktopSessionEnvironment.ResolvePipeEndpoint(
            config?.desktopPipeEndpoint,
            SdkEnvironment);

        private void EnsureSDKInitialized()
        {
            if (!initializeRequested)
            {
                throw new IGPSDKException("IGP SDK is not initialized. Call IGPRuntimeManager.InitializeAsync() before using SDK features.");
            }
        }


#if UNITY_EDITOR
        private const string DefaultConfigAssetPath = "Assets/IGP/IGPConfig.asset";

        private void Reset()
        {
            AutoAssignConfigInEditor();
        }

        private void OnValidate()
        {
            kcpMaxDatagramsPerTick = NormalizeKcpMaxDatagramsPerTick(kcpMaxDatagramsPerTick);
            kcpSendWindowSize = NormalizeKcpWindowSize(kcpSendWindowSize);
            kcpReceiveWindowSize = NormalizeKcpWindowSize(kcpReceiveWindowSize);

            if (config != null || Application.isPlaying || EditorApplication.isCompiling || EditorApplication.isUpdating)
            {
                return;
            }

            EditorApplication.delayCall -= AutoAssignConfigInEditor;
            EditorApplication.delayCall += AutoAssignConfigInEditor;
        }

        private void AutoAssignConfigInEditor()
        {
            if (this == null || config != null || Application.isPlaying)
            {
                return;
            }

            var resolvedConfig = ResolveConfigAssetInEditor();
            if (resolvedConfig == null)
            {
                return;
            }

            config = resolvedConfig;
            EditorUtility.SetDirty(this);
        }

        private static IGPConfig? ResolveConfigAssetInEditor()
        {
            var defaultConfig = AssetDatabase.LoadAssetAtPath<IGPConfig>(DefaultConfigAssetPath);
            if (defaultConfig != null)
            {
                return defaultConfig;
            }

            var guids = AssetDatabase.FindAssets("t:IGPConfig");
            if (guids.Length == 1)
            {
                return AssetDatabase.LoadAssetAtPath<IGPConfig>(AssetDatabase.GUIDToAssetPath(guids[0]));
            }

            if (guids.Length > 1)
            {
                Array.Sort(guids, StringComparer.Ordinal);
                var firstPath = AssetDatabase.GUIDToAssetPath(guids[0]);
                var selectedConfig = AssetDatabase.LoadAssetAtPath<IGPConfig>(firstPath);
                IGPLog.Configure(selectedConfig != null ? selectedConfig.debugLogging : IGPLogLevel.Off);
                IGPLog.Warning(
                    "editor",
                    "config",
                    $"event=multiple-configs-selected path={IGPLog.FormatValue(firstPath)}");
                return selectedConfig;
            }

            EnsureDefaultConfigFolderInEditor();
            var createdConfig = ScriptableObject.CreateInstance<IGPConfig>();
            AssetDatabase.CreateAsset(createdConfig, DefaultConfigAssetPath);
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
            IGPLog.Configure(createdConfig.debugLogging);
            IGPLog.Info(
                "editor",
                "config",
                $"event=default-config-created path={DefaultConfigAssetPath}");
            return createdConfig;
        }

        private static void EnsureDefaultConfigFolderInEditor()
        {
            if (!AssetDatabase.IsValidFolder("Assets/IGP"))
            {
                AssetDatabase.CreateFolder("Assets", "IGP");
            }
        }
#endif

        private void Awake()
        {
            IGPLog.Configure(LogLevel);
            try
            {
                cancellationTokenSource ??= new CancellationTokenSource();
            }
            catch (Exception ex)
            {
                LogSdkError("runtime", "awake", $"event=failed error={FormatNetworkLogValue(ex.Message)}");
                onError?.Invoke($"SDK prepare failed: {ex.Message}");
            }
        }

        /// <summary>
        /// 显式初始化 IGP SDK。游戏需要主动调用该方法；仅把 IGPRuntimeManager 放进场景不会自动启动 SDK。
        /// </summary>
        public async Task<bool> InitializeAsync()
        {
            if (sdkInitializationTask != null)
            {
                return await sdkInitializationTask;
            }

            initializeRequested = true;
            sdkInitializationTask = InitializeInternalAsync();
            return await sdkInitializationTask;
        }

        /// <summary>
        /// 显式初始化 IGP SDK 的 fire-and-forget 入口，便于 UnityEvent 或同步启动脚本调用。
        /// </summary>
        public void Initialize()
        {
            _ = RunInitializeAndReportAsync();
        }

        private async Task RunInitializeAndReportAsync()
        {
            try
            {
                await InitializeAsync();
            }
            catch (Exception ex)
            {
                LogSdkError("runtime", "initialize", $"event=failed error={FormatNetworkLogValue(ex.Message)}");
                onError?.Invoke($"SDK initialization failed: {ex.Message}");
            }
        }

        private async Task<bool> InitializeInternalAsync()
        {
            try
            {
                cancellationTokenSource ??= new CancellationTokenSource();

                if (IGPLaunchArgsParser.TryParse(Environment.GetCommandLineArgs(), out var launchOptions) &&
                    launchOptions != null)
                {
                    pendingLaunchOptions = launchOptions;
                }

                Network = new IGPNetwork(this);
                InitializeSDKClients();
                ApplyLogSettings();

                if (heartbeatCoroutine == null)
                {
                    heartbeatCoroutine = StartCoroutine(HeartbeatCoroutine());
                }

                if (string.IsNullOrEmpty(playerId))
                {
                    playerId = GeneratePlayerId();
                }

                if (string.IsNullOrEmpty(playerName))
                {
                    playerName = "Player_" + UnityEngine.Random.Range(1000, 9999);
                }

                LogSessionStart();

                if (ShouldAutoAttachDesktopSessionOnStartup())
                {
                    var attached = await TryAttachDesktopSessionAsync();
                    if (!attached)
                    {
                        LogSdkInfo("desktop-session", "attach", "event=launch-retry-started reason=session-unavailable");
                        await TryLaunchDesktopAndRetryAttachAsync();
                    }
                }

                if (!await TryAuthorizeOnStartupAsync())
                {
                    return false;
                }

                if (HasHostedLaunchContext(pendingLaunchOptions))
                {
                    if (!await TryBootstrapFromLaunchTicketAsync())
                    {
                        onError?.Invoke("Launch bootstrap failed: hosted session endpoint is unavailable");
                        return false;
                    }
                }
                else if (AutoConnect && !string.IsNullOrEmpty(currentRoomId))
                {
                    onError?.Invoke("AutoConnect requires a desktop-provided room launch context");
                }

                sdkInitialized = true;
                LogSdkInfo(
                    "runtime",
                    "initialize",
                    $"event=succeeded playerId={FormatNetworkLogValue(playerId)} playerName={FormatNetworkLogValue(playerName)}");
                return true;
            }
            catch (Exception ex)
            {
                sdkInitialized = false;
                LogSdkError("runtime", "initialize", $"event=failed error={FormatNetworkLogValue(ex.Message)}");
                onError?.Invoke($"SDK initialization failed: {ex.Message}");
                return false;
            }
        }

        private void InitializeSDKClients()
        {
            try
            {
                WebSocketClient?.Dispose();
                KcpClient?.Dispose();
                UnreliableUdpClient?.Dispose();

                // 初始化宿主 WebSocket façade（真实传输由 desktop host session 接管）
                WebSocketClient = new IGPHostedRealtimeClient();

                // 设置WebSocket事件
                WebSocketClient.ConnectionStateChanged += (connected) =>
                {
                    if (isDestroyed) return;
                    if (connected)
                    {
                        UpdateHostedConnectionState(IGPHostedConnectionState.WebSocketConnected);
                    }
                    else
                    {
                        UpdateHostedConnectionState(IGPHostedConnectionState.Disconnected);
                    }
                    onConnectionStateChanged?.Invoke(connected);
                };

                WebSocketClient.MessageReceived += (message) =>
                {
                    if (isDestroyed) return;

                    if (TryHandleBusinessErrorMessage(message, fromDataPlane: false))
                    {
                        return;
                    }

                    ApplyGameReadyStateSet(message);

                    // Intercept P2P data for the Network module
                    if (message.type == "p2p_data")
                    {
                        Network?.HandleIncomingP2P(message.content);
                        return; // Stop further propagation for P2P data
                    }

                    // 处理队伍变更消息
                    if (message.type == "team_change")
                    {
                        onTeamChanged?.Invoke(message.type, message.content);
                        return; // 队伍消息有专门事件，不传播到通用事件
                    }

                    // 处理队伍初始化消息
                    if (message.type == "team_initialize")
                    {
                        onTeamInitialized?.Invoke(message.type, message.content);
                        return;
                    }

                    // 处理队伍状态更新消息
                    if (message.type == "team_status")
                    {
                        onTeamStatusUpdated?.Invoke(message.type, message.content);
                        return;
                    }

                    // 处理房主转移消息
                    if (message.type == "room_host_transfer")
                    {
                        var from = message.content?.GetType().GetProperty("from")?.GetValue(message.content)?.ToString();
                        var to = message.content?.GetType().GetProperty("to")?.GetValue(message.content)?.ToString();

                        if (!string.IsNullOrEmpty(to))
                        {
                            roomData.hostId = to; // 更新本地房主
                        }

                        onHostTransferred?.Invoke(from ?? string.Empty, to ?? string.Empty);
                        return;
                    }

                    onMessageReceived?.Invoke(message.type, message.content);
                };

                WebSocketClient.ErrorOccurred += (error) =>
                {
                    if (isDestroyed) return;
                    onError?.Invoke(error);
                };

                // 初始化KCP客户端（连接在进入房间后触发）
                KcpClient = new IGPKcpClient();
                KcpClient.MessageReceived += (message) =>
                {
                    if (isDestroyed) return;

                    if (TryHandleBusinessErrorMessage(message, fromDataPlane: true))
                    {
                        return;
                    }

                    // Intercept P2P data for the Network module
                    if (message.type == "p2p_data")
                    {
                        Network?.HandleIncomingP2P(message.content);
                        return;
                    }

                    onMessageReceived?.Invoke(message.type, message.content);
                };
                KcpClient.ErrorOccurred += (error) =>
                {
                    if (isDestroyed) return;
                    bool shouldReport = hostedConnectionState != IGPHostedConnectionState.ConnectionFailed;
                    if (dataPlaneTransport != null || currentDataPlaneMode != IGPDataPlaneMode.None)
                    {
                        currentHostedDataPlaneStatus = "error";
                        currentHostedDataPlaneError = error;
                        UpdateHostedConnectionState(IGPHostedConnectionState.ConnectionFailed);
                    }
                    if (shouldReport)
                    {
                        onError?.Invoke(error);
                    }
                };
                KcpClient.ConnectionStateChanged += (connected) =>
                {
                    if (isDestroyed) return;
                    HandleKcpConnectionStateChanged(connected);
                };
                KcpClient.TransportFaulted += (reason) =>
                {
                    if (isDestroyed) return;
                    HandleKcpTransportFaulted(reason);
                };

                UnreliableUdpClient = new IGPUnreliableUdpClient();
                UnreliableUdpClient.MessageReceived += (message) =>
                {
                    if (isDestroyed) return;

                    if (TryHandleBusinessErrorMessage(message, fromDataPlane: true))
                    {
                        return;
                    }

                    if (message.type == "p2p_data")
                    {
                        Network?.HandleIncomingP2P(message.content);
                        return;
                    }

                    onMessageReceived?.Invoke(message.type, message.content);
                };
                UnreliableUdpClient.ErrorOccurred += (error) =>
                {
                    if (isDestroyed) return;
                    onError?.Invoke(error);
                };

                ApplyKcpRuntimeSettings();
                ApplyLogSettings();

                LogSdkInfo("runtime", "clients", "event=initialized");
            }
            catch (Exception ex)
            {
                LogSdkError("runtime", "clients", $"event=initialize-failed error={FormatNetworkLogValue(ex.Message)}");
                onError?.Invoke($"SDK initialization failed: {ex.Message}");
            }
        }

        private void OnDestroy()
        {
            isDestroyed = true;  // 设置销毁标志，防止事件回调
            StopAuthorizationRefreshLoop();
            Disconnect();

            // 清理Network模块
            Network?.Dispose();

            // 清理资源
            if (cancellationTokenSource != null)
            {
                cancellationTokenSource.Cancel();
                cancellationTokenSource.Dispose();
                cancellationTokenSource = null;
            }

            if (heartbeatCoroutine != null)
            {
                StopCoroutine(heartbeatCoroutine);
                heartbeatCoroutine = null;
            }

            // 清理SDK客户端
            desktopSessionClient?.Dispose();
            WebSocketClient?.Dispose();
            KcpClient?.Dispose();
            UnreliableUdpClient?.Dispose();
        }

        private void Update()
        {
            ApplyLogSettingsIfNeeded();

            // drive Network module update (RTT broadcasting)
            Network?.Update();

            // drive KCP tick on main thread
            if (dataPlaneTransport != null)
            {
                dataPlaneTransport.Tick();
            }

            UnreliableUdpClient?.Tick();

            RecoverHostedDataPlaneStatusIfAlive();

            // 对齐 GameMaker bridge 的 TryRecoverDesktopAttachIfNeeded / TryRefreshAuthorizationStateIfNeeded
            // 在主循环内持续做 desktop session + authorization 自愈。
            TryRecoverDesktopAttachIfNeeded();
            TryRecoverAuthorizationStateIfNeeded();
        }

        private void UpdateHostedConnectionState(IGPHostedConnectionState state)
        {
            if (hostedConnectionState == state)
            {
                return;
            }

            hostedConnectionState = state;
            LogSdkInfo(
                "hosted-state",
                $"event=state-changed state={state} roomId={currentRoomId} playerId={playerId} " +
                $"dataPlaneStatus={currentHostedDataPlaneStatus}");
            HostedConnectionStateChanged?.Invoke(state);
        }

        /// <summary>
        /// 异步断开连接
        /// </summary>
        public async Task DisconnectAsync()
        {
            try
            {
                hostedDisconnectRequested = true;
                StopAuthorizationRefreshLoop();
                LogSdkInfo("runtime", "disconnect", "event=started");
                var previousRoom = roomData;

                // 断开WebSocket
                if (WebSocketClient != null)
                {
                    await WebSocketClient.DisconnectAsync();
                }

                // 断开数据面
                ResetHostedDataPlaneState();

                await DetachHostedSessionAsync();

                // Reset Network P2P sessions
                Network?.ResetSessionState();
                currentRoomId = string.Empty;
                currentRoomCode = string.Empty;
                sessionToken = string.Empty;
                roomData = new Room();
                currentPlayerData = new Player();
                hostedConnectionStatus = "disconnected";

                // 只有在对象未被销毁时才触发事件
                if (!isDestroyed)
                {
                    onConnectionStateChanged?.Invoke(false);
                    if (previousRoom != null)
                    {
                        onRoomLeft?.Invoke(previousRoom);
                    }
                }

                LogSdkInfo("runtime", "disconnect", "event=succeeded");
            }
            catch (Exception ex)
            {
                LogSdkError("runtime", "disconnect", $"event=failed error={FormatNetworkLogValue(ex.Message)}");
                if (!isDestroyed)
                {
                    onError?.Invoke($"Disconnect error: {ex.Message}");
                }
            }
        }

        /// <summary>
        /// 断开连接（同步版本，用于Unity事件或Inspector调用）
        /// </summary>
        public void Disconnect()
        {
            _ = DisconnectAsync().ContinueWith(t =>
            {
                if (t.Exception != null)
                {
                    LogSdkError(
                        "runtime",
                        "disconnect",
                        $"event=background-failed error={FormatNetworkLogValue(t.Exception.InnerException?.Message)}");
                }
            }, TaskScheduler.FromCurrentSynchronizationContext());
        }

        /// <summary>
        /// 根据游戏状态调整KCP心跳间隔
        /// </summary>
        public void SetHeartbeatIntervalForGameState(GameState state)
        {
            switch (state)
            {
                case GameState.InBattle: kcpHeartbeatInterval = 0.1f; break;
                case GameState.InGame: kcpHeartbeatInterval = 1.0f; break;
                case GameState.Idle: kcpHeartbeatInterval = 5.0f; break;
            }

            if (dataPlaneTransport != null)
            {
                dataPlaneTransport.SetHeartbeatInterval(kcpHeartbeatInterval);
            }
            else if (KcpClient != null && enableKcpHeartbeat)
            {
                KcpClient.HeartbeatInterval = kcpHeartbeatInterval;
            }
        }


        /// <summary>
        /// 生成唯一的玩家ID
        /// </summary>
        private string GeneratePlayerId()
        {
            return $"player_{Guid.NewGuid().ToString("N").Substring(0, 8)}";
        }

        /// <summary>
        /// 心跳协程
        /// </summary>
        private System.Collections.IEnumerator HeartbeatCoroutine()
        {
            while (true)
            {
                yield return new WaitForSeconds(HeartbeatInterval);

                if (isDestroyed) yield break; // 对象已销毁，停止协程

                if (IsWebSocketConnected && WebSocketClient != null)
                {
                    try
                    {
                        // 发送心跳
                        _ = WebSocketClient.SendPingAsync();

                        onHeartbeatSent?.Invoke();
                        LogSdkInfo("heartbeat", "event=hosted-ping-sent");
                    }
                    catch (Exception ex)
                    {
                        if (!isDestroyed)
                        {
                            LogSdkError("heartbeat", $"event=hosted-ping-failed error={FormatNetworkLogValue(ex.Message)}");
                        }
                    }
                }
            }
        }

    }

    internal sealed class IGPAuthorizationPipeRequest
    {
        public string type = string.Empty;
        public string authToken = string.Empty;
        public string sessionToken = string.Empty;
        public string clientVersion = string.Empty;
    }

    internal sealed class IGPAuthorizationBootstrapPayload
    {
        public string launchTicket = string.Empty;
        public int gameId;
        public bool autoAuth = true;
        public string deviceIdHash = string.Empty;
        public bool offlineMode;
        public bool authorizationRequired = true;
        public string brokerToken = string.Empty;
        public string offlineLicensePublicKey = string.Empty;
        public string offlineLicenseKeyId = string.Empty;
        public string offlineLicenseAlgorithm = string.Empty;
    }

    internal sealed class IGPAuthorizationPolicy
    {
        public int refreshBeforeSeconds = 30;
        public bool offlineSupported;
    }

    internal sealed class IGPAuthorizationSessionResponse
    {
        public string sessionToken = string.Empty;
        public string expiresAt = string.Empty;
        public IGPAuthorizationPolicy? policy;
    }

    internal sealed class IGPAuthorizationOfflineLicenseResponse
    {
        public string offlineLicense = string.Empty;
        public string expiresAt = string.Empty;
        public IGPAuthorizationPolicy? policy;
    }

    internal sealed class IGPAuthorizationBootstrapEnvelope
    {
        public bool ok;
        public string error = string.Empty;
        public string type = string.Empty;
        public IGPAuthorizationBootstrapPayload? data;
    }

    internal sealed class IGPAuthorizationSessionEnvelope
    {
        public bool ok;
        public string error = string.Empty;
        public string type = string.Empty;
        public IGPAuthorizationSessionResponse? data;
    }

    internal sealed class IGPAuthorizationOfflineLicenseEnvelope
    {
        public bool ok;
        public string error = string.Empty;
        public string type = string.Empty;
        public IGPAuthorizationOfflineLicenseResponse? data;
    }

    internal sealed class IGPAuthorizationJwtHeader
    {
        public string alg = string.Empty;
        public string kid = string.Empty;
        public string typ = string.Empty;
    }

    internal sealed class IGPAuthorizationJwtPayload
    {
        public string sub = string.Empty;
        public int gameId;
        public string lid = string.Empty;
        public string jti = string.Empty;
        public string type = string.Empty;
        public string deviceIdHash = string.Empty;
        public long exp;
    }

    internal sealed class IGPAuthorizationCacheRecord
    {
        public string offlineLicense = string.Empty;
        public string expiresAt = string.Empty;
        public string deviceIdHash = string.Empty;
        public int gameId;
        public string publicKey = string.Empty;
        public string keyId = string.Empty;
        public string algorithm = string.Empty;
        public string clientVersion = string.Empty;
        public string savedAt = string.Empty;
    }

    internal sealed class IGPAuthorizationCacheEnvelope
    {
        public int version = 1;
        public string protection = string.Empty;
        public string payload = string.Empty;
    }

    internal static class MissingStateDefaultSentinel
    {
        public static readonly object Value = new object();
    }

    internal sealed class StaleHostedDataPlaneAttemptException : Exception
    {
    }
}
