#nullable enable
using IGP.UnitySDK.Models;
using IGP.UnitySDK;
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;
#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
    {

        /// <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 appId = ResolveDesktopAppId(appIdOverride);
                if (!appId.HasValue)
                {
                    LogSdkWarning("hosted", "bootstrap", "event=request-skipped reason=app-id-missing");
                    return false;
                }

                var desktop = runtimeContext?.Desktop
                    ?? throw new IGPSDKException("Core desktop gateway is unavailable");
                if (!desktop.SupportsCapability(IGPDesktopHostedCommands.CapabilityFieldNumber))
                {
                    throw new IGPSDKException(
                        "Hosted bootstrap is not available on the current desktop session",
                        IGPMultiplayerErrorCodes.ERR_DESKTOP_SESSION_CAPABILITY_MISSING);
                }

                var bootstrap = await IGPDesktopHostedCommands.RequestBootstrapAsync(
                    desktop,
                    appId.Value,
                    RuntimeCancellationToken);
                var launchOptions = BuildLaunchOptionsFromHostedBootstrap(bootstrap);
                return await TryBootstrapFromLaunchOptionsAsync(launchOptions);
            }
            catch (Exception ex)
            {
                LogSdkError("hosted", "bootstrap", $"event=request-failed error={FormatNetworkLogValue(ex.Message)}");
                PublishMultiplayerError(
                    "HOSTED_BOOTSTRAP_FAILED",
                    $"Hosted bootstrap request failed: {ex.Message}",
                    "desktop-hosted-bootstrap",
                    null,
                    false,
                    ex);
                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);

                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");
                }

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

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

        private static IGPLaunchOptions BuildLaunchOptionsFromHostedBootstrap(
            IGPDesktopHostedBootstrapContext bootstrap)
        {
            return new IGPLaunchOptions
            {
                launchTicket = bootstrap.launchTicket,
                bootstrapEndpoint = bootstrap.bootstrapEndpoint,
                bootstrapSecret = bootstrap.bootstrapSecret,
                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;
            }
            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;

            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",
                    IGPMultiplayerErrorCodes.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;
            terminalDataPlaneFailure = false;
            var previousRoom = CloneRoom(roomData);
            var hadRoom = roomData != null && !string.IsNullOrEmpty(roomData.id);
            var previousRoomId = roomData?.id ?? string.Empty;
            var previousPlayerId = playerId;
            var previousStatus = roomData?.status ?? string.Empty;
            var previousSceneGate = roomData?.sceneGate ?? string.Empty;
            var isSameRoom = hadRoom &&
                string.Equals(previousRoomId, snapshot.Room.id, StringComparison.Ordinal);

            var roomContextChanged = hadRoom &&
                (!string.Equals(previousRoomId, snapshot.Room.id, StringComparison.Ordinal) ||
                 (!string.IsNullOrEmpty(previousPlayerId) &&
                  !string.Equals(previousPlayerId, snapshot.CurrentPlayer.id, StringComparison.Ordinal)));
            if (roomContextChanged)
            {
                InvalidateRealtimeDataPlane("idle:room-context-changed");
                Network?.ResetSessionState();
            }

            hostedConnectionStatus = snapshot.ConnectionStatus;
            snapshot.Room.sceneGate = isSameRoom ? previousSceneGate : string.Empty;
            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;
            }
            if (!roomContextChanged &&
                isSameRoom &&
                IsHostedRoundResetBoundary(previousStatus, snapshot.Room.status))
            {
                ResetHostedRoundRuntimeState(previousStatus, snapshot.Room.status);
            }

            if (hadRoom && !string.Equals(previousRoomId, snapshot.Room.id, StringComparison.Ordinal))
            {
                RoomChanged?.Invoke(new IGPRoomChangedEvent(
                    IGPRoomChangeKind.Left,
                    previousRoom,
                    null,
                    null,
                    IGPRoomChangeFlags.None,
                    "room-switched"));
            }

            if (!hadRoom || !string.Equals(previousRoomId, snapshot.Room.id, StringComparison.Ordinal))
            {
                PublishRoomChangedIfNeeded(
                    null,
                    IGPRoomChangeKind.Joined,
                    "hosted-snapshot",
                    IGPRoomChangeFlags.Players |
                    IGPRoomChangeFlags.Host |
                    IGPRoomChangeFlags.Status |
                    IGPRoomChangeFlags.Map |
                    IGPRoomChangeFlags.SceneGate |
                    IGPRoomChangeFlags.Teams |
                    IGPRoomChangeFlags.Metadata);
            }
            else
            {
                PublishRoomChangedIfNeeded(previousRoom, IGPRoomChangeKind.Updated, "hosted-snapshot");
            }

            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 (reliableTransport != null || currentDataPlaneMode != IGPDataPlaneMode.None)
            {
                LogSdkInfo(
                    "hosted",
                    "round-boundary",
                    $"event=data-plane-reset previousStatus={FormatNetworkLogValue(previousStatus)} " +
                    $"currentStatus={FormatNetworkLogValue(currentStatus)}");
            }

            InvalidateRealtimeDataPlane("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 void HandleHostedRoomEvent(IGPHostSessionRoomEvent roomEvent)
        {
            if (roomEvent == null)
            {
                return;
            }

            // The legacy room event is intentionally not surfaced. The accompanying
            // hosted snapshot is the authoritative source for RoomChanged.
        }

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

        private void HandleHostedSessionError(IGPMultiplayerErrorEvent error)
        {
            if (!isDestroyed)
            {
                LogSdkError(
                    "hosted-session",
                    $"event=error code={FormatNetworkLogValue(error.Code)} message={FormatNetworkLogValue(error.Message)}");
                ErrorOccurred?.Invoke(error);
            }
        }
    }
}
