#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
{
    public partial class IGPRuntimeManager
    {

        private async Task ConnectHostedRealtimeAsync(string roomId, string? roomCode = null)
        {
            try
            {
                EnsureSDKInitialized();
                hostedDisconnectRequested = false;
                EnsureHostedServerUrl();
                currentRoomId = roomId;
                currentRoomCode = roomCode ?? string.Empty;

                LogSdkInfo("room", "connect", $"event=started roomId={FormatNetworkLogValue(roomId)}");

                if (WebSocketClient == null)
                {
                    throw new InvalidOperationException("WebSocket client not initialized");
                }

                if (!IsHostedSessionAttached)
                {
                    throw new IGPSDKException(
                        "Room context is required before hosted realtime can be used",
                        IGPErrorCodes.ERR_ROOM_CONTEXT_REQUIRED);
                }

                await WaitForHostedRealtimeConnectionAsync();

                await EnsureHostedDataPlaneAttachedAsync();

                LogSdkInfo("room", "connect", $"event=succeeded roomId={FormatNetworkLogValue(roomId)}");
            }
            catch (Exception ex)
            {
                string errorMessage = ex.Message;

                // 为409冲突错误提供更友好的中文提示
                if (ex.Message.Contains("409") || ex.Message.Contains("Conflict"))
                {
                    LogSdkWarning("room", "connect", $"event=conflict roomId={FormatNetworkLogValue(roomId)}");

                    // 检查是否已经在房间中
                    if (!string.IsNullOrEmpty(currentRoomId) && currentRoomId == roomId)
                    {
                        errorMessage = "您已经连接到这个房间了！";
                        LogSdkInfo("room", "connect", $"event=skipped reason=already-connected roomId={FormatNetworkLogValue(roomId)}");
                        onRoomJoined?.Invoke(roomData); // 触发房间加入事件
                        return; // 不是真正的错误，直接返回
                    }
                    else if (!string.IsNullOrEmpty(currentRoomId))
                    {
                        errorMessage = $"您已经在房间 {currentRoomId} 中！请先离开当前房间再加入新房间。";
                        LogSdkWarning("room", "connect", $"event=conflict error={FormatNetworkLogValue(errorMessage)}");
                    }
                }

                LogSdkError(
                    "room",
                    "connect",
                    $"event=failed roomId={FormatNetworkLogValue(roomId)} error={FormatNetworkLogValue(errorMessage)}");
                onError?.Invoke($"Connection failed: {errorMessage}");
                throw;
            }
        }

        private async Task WaitForHostedRealtimeConnectionAsync()
        {
            if (WebSocketClient == null)
            {
                throw new IGPSDKException("WebSocket client not initialized");
            }

            if (WebSocketClient.IsConnected)
            {
                return;
            }

            UpdateHostedConnectionState(IGPHostedConnectionState.WebSocketConnecting);

            var readyTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);

            void HandleConnectionStateChanged(bool connected)
            {
                if (connected)
                {
                    readyTcs.TrySetResult(true);
                }
            }

            WebSocketClient.ConnectionStateChanged += HandleConnectionStateChanged;
            try
            {
                if (WebSocketClient.IsConnected)
                {
                    return;
                }

                var completed = await Task.WhenAny(
                    readyTcs.Task,
                    Task.Delay(HostedRealtimeReadyTimeout, RuntimeCancellationToken));

                if (completed == readyTcs.Task && WebSocketClient.IsConnected)
                {
                    return;
                }

                RuntimeCancellationToken.ThrowIfCancellationRequested();

                throw new IGPSDKException(
                    "Curio desktop hosted realtime connection is not ready yet. " +
                    $"sessionAttached={IsHostedSessionAttached}, " +
                    $"connectionState={hostedConnectionStatus}, " +
                    $"roomId={currentRoomId}, " +
                    $"waited={HostedRealtimeReadyTimeout.TotalSeconds:F0}s");
            }
            finally
            {
                WebSocketClient.ConnectionStateChanged -= HandleConnectionStateChanged;
            }
        }

        private void EnsureHostedServerUrl()
        {
            if (!string.IsNullOrWhiteSpace(serverUrl))
            {
                return;
            }

            throw new IGPSDKException(
                "Hosted bootstrap did not provide a server endpoint",
                IGPErrorCodes.ERR_ROOM_CONTEXT_REQUIRED);
        }

        private void ApplyGameReadyStateSet(Message message)
        {
            if (message == null ||
                !string.Equals(message.type, "state_set", StringComparison.OrdinalIgnoreCase) ||
                !string.Equals(ExtractStringFromContent(message.content, "scope"), "player", StringComparison.OrdinalIgnoreCase) ||
                !string.Equals(ExtractStringFromContent(message.content, "key"), GameReadyStateKey, StringComparison.Ordinal))
            {
                return;
            }

            var targetPlayerId = ExtractStringFromContent(message.content, "playerId");
            if (string.IsNullOrWhiteSpace(targetPlayerId))
            {
                targetPlayerId = message.playerId;
            }

            bool? isReady = null;
            if (message.content is JObject contentObject &&
                contentObject.TryGetValue("value", out var token) &&
                token.Type == JTokenType.Boolean)
            {
                isReady = token.Value<bool>();
            }
            else if (message.content is IDictionary<string, object> contentDictionary &&
                     contentDictionary.TryGetValue("value", out var value) &&
                     value is bool boolValue)
            {
                isReady = boolValue;
            }

            if (!isReady.HasValue ||
                roomData?.players == null ||
                !roomData.players.TryGetValue(targetPlayerId, out var targetPlayer))
            {
                return;
            }

            targetPlayer.state ??= new Dictionary<string, object>();
            targetPlayer.state[GameReadyStateKey] = isReady.Value;
            LogSdkInfo(
                "game-ready",
                "state-update",
                $"event=applied roomId={FormatNetworkLogValue(message.roomId)} " +
                $"playerId={FormatNetworkLogValue(targetPlayerId)} ready={isReady.Value}");
        }

        private bool TryHandleBusinessErrorMessage(Message message, bool fromDataPlane)
        {
            if (message == null || !string.Equals(message.type, "error", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }

            var code = ExtractStringFromContent(message.content, "code");
            if (string.IsNullOrWhiteSpace(code))
            {
                return false;
            }

            if (string.Equals(code, "STATE_NOT_FOUND", StringComparison.OrdinalIgnoreCase))
            {
                ApplyDefaultForMissingState(message.content);
                LogSdkWarning(
                    "hosted",
                    "business-state",
                    $"event=missing roomId={message.roomId} playerId={message.playerId} " +
                    $"scope={ExtractStringFromContent(message.content, "scope")} key={ExtractStringFromContent(message.content, "key")} " +
                    $"source={(fromDataPlane ? "data-plane" : "hosted-session")}");
                onMessageReceived?.Invoke(message.type, message.content);
                return true;
            }

            if (IsHostedBusinessError(code))
            {
                LogSdkWarning(
                    "hosted",
                    "business-error",
                    $"event=received code={code} roomId={message.roomId} playerId={message.playerId} " +
                    $"source={(fromDataPlane ? "data-plane" : "hosted-session")}");
                onMessageReceived?.Invoke(message.type, message.content);
                return true;
            }

            return false;
        }

        private static bool IsHostedBusinessError(string? code)
        {
            return string.Equals(code, "STATE_NOT_FOUND", StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(code, "INVALID_STATE_SCOPE", StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(code, "INVALID_REQUEST", StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(code, "FORBIDDEN", StringComparison.OrdinalIgnoreCase);
        }

        private void ApplyDefaultForMissingState(object? content)
        {
            var scope = ExtractStringFromContent(content, "scope");
            if (!string.Equals(scope, "global", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            var key = ExtractStringFromContent(content, "key");
            if (string.IsNullOrWhiteSpace(key))
            {
                return;
            }

            var defaultValue = GetDefaultGlobalStateValue(key);
            if (defaultValue == MissingStateDefaultSentinel.Value)
            {
                return;
            }

            roomData.globalState ??= new Dictionary<string, object>();
            roomData.globalState[key] = defaultValue!;
        }

        private object? GetDefaultGlobalStateValue(string key)
        {
            switch (key)
            {
                case "LobbyName":
                case "LobbyIntroduction":
                case "OwnerID":
                case "GameVersion":
                case "ArchiveID":
                case "ArchiveName":
                case "ArchiveHash":
                    return string.Empty;
                case "MiniGameInfo":
                    return null;
                case "MaxPlayerNumber":
                    return roomData?.maxPlayers ?? 0;
                case "GameIsRunning":
                case "IsRelease":
                    return false;
                case "GameModeID":
                    return 0;
                default:
                    return MissingStateDefaultSentinel.Value;
            }
        }

        private static string ExtractStringFromContent(object? content, string name)
        {
            if (content == null)
            {
                return string.Empty;
            }

            if (content is JObject jobj)
            {
                return jobj.Value<string>(name) ?? string.Empty;
            }

            if (content is IDictionary<string, object> dict &&
                dict.TryGetValue(name, out var dictValue))
            {
                return dictValue?.ToString() ?? string.Empty;
            }

            var property = content.GetType().GetProperty(name);
            return property?.GetValue(content)?.ToString() ?? string.Empty;
        }

        /// <summary>
        /// 通过宿主实时控制面发送自定义消息。
        /// </summary>
        public async Task SendMessageAsync(Message message)
        {
            if (WebSocketClient == null)
            {
                throw new IGPSDKException("IGP realtime client is not initialized");
            }

            await WebSocketClient.SendMessageAsync(message);
        }

        /// <summary>
        /// 通过宿主实时控制面发送 ping。
        /// </summary>
        public async Task SendPingAsync()
        {
            if (WebSocketClient == null)
            {
                throw new IGPSDKException("IGP realtime client is not initialized");
            }

            await WebSocketClient.SendPingAsync();
        }

        /// <summary>
        /// Sets the current player's in-game ready state in Arena.
        /// This state is independent of room isReady and Mirror NetworkClient.ready.
        /// </summary>
        public async Task SetGameReadyAsync(
            bool isReady,
            CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            LogSdkInfo(
                "game-ready",
                "set",
                $"event=started roomId={FormatNetworkLogValue(currentRoomId)} " +
                $"playerId={FormatNetworkLogValue(playerId)} ready={isReady}");
            await SetStateAsync("player", GameReadyStateKey, isReady);

            roomData.players[playerId].state[GameReadyStateKey] = isReady;
            currentPlayerData.state[GameReadyStateKey] = isReady;
            LogSdkInfo(
                "game-ready",
                "set",
                $"event=succeeded roomId={FormatNetworkLogValue(currentRoomId)} " +
                $"playerId={FormatNetworkLogValue(playerId)} ready={isReady}");
        }

        /// <summary>
        /// Reads in-game ready state from the current Arena room snapshot.
        /// </summary>
        public IReadOnlyDictionary<string, bool> GetGameReadyStates(IEnumerable<string> playerIds)
        {
            LogSdkInfo(
                "game-ready",
                "get",
                $"event=started roomId={FormatNetworkLogValue(currentRoomId)}");
            var result = new Dictionary<string, bool>(StringComparer.Ordinal);
            var requestedCount = 0;
            foreach (var targetPlayerId in playerIds)
            {
                requestedCount++;
                if (!roomData.players.TryGetValue(targetPlayerId, out var targetPlayer) ||
                    !targetPlayer.state.TryGetValue(GameReadyStateKey, out var value))
                {
                    result[targetPlayerId] = false;
                    continue;
                }

                result[targetPlayerId] = (bool)value;
            }

            var readyCount = 0;
            foreach (var isReady in result.Values)
            {
                if (isReady)
                {
                    readyCount++;
                }
            }

            LogSdkInfo(
                "game-ready",
                "get",
                $"event=completed roomId={FormatNetworkLogValue(currentRoomId)} " +
                $"requestedCount={requestedCount} playerCount={result.Count} readyCount={readyCount}");
            return result;
        }

        /// <summary>
        /// 设置指定作用域的状态。
        /// </summary>
        public async Task SetStateAsync(string scope, string key, object? value, string? targetPlayerId = null, bool reliable = true)
        {
            if (WebSocketClient == null)
            {
                throw new IGPSDKException("IGP realtime client is not initialized");
            }

            await WebSocketClient.SetStateAsync(scope, key, value, targetPlayerId);
        }

        /// <summary>
        /// 获取指定作用域的状态。
        /// </summary>
        public async Task GetStateAsync(string scope, string key, string? targetPlayerId = null)
        {
            if (WebSocketClient == null)
            {
                throw new IGPSDKException("IGP realtime client is not initialized");
            }

            await WebSocketClient.GetStateAsync(scope, key, targetPlayerId);
        }

        /// <summary>
        /// 重置指定作用域的状态。
        /// </summary>
        public async Task ResetStateAsync(string scope, string[]? keysToExclude = null)
        {
            if (WebSocketClient == null)
            {
                throw new IGPSDKException("IGP realtime client is not initialized");
            }

            await WebSocketClient.ResetStateAsync(scope, keysToExclude);
        }

        /// <summary>
        /// 设置全局状态
        /// </summary>
        /// <param name="key">状态键</param>
        /// <param name="value">状态值</param>
        public async Task SetGlobalStateAsync(string key, object value)
        {
            await SetStateAsync("global", key, value);
        }

        /// <summary>
        /// 设置玩家状态
        /// </summary>
        /// <param name="key">状态键</param>
        /// <param name="value">状态值</param>
        public async Task SetPlayerStateAsync(string key, object value)
        {
            await SetStateAsync("player", key, value);
        }
    }
}
