#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 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>通过可靠 Game data plane 结束当前对局。</summary>
        public async Task FinishHostedGameAsync()
        {
            await 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,
            CancellationToken cancellationToken = default)
        {
            if (!initializeRequested)
            {
                LogSdkDebug("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;
            }

            CancellationToken effectiveCancellationToken = cancellationToken.CanBeCanceled
                ? cancellationToken
                : RuntimeCancellationToken;
            return await TryBootstrapFromLaunchOptionsAsync(launchOptions, effectiveCancellationToken);
        }

        /// <summary>
        /// 通过 Core 的统一访问能力领取当前 hosted room，并连接 hosted session。
        /// </summary>
        private async Task<bool> TryAttachHostedRoomAsync(CancellationToken cancellationToken = default)
        {
            if (!initializeRequested)
            {
                LogSdkDebug("hosted", "bootstrap", "event=request-skipped reason=not-initialized");
                return false;
            }

            if (IsHostedSessionAttached)
            {
                return true;
            }

            if (hostedBootstrapRequestInFlight)
            {
                return false;
            }

            hostedBootstrapRequestInFlight = true;
            try
            {
                CancellationToken effectiveCancellationToken = cancellationToken.CanBeCanceled
                    ? cancellationToken
                    : RuntimeCancellationToken;
                var bootstrap = await (multiplayerAccessProvider
                    ?? throw new IGPSDKException("Core multiplayer access is unavailable"))
                    .AcquireHostedBootstrapAsync(effectiveCancellationToken);
                if (bootstrap == null)
                {
                    LogSdkDebug("hosted", "bootstrap", "event=request-skipped reason=no-hosted-room");
                    return false;
                }
                var launchOptions = BuildLaunchOptionsFromHostedBootstrap(bootstrap);
                return await TryBootstrapFromLaunchOptionsAsync(launchOptions, effectiveCancellationToken);
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                if (runtimeCreationInProgress)
                {
                    LogSdkWarning("hosted", "bootstrap", $"event=request-failed error={FormatNetworkLogValue(ex.Message)}");
                }
                else
                {
                    LogSdkError("hosted", "bootstrap", $"event=request-failed error={FormatNetworkLogValue(ex.Message)}");
                    PublishMultiplayerError(
                        "HOSTED_BOOTSTRAP_FAILED",
                        $"Hosted bootstrap request failed: {ex.Message}",
                        "core-room-access",
                        null,
                        false,
                        ex);
                }
                return false;
            }
            finally
            {
                hostedBootstrapRequestInFlight = false;
            }
        }

        private async Task<bool> TryBootstrapFromLaunchOptionsAsync(
            IGPLaunchOptions launchOptions,
            CancellationToken cancellationToken = default)
        {
            if (!HasHostedLaunchContext(launchOptions))
            {
                return false;
            }

            try
            {
                var bridgeClient = new IGPHostBridgeClient(
                    launchOptions.bootstrapEndpoint,
                    launchOptions.bootstrapSecret);
                CancellationToken effectiveCancellationToken = cancellationToken.CanBeCanceled
                    ? cancellationToken
                    : RuntimeCancellationToken;
                var redeem = await bridgeClient.RedeemLaunchTicketAsync(
                    launchOptions.launchTicket,
                    effectiveCancellationToken);

                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,
                    effectiveCancellationToken);

                pendingLaunchOptions = null;
                terminalDataPlaneFailure = false;
                return true;
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                if (runtimeCreationInProgress)
                {
                    LogSdkWarning("hosted", "launch-ticket", $"event=bootstrap-failed error={FormatNetworkLogValue(ex.Message)}");
                }
                else
                {
                    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(
            HostedBootstrapContext 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,
            CancellationToken cancellationToken = default)
        {
            EnsureExtensionInitialized();

            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
            {
                CancellationToken effectiveCancellationToken = cancellationToken.CanBeCanceled
                    ? cancellationToken
                    : RuntimeCancellationToken;
                await sessionClient.ConnectAsync(currentRoomId, playerId, effectiveCancellationToken);
                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("runtime-detach");
            if (clearConnectionInfo)
            {
                currentHostedSessionEndpoint = string.Empty;
                currentHostedSessionSecret = string.Empty;
            }
        }

        private IGPHostSessionClient EnsureHostedSession()
        {
            EnsureExtensionInitialized();

            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 void HandleHostedRoomSnapshot(IGPHostSessionSnapshotEvent snapshot)
        {
            if (snapshot.Room == null || snapshot.CurrentPlayer == null)
            {
                return;
            }

            ApplyHostedRoomSnapshot(snapshot, "hosted-snapshot");
        }

        private void ApplyHostedRoomSnapshot(IGPHostSessionSnapshotEvent snapshot, string changeReason)
        {
            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,
                    changeReason,
                    IGPRoomChangeFlags.Players |
                    IGPRoomChangeFlags.Host |
                    IGPRoomChangeFlags.Status |
                    IGPRoomChangeFlags.Map |
                    IGPRoomChangeFlags.SceneGate |
                    IGPRoomChangeFlags.Teams |
                    IGPRoomChangeFlags.Metadata);
            }
            else
            {
                PublishRoomChangedIfNeeded(previousRoom, IGPRoomChangeKind.Updated, changeReason);
            }

            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))
                {
                    _ = DisconnectInternalAsync("hosted-session-detached");
                }
            }
        }

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