#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
    {

        /// <summary>
        /// 通过宿主控制通道设置准备状态。
        /// </summary>
        public async Task SetReadyAsync(bool isReady)
        {
            var session = EnsureHostedSession();
            await session.SetReadyAsync(isReady, RuntimeCancellationToken);
        }

        /// <summary>
        /// 通过宿主控制通道离开当前房间。
        /// </summary>
        public async Task LeaveHostedRoomAsync()
        {
            var session = EnsureHostedSession();
            await session.LeaveRoomAsync(RuntimeCancellationToken);
            await DisconnectAsync();
        }

        /// <summary>
        /// 通过宿主控制通道开始游戏。
        /// </summary>
        public async Task StartHostedGameAsync()
        {
            var session = EnsureHostedSession();
            await session.StartGameAsync(RuntimeCancellationToken);
        }

        /// <summary>
        /// 通过宿主控制通道结束当前对局。
        /// </summary>
        public async Task FinishHostedGameAsync()
        {
            var session = EnsureHostedSession();
            await session.FinishGameAsync(RuntimeCancellationToken);
        }

        /// <summary>
        /// 通过宿主控制通道请求再来一局。
        /// </summary>
        public async Task RequestRematchAsync()
        {
            var session = EnsureHostedSession();
            await session.RematchGameAsync(RuntimeCancellationToken);
        }

        /// <summary>
        /// 通过宿主控制通道切换当前玩家队伍。
        /// </summary>
        public async Task ChangeHostedTeamAsync(string teamId)
        {
            var session = EnsureHostedSession();
            await session.ChangeTeamAsync(teamId, RuntimeCancellationToken);
        }

        /// <summary>
        /// 主动刷新宿主房间快照。
        /// </summary>
        public async Task RefreshHostedRoomAsync()
        {
            var session = EnsureHostedSession();
            await session.RefreshRoomAsync(RuntimeCancellationToken);
        }

        /// <summary>
        /// 解析启动参数并自动兑换启动票据。
        /// </summary>
        public async Task<bool> TryBootstrapFromLaunchTicketAsync(string[]? launchArgs = null)
        {
            if (!initializeRequested)
            {
                LogSdkInfo("hosted", "launch-ticket", "event=bootstrap-skipped reason=not-initialized");
                return false;
            }

            IGPLaunchOptions? launchOptions = pendingLaunchOptions;
            if (launchArgs != null)
            {
                if (!IGPLaunchArgsParser.TryParse(launchArgs, out launchOptions))
                {
                    return false;
                }
            }
            else if (launchOptions == null && !IGPLaunchArgsParser.TryParse(Environment.GetCommandLineArgs(), out launchOptions))
            {
                return false;
            }

            if (launchOptions == null || string.IsNullOrEmpty(launchOptions.launchTicket))
            {
                return false;
            }

            return await TryBootstrapFromLaunchOptionsAsync(launchOptions);
        }

        /// <summary>
        /// 从已附着的 desktop session 补领 hosted bootstrap，并连接 hosted session。
        /// </summary>
        public async Task<bool> TryRequestHostedBootstrapFromDesktopSessionAsync(int? appIdOverride = null)
        {
            if (!initializeRequested)
            {
                LogSdkInfo("hosted", "bootstrap", "event=request-skipped reason=not-initialized");
                return false;
            }

            if (IsHostedSessionAttached)
            {
                return true;
            }

            if (hostedBootstrapRequestInFlight)
            {
                return false;
            }

            hostedBootstrapRequestInFlight = true;
            try
            {
                var initializationTask = sdkInitializationTask;
                if (!sdkInitialized && initializationTask != null && !initializationTask.IsCompleted)
                {
                    var initialized = await initializationTask;
                    if (!initialized)
                    {
                        return false;
                    }

                    if (IsHostedSessionAttached)
                    {
                        return true;
                    }
                }

                var appId = ResolveDesktopAppId(appIdOverride) ??
                    (desktopSessionClient?.CurrentSession?.AppId > 0
                        ? desktopSessionClient.CurrentSession.AppId
                        : null);
                if (!appId.HasValue)
                {
                    LogSdkWarning("hosted", "bootstrap", "event=request-skipped reason=app-id-missing");
                    return false;
                }

                var session = await EnsureDesktopSessionAsync(appId.Value);
                EnsureDesktopCapability(
                    currentDesktopCapabilities?.hostedBootstrap ?? false,
                    "Hosted bootstrap is not available on the current desktop session");

                var bootstrap = await session.RequestHostedBootstrapAsync(appId.Value, RuntimeCancellationToken);
                var launchOptions = BuildLaunchOptionsFromHostedBootstrap(bootstrap);
                return await TryBootstrapFromLaunchOptionsAsync(launchOptions);
            }
            catch (Exception ex)
            {
                LogSdkError("hosted", "bootstrap", $"event=request-failed error={FormatNetworkLogValue(ex.Message)}");
                onError?.Invoke($"Hosted bootstrap request failed: {ex.Message}");
                return false;
            }
            finally
            {
                hostedBootstrapRequestInFlight = false;
            }
        }

        private async Task<bool> TryBootstrapFromLaunchOptionsAsync(IGPLaunchOptions launchOptions)
        {
            if (!HasHostedLaunchContext(launchOptions))
            {
                return false;
            }

            try
            {
                var bridgeClient = new IGPHostBridgeClient(
                    launchOptions.bootstrapEndpoint,
                    launchOptions.bootstrapSecret);
                var redeem = await bridgeClient.RedeemLaunchTicketAsync(launchOptions.launchTicket);

                if (!string.IsNullOrEmpty(redeem.serverUrl) &&
                    !string.Equals(ServerUrl, redeem.serverUrl, StringComparison.OrdinalIgnoreCase))
                {
                    ServerUrl = redeem.serverUrl;
                    InitializeSDKClients();
                }

                currentRoomId = redeem.roomId;
                currentRoomCode = redeem.roomCode;
                sessionToken = redeem.token;
                playerId = redeem.playerId;
                if (redeem.player != null && !string.IsNullOrEmpty(redeem.player.name))
                {
                    playerName = redeem.player.name;
                }

                if (string.IsNullOrEmpty(redeem.sessionEndpoint) || string.IsNullOrEmpty(redeem.sessionSecret))
                {
                    throw new IGPSDKException("Incomplete hosted bootstrap response");
                }

                ApplyNegotiatedTransportOptions(
                    redeem.reliableMessageMaxBytes,
                    redeem.reliableChunkMaxBytes,
                    redeem.kcpDataPlanePayloadMaxBytes,
                    redeem.kcpFrameMaxBytes);

                await AttachHostedSessionAsync(redeem.sessionEndpoint, redeem.sessionSecret);

                if (launchOptions.autoConnect)
                {
                    await ConnectHostedRealtimeAsync(currentRoomId, currentRoomCode);
                }

                pendingLaunchOptions = null;
                return true;
            }
            catch (Exception ex)
            {
                LogSdkError("hosted", "launch-ticket", $"event=bootstrap-failed error={FormatNetworkLogValue(ex.Message)}");
                onError?.Invoke($"Launch ticket bootstrap failed: {ex.Message}");
                return false;
            }
        }

        private static IGPLaunchOptions BuildLaunchOptionsFromHostedBootstrap(
            IGPDesktopHostedBootstrapContext bootstrap)
        {
            return new IGPLaunchOptions
            {
                launchTicket = bootstrap.launchTicket,
                bootstrapEndpoint = bootstrap.bootstrapEndpoint,
                bootstrapSecret = bootstrap.bootstrapSecret,
                autoConnect = bootstrap.autoConnect,
                appId = bootstrap.appId > 0 ? bootstrap.appId : null,
            };
        }

        private async Task AttachHostedSessionAsync(string sessionEndpoint, string sessionSecret)
        {
            EnsureSDKInitialized();

            await DetachHostedSessionAsync(clearConnectionInfo: false);

            if (string.IsNullOrWhiteSpace(currentRoomId))
            {
                throw new IGPSDKException("Cannot attach hosted session without room context");
            }

            if (string.IsNullOrWhiteSpace(playerId))
            {
                throw new IGPSDKException("Cannot attach hosted session without player context");
            }

            var sessionClient = new IGPHostSessionClient(sessionEndpoint, sessionSecret)
            {
                LogLevel = LogLevel
            };
            sessionClient.RoomSnapshotReceived += HandleHostedRoomSnapshot;
            sessionClient.RoomEventReceived += HandleHostedRoomEvent;
            sessionClient.Detached += HandleHostedSessionDetached;
            sessionClient.ErrorOccurred += HandleHostedSessionError;

            try
            {
                await sessionClient.ConnectAsync(currentRoomId, playerId, RuntimeCancellationToken);
                hostSessionClient = sessionClient;
                currentHostedSessionEndpoint = sessionEndpoint;
                currentHostedSessionSecret = sessionSecret;
                WebSocketClient?.AttachHostedSession(sessionClient, currentRoomId, playerId);
                WebSocketClient?.UpdateHostedConnectionState(string.Equals(
                    hostedConnectionStatus,
                    "connected",
                    StringComparison.OrdinalIgnoreCase));
            }
            catch
            {
                sessionClient.RoomSnapshotReceived -= HandleHostedRoomSnapshot;
                sessionClient.RoomEventReceived -= HandleHostedRoomEvent;
                sessionClient.Detached -= HandleHostedSessionDetached;
                sessionClient.ErrorOccurred -= HandleHostedSessionError;
                sessionClient.Dispose();
                throw;
            }
        }

        private async Task DetachHostedSessionAsync(bool clearConnectionInfo = true)
        {
            if (hostSessionClient == null)
            {
                if (clearConnectionInfo)
                {
                    currentHostedSessionEndpoint = string.Empty;
                    currentHostedSessionSecret = string.Empty;
                }
                return;
            }

            var sessionClient = hostSessionClient;
            hostSessionClient = null;
            WebSocketClient?.ClearHostedSession();

            sessionClient.RoomSnapshotReceived -= HandleHostedRoomSnapshot;
            sessionClient.RoomEventReceived -= HandleHostedRoomEvent;
            sessionClient.Detached -= HandleHostedSessionDetached;
            sessionClient.ErrorOccurred -= HandleHostedSessionError;
            await sessionClient.DisconnectAsync();
            if (clearConnectionInfo)
            {
                currentHostedSessionEndpoint = string.Empty;
                currentHostedSessionSecret = string.Empty;
            }
        }

        private IGPHostSessionClient EnsureHostedSession()
        {
            EnsureSDKInitialized();

            if (hostSessionClient == null || !hostSessionClient.IsAttached)
            {
                throw new IGPSDKException(
                    "Room context is required before hosted APIs can be used",
                    IGPErrorCodes.ERR_ROOM_CONTEXT_REQUIRED);
            }

            return hostSessionClient;
        }

        private T ParseDesktopCommandPayload<T>(
            IGPDesktopSessionCommandResult result,
            Func<T> fallbackFactory)
        {
            if (!string.IsNullOrWhiteSpace(result.ContentJson))
            {
                try
                {
                    var parsed = JsonConvert.DeserializeObject<T>(result.ContentJson);
                    if (parsed != null)
                    {
                        return parsed;
                    }
                }
                catch (Exception ex)
                {
                    throw new IGPSDKException($"Failed to parse desktop command payload: {ex.Message}", ex);
                }
            }

            return fallbackFactory();
        }

        private void HandleHostedRoomSnapshot(IGPHostSessionSnapshotEvent snapshot)
        {
            if (snapshot.Room == null || snapshot.CurrentPlayer == null)
            {
                return;
            }

            hostedDisconnectRequested = false;
            var hadRoom = roomData != null && !string.IsNullOrEmpty(roomData.id);
            var previousRoomId = roomData?.id ?? string.Empty;
            var previousStatus = roomData?.status ?? string.Empty;
            var previousMapPublicId = roomData?.mapPublicId ?? string.Empty;
            var previousMapVersionId = roomData?.mapVersionId;

            if (hadRoom && !string.Equals(previousRoomId, snapshot.Room.id, StringComparison.Ordinal))
            {
            }

            hostedConnectionStatus = snapshot.ConnectionStatus;
            roomData = snapshot.Room;
            currentPlayerData = snapshot.CurrentPlayer;
            currentRoomId = snapshot.Room.id;
            currentRoomCode = snapshot.Room.code;
            playerId = snapshot.CurrentPlayer.id;
            if (!string.IsNullOrEmpty(snapshot.CurrentPlayer.name))
            {
                playerName = snapshot.CurrentPlayer.name;
            }
            WebSocketClient?.UpdateHostedContext(currentRoomId, playerId);
            WebSocketClient?.UpdateHostedConnectionState(string.Equals(
                hostedConnectionStatus,
                "connected",
                StringComparison.OrdinalIgnoreCase));

            var isSameRoom = hadRoom &&
                string.Equals(previousRoomId, snapshot.Room.id, StringComparison.Ordinal);
            if (isSameRoom && IsHostedRoundResetBoundary(previousStatus, snapshot.Room.status))
            {
                ResetHostedRoundRuntimeState(previousStatus, snapshot.Room.status);
            }

            if (!hadRoom || !string.Equals(previousRoomId, snapshot.Room.id, StringComparison.Ordinal))
            {
                onRoomJoined?.Invoke(snapshot.Room);
            }
            else
            {
                onRoomUpdated?.Invoke(snapshot.Room);
            }

            if (hadRoom &&
                string.Equals(previousRoomId, snapshot.Room.id, StringComparison.Ordinal) &&
                HasMapChanged(previousMapPublicId, previousMapVersionId, snapshot.Room))
            {
                onMapChanged?.Invoke(new IGPMapChangeData
                {
                    roomId = snapshot.Room.id,
                    previousMapPublicId = previousMapPublicId,
                    previousMapVersionId = previousMapVersionId,
                    currentMapPublicId = snapshot.Room.mapPublicId,
                    currentMapVersionId = snapshot.Room.mapVersionId,
                    room = snapshot.Room,
                });
            }

            if (!string.Equals(previousStatus, snapshot.Room.status, StringComparison.Ordinal) &&
                string.Equals(snapshot.Room.status, "playing", StringComparison.OrdinalIgnoreCase))
            {
                LogSdkInfo("hosted", "room-status", "event=changed status=playing");
            }
            else if (!string.Equals(previousStatus, snapshot.Room.status, StringComparison.Ordinal) &&
                string.Equals(snapshot.Room.status, "finished", StringComparison.OrdinalIgnoreCase))
            {
                LogSdkInfo("hosted", "room-status", "event=changed status=finished");
            }
        }

        private void ResetHostedRoundRuntimeState(string previousStatus, string currentStatus)
        {
            if (dataPlaneTransport != null || currentDataPlaneMode != IGPDataPlaneMode.None)
            {
                LogSdkInfo(
                    "hosted",
                    "round-boundary",
                    $"event=data-plane-reset previousStatus={FormatNetworkLogValue(previousStatus)} " +
                    $"currentStatus={FormatNetworkLogValue(currentStatus)}");
                ResetHostedDataPlaneState("idle:round-reset");
            }

            Network?.ResetSessionState();
        }

        private static bool IsHostedRoundResetBoundary(string previousStatus, string currentStatus)
        {
            if (!string.Equals(currentStatus, "waiting", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }

            return string.Equals(previousStatus, "finished", StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(previousStatus, "ended", StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(previousStatus, "playing", StringComparison.OrdinalIgnoreCase);
        }

        private static bool HasMapChanged(string previousMapPublicId, int? previousMapVersionId, Room currentRoom)
        {
            return !string.Equals(previousMapPublicId, currentRoom.mapPublicId, StringComparison.Ordinal) ||
                   previousMapVersionId != currentRoom.mapVersionId;
        }

        private void HandleHostedRoomEvent(IGPHostSessionRoomEvent roomEvent)
        {
            if (roomEvent == null)
            {
                return;
            }

            if (roomEvent.EventType == IGPHostSessionRoomEventType.HostTransferred)
            {
                if (roomData != null && !string.IsNullOrEmpty(roomEvent.CurrentHostId))
                {
                    roomData.hostId = roomEvent.CurrentHostId;
                }

                onHostTransferred?.Invoke(roomEvent.PreviousHostId, roomEvent.CurrentHostId);
            }

            HostedRoomEventReceived?.Invoke(roomEvent);
        }

        private void HandleHostedSessionDetached(string reason)
        {
            hostedConnectionStatus = "disconnected";
            UpdateHostedConnectionState(IGPHostedConnectionState.Disconnected);
            if (!isDestroyed)
            {
                LogSdkWarning("hosted-session", $"event=detached reason={FormatNetworkLogValue(reason)}");
                if (!hostedDisconnectRequested && !string.IsNullOrEmpty(currentRoomId))
                {
                    _ = DisconnectAsync();
                }
            }
        }

        private void HandleHostedSessionError(string code, string message)
        {
            if (!isDestroyed)
            {
                if (!IsHostedBusinessError(code))
                {
                    UpdateHostedConnectionState(IGPHostedConnectionState.ConnectionFailed);
                }

                LogSdkError(
                    "hosted-session",
                    $"event=error code={FormatNetworkLogValue(code)} message={FormatNetworkLogValue(message)}");
                onError?.Invoke($"Hosted session error: {message}");
            }
        }
    }
}
