#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 void TryRecoverDesktopAttachIfNeeded()
        {
            if (isDestroyed || !DesktopSessionAutoAttach)
            {
                return;
            }

            if (desktopAttachRetryInFlight)
            {
                return;
            }

            if (!nextDesktopAttachRetryAtUtc.HasValue ||
                DateTime.UtcNow < nextDesktopAttachRetryAtUtc.Value)
            {
                return;
            }

            if (IsDesktopSessionAttached)
            {
                nextDesktopAttachRetryAtUtc = null;
                return;
            }

            if (!CanRetryDesktopAttach())
            {
                nextDesktopAttachRetryAtUtc = null;
                return;
            }

            desktopAttachRetryInFlight = true;
            nextDesktopAttachRetryAtUtc = null;
            _ = RunDesktopAttachRetryAsync();
        }

        private async Task RunDesktopAttachRetryAsync()
        {
            try
            {
                var attached = await TryAttachDesktopSessionAsync();
                if (!attached && !isDestroyed && CanRetryDesktopAttach())
                {
                    // 失败则安排下一轮退避重试，避免死循环但保证持续自愈。
                    nextDesktopAttachRetryAtUtc = DateTime.UtcNow.AddSeconds(DesktopAttachRetryBackoffSeconds);
                }
                else if (attached)
                {
                    // attach 回到正常之后，立刻排程一次授权恢复。
                    ScheduleAuthorizationRecoverIfNeeded();
                }
            }
            catch (Exception ex)
            {
                LogSdkWarning("desktop-session", "recovery", $"event=attach-failed error={FormatNetworkLogValue(ex.Message)}");
                if (!isDestroyed && CanRetryDesktopAttach())
                {
                    nextDesktopAttachRetryAtUtc = DateTime.UtcNow.AddSeconds(DesktopAttachRetryBackoffSeconds);
                }
            }
            finally
            {
                desktopAttachRetryInFlight = false;
            }
        }

        internal async Task<bool> TryAttachDesktopSessionAsync(int? appIdOverride = null)
        {
            if (!initializeRequested)
            {
                MarkDesktopSessionUnavailable(
                    IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                    "IGP SDK is not initialized");
                return false;
            }

            try
            {
                await AttachDesktopSessionAsync(appIdOverride);
                desktopSessionLastErrorCode = string.Empty;
                desktopSessionLastErrorMessage = string.Empty;
                return true;
            }
            catch (Exception ex)
            {
                CaptureDesktopAttachFailure(ex);
                LogSdkWarning(
                    "desktop-session",
                    "attach",
                    $"event=failed error={FormatNetworkLogValue(desktopSessionLastErrorMessage)}");
                return false;
            }
        }

        internal async Task<bool> TryLaunchDesktopAndRetryAttachAsync(int? appIdOverride = null)
        {
            if (!initializeRequested)
            {
                MarkDesktopSessionUnavailable(
                    IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                    "IGP SDK is not initialized");
                return false;
            }

            if (!CanRetryDesktopAttach())
            {
                return false;
            }

            var launchCommand = IGPDesktopSessionEnvironment.ResolveDesktopLaunchCommand(
                config?.desktopLaunchCommand,
                sdkEnvironment: SdkEnvironment);
            if (string.IsNullOrWhiteSpace(launchCommand))
            {
                MarkDesktopSessionUnavailable(
                    IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                    "Desktop launch command could not be resolved");
                LogSdkWarning("desktop-session", "launch", "event=retry-skipped reason=command-unresolved");
                return false;
            }

            try
            {
                var startInfo = IGPDesktopSessionEnvironment.CreateDesktopLaunchStartInfo(launchCommand);
                LogSdkInfo(
                    "desktop-session",
                    "launch",
                    $"event=started executable={FormatNetworkLogValue(startInfo.FileName)} arguments={FormatNetworkLogValue(startInfo.Arguments)}");
                Process.Start(startInfo);
            }
            catch (Exception ex)
            {
                MarkDesktopSessionUnavailable(
                    IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                    $"Failed to launch desktop: {ex.Message}");
                LogSdkWarning("desktop-session", "launch", $"event=failed error={FormatNetworkLogValue(ex.Message)}");
                return false;
            }

            for (var attempt = 0; attempt < 10; attempt++)
            {
                await Task.Delay(500, RuntimeCancellationToken);
                if (await TryAttachDesktopSessionAsync(appIdOverride))
                {
                    LogSdkInfo("desktop-session", "launch", "event=retry-succeeded attachState=attached");
                    return true;
                }

                if (!CanRetryDesktopAttach())
                {
                    LogSdkWarning(
                        "desktop-session",
                        "launch",
                        $"event=retry-stopped error={FormatNetworkLogValue(desktopSessionLastErrorMessage)}");
                    return false;
                }
            }

            if (string.IsNullOrWhiteSpace(desktopSessionLastErrorCode))
            {
                MarkDesktopSessionUnavailable(
                    IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                    "Desktop session is unavailable after launch retry");
            }

            LogSdkWarning(
                "desktop-session",
                "launch",
                $"event=retry-timeout error={FormatNetworkLogValue(desktopSessionLastErrorMessage)}");

            return false;
        }

        private async Task AttachDesktopSessionAsync(int? appIdOverride = null)
        {
            var request = BuildDesktopAttachRequest(appIdOverride);

            if (desktopSessionClient != null &&
                desktopSessionClient.IsAttached &&
                desktopSessionClient.CurrentSession != null &&
                desktopSessionClient.CurrentSession.AppId == request.AppId)
            {
                return;
            }

            await DetachDesktopSessionAsync();

            var client = new IGPDesktopSessionClient(DesktopPipeEndpoint);
            client.Detached += HandleDesktopSessionDetached;
            client.ErrorOccurred += HandleDesktopSessionError;
            client.ErrorDetailed += HandleDesktopSessionErrorDetailed;
            client.AntiAddictionChanged += HandleDesktopAntiAddictionChanged;
            client.HostedBootstrapAvailable += HandleDesktopHostedBootstrapAvailable;
            client.OfflineMapLaunchRequested += HandleDesktopOfflineMapLaunchRequested;

            try
            {
                var response = await client.ConnectAsync(request, RuntimeCancellationToken);
                desktopSessionClient = client;
                currentDesktopUserContext = new IGPDesktopUserContext
                {
                    userId = response.UserId,
                    accountId = response.AccountId,
                    loginState = response.LoginState,
                };
                currentDesktopCapabilities = response.Capabilities ?? new IGPDesktopCapabilitySet();
                currentDesktopUserProfile = response.UserProfile;
                currentAntiAddictionStatus = response.AntiAddictionStatus;
                desktopSessionLastErrorCode = string.Empty;
                desktopSessionLastErrorMessage = string.Empty;
                desktopSessionLastErrorCategory = string.Empty;
                desktopSessionLastChannelState = response.ChannelState ?? string.Empty;
                desktopSessionLastAttachState = response.AttachState ?? string.Empty;
                desktopSessionLastAttachSource = response.AttachSource ?? string.Empty;
                // attach 成功后清空 pending retry，避免 Update 再次触发。
                nextDesktopAttachRetryAtUtc = null;
            }
            catch
            {
                client.Detached -= HandleDesktopSessionDetached;
                client.ErrorOccurred -= HandleDesktopSessionError;
                client.ErrorDetailed -= HandleDesktopSessionErrorDetailed;
                client.AntiAddictionChanged -= HandleDesktopAntiAddictionChanged;
                client.HostedBootstrapAvailable -= HandleDesktopHostedBootstrapAvailable;
                client.OfflineMapLaunchRequested -= HandleDesktopOfflineMapLaunchRequested;
                client.Dispose();
                throw;
            }
        }

        private async Task DetachDesktopSessionAsync()
        {
            if (desktopSessionClient == null)
            {
                currentDesktopUserContext = null;
                currentDesktopCapabilities = null;
                currentDesktopUserProfile = null;
                currentAntiAddictionStatus = null;
                return;
            }

            var client = desktopSessionClient;
            desktopSessionClient = null;
            currentDesktopUserContext = null;
            currentDesktopCapabilities = null;
            currentDesktopUserProfile = null;
            currentAntiAddictionStatus = null;

            client.Detached -= HandleDesktopSessionDetached;
            client.ErrorOccurred -= HandleDesktopSessionError;
            client.ErrorDetailed -= HandleDesktopSessionErrorDetailed;
            client.AntiAddictionChanged -= HandleDesktopAntiAddictionChanged;
            client.HostedBootstrapAvailable -= HandleDesktopHostedBootstrapAvailable;
            client.OfflineMapLaunchRequested -= HandleDesktopOfflineMapLaunchRequested;
            await client.DisconnectAsync();
        }

        private async Task<IGPDesktopSessionClient> EnsureDesktopSessionAsync(int? appIdOverride = null)
        {
            EnsureSDKInitialized();

            if (desktopSessionClient != null &&
                desktopSessionClient.IsAttached &&
                desktopSessionClient.CurrentSession != null &&
                (!appIdOverride.HasValue || desktopSessionClient.CurrentSession.AppId == appIdOverride.Value))
            {
                return desktopSessionClient;
            }

            if (await TryAttachDesktopSessionAsync(appIdOverride))
            {
                return desktopSessionClient!;
            }

            if (await TryLaunchDesktopAndRetryAttachAsync(appIdOverride))
            {
                return desktopSessionClient!;
            }

            throw CreateDesktopUnavailableException();
        }

        private bool ShouldAutoAttachDesktopSessionOnStartup()
        {
            if (!initializeRequested)
            {
                return false;
            }

            if (!DesktopSessionAutoAttach)
            {
                return false;
            }

            if (!Application.isEditor)
            {
                return true;
            }

            return true;
        }

        private IGPDesktopSessionAttachRequest BuildDesktopAttachRequest(int? appIdOverride)
        {
            var appId = ResolveDesktopAppId(appIdOverride);
            if (!appId.HasValue)
            {
                throw new IGPSDKException(
                    "IGP appId is required before desktop capabilities can be used",
                    IGPErrorCodes.ERR_APP_ID_REQUIRED);
            }

            var executablePath = IGPDesktopSessionEnvironment.ResolveExecutablePath(
                config?.desktopExecutablePathDebugOverride,
                editorDetector: () => Application.isEditor);
            if (string.IsNullOrWhiteSpace(executablePath))
            {
                throw new IGPSDKException(
                    "Executable path is required before desktop capabilities can be used",
                    IGPErrorCodes.ERR_EXECUTABLE_PATH_REQUIRED);
            }
            else if (Application.isEditor && string.IsNullOrWhiteSpace(config?.desktopExecutablePathDebugOverride))
            {
                LogSdkInfo(
                    "desktop-session",
                    "attach",
                    "event=editor-executable-selected validation=app-id-and-account-permissions");
            }

            return new IGPDesktopSessionAttachRequest
            {
                AppId = appId.Value,
                ExecutablePath = executablePath,
                ProcessId = GetCurrentProcessId(),
                SdkVersion = ResolveSdkVersion(),
                EngineVersion = Application.unityVersion,
            };
        }

        private int? ResolveDesktopAppId(int? appIdOverride = null)
        {
            if (appIdOverride.HasValue && appIdOverride.Value > 0)
            {
                return appIdOverride.Value;
            }

            return IGPDesktopSessionEnvironment.TryResolveAppId(
                config?.appId ?? 0,
                pendingLaunchOptions?.appId,
                out var resolvedAppId)
                ? resolvedAppId
                : null;
        }

        private int GetCurrentProcessId()
        {
            if (Application.isEditor)
            {
                return 0;
            }

            try
            {
                using var process = Process.GetCurrentProcess();
                return process.Id;
            }
            catch
            {
                return 0;
            }
        }

        private string ResolveSdkVersion()
        {
            return IGPSdkVersion.PackageVersion;
        }

        private bool CanRetryDesktopAttach()
        {
            if (string.IsNullOrWhiteSpace(desktopSessionLastErrorCode))
            {
                return true;
            }

            // validation 类（appId / executable path 等）属于游戏配置错误，立刻告知用户而不是无限重试。
            if (IGPErrorCodes.IsValidationError(desktopSessionLastErrorCode))
            {
                return false;
            }

            // authorization 类同样不应该通过“重新 attach”自愈，需要上层走授权流程。
            if (IGPErrorCodes.IsAuthorizationError(desktopSessionLastErrorCode))
            {
                return false;
            }

            // channel / runtime / 以及经典的 DESKTOP_SESSION_REQUIRED 都允许继续重试。
            return true;
        }

        private void CaptureDesktopAttachFailure(Exception error)
        {
            if (error is IGPSDKException sdkException)
            {
                MarkDesktopSessionUnavailable(
                    string.IsNullOrWhiteSpace(sdkException.ErrorCode)
                        ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                        : sdkException.ErrorCode!,
                    sdkException.Message,
                    sdkException.Category,
                    sdkException.DesktopChannelState,
                    sdkException.DesktopAttachState);
                return;
            }

            MarkDesktopSessionUnavailable(
                IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                error.Message,
                IGPErrorCodes.CATEGORY_CHANNEL,
                channelState: null,
                attachState: null);
        }

        private void MarkDesktopSessionUnavailable(string errorCode, string message)
        {
            MarkDesktopSessionUnavailable(
                errorCode,
                message,
                IGPErrorCodes.ResolveCategory(errorCode),
                channelState: null,
                attachState: null);
        }

        private void MarkDesktopSessionUnavailable(
            string errorCode,
            string message,
            string? category,
            string? channelState,
            string? attachState)
        {
            currentDesktopUserContext = null;
            currentDesktopCapabilities = null;
            currentDesktopUserProfile = null;
            currentAntiAddictionStatus = null;
            desktopSessionLastErrorCode = errorCode ?? string.Empty;
            desktopSessionLastErrorMessage = message ?? string.Empty;
            desktopSessionLastErrorCategory = string.IsNullOrWhiteSpace(category)
                ? IGPErrorCodes.ResolveCategory(errorCode)
                : category!;

            if (!string.IsNullOrEmpty(channelState))
            {
                desktopSessionLastChannelState = channelState!;
            }

            if (!string.IsNullOrEmpty(attachState))
            {
                desktopSessionLastAttachState = attachState!;
            }

            ScheduleDesktopAttachRetryIfNeeded();
        }

        private void ScheduleDesktopAttachRetryIfNeeded()
        {
            if (isDestroyed)
            {
                return;
            }

            if (!DesktopSessionAutoAttach)
            {
                nextDesktopAttachRetryAtUtc = null;
                return;
            }

            if (!CanRetryDesktopAttach())
            {
                nextDesktopAttachRetryAtUtc = null;
                return;
            }

            nextDesktopAttachRetryAtUtc = DateTime.UtcNow.AddSeconds(DesktopAttachRetryBackoffSeconds);
        }

        private void ScheduleAuthorizationRecoverIfNeeded()
        {
            if (isDestroyed)
            {
                return;
            }

            // 只有在 desktop session 可用、且当前不是已在线授权时，才安排自愈重试。
            if (authorizationState == IGPAuthorizationState.AuthorizedOnline ||
                authorizationState == IGPAuthorizationState.Skipped)
            {
                nextAuthorizationRecoverAtUtc = null;
                return;
            }

            nextAuthorizationRecoverAtUtc = DateTime.UtcNow.AddSeconds(AuthorizationRecoverBackoffSeconds);
        }

        private IGPSDKException CreateDesktopUnavailableException()
        {
            var errorCode = string.IsNullOrWhiteSpace(desktopSessionLastErrorCode)
                ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                : desktopSessionLastErrorCode;
            var message = string.IsNullOrWhiteSpace(desktopSessionLastErrorMessage)
                ? "Desktop session is not attached"
                : desktopSessionLastErrorMessage;
            return new IGPSDKException(message, errorCode);
        }

        private void EnsureDesktopCapability(bool supported, string message)
        {
            if (!supported)
            {
                throw new IGPSDKException(
                    message,
                    IGPErrorCodes.ERR_DESKTOP_SESSION_CAPABILITY_MISSING);
            }
        }

        private bool HasHostedLaunchContext(IGPLaunchOptions? launchOptions)
        {
            return launchOptions != null &&
                   !string.IsNullOrWhiteSpace(launchOptions.launchTicket) &&
                   !string.IsNullOrWhiteSpace(launchOptions.bootstrapEndpoint) &&
                   !string.IsNullOrWhiteSpace(launchOptions.bootstrapSecret);
        }

        private void HandleDesktopSessionDetached(string reason)
        {
            currentAntiAddictionStatus = null;
            MarkDesktopSessionUnavailable(
                IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                string.IsNullOrWhiteSpace(reason) ? "Desktop session detached" : reason,
                IGPErrorCodes.CATEGORY_CHANNEL,
                channelState: "disconnected",
                attachState: "idle");

            // desktop 掉线通常是通道问题，对齐 GameMaker 的 TryRecoverDesktopAttach：排程下一次重试。
            ScheduleDesktopAttachRetryIfNeeded();
            // desktop 掉了之后授权当然也需要重走一次，排程一次恢复。
            ScheduleAuthorizationRecoverIfNeeded();

            if (!isDestroyed)
            {
                LogSdkWarning(
                    "desktop-session",
                    "detach",
                    $"event=detached error={FormatNetworkLogValue(desktopSessionLastErrorMessage)}");
            }
        }

        private void HandleDesktopHostedBootstrapAvailable(
            IGPDesktopHostedBootstrapAvailableEvent hostedBootstrapAvailable)
        {
            if (isDestroyed || hostedBootstrapAvailable == null || IsHostedSessionAttached)
            {
                return;
            }

            var attachedAppId = desktopSessionClient?.CurrentSession?.AppId;
            if (hostedBootstrapAvailable.appId > 0 &&
                attachedAppId.HasValue &&
                attachedAppId.Value > 0 &&
                hostedBootstrapAvailable.appId != attachedAppId.Value)
            {
                return;
            }

            _ = RunDesktopHostedBootstrapRequestAsync(hostedBootstrapAvailable.appId > 0
                ? hostedBootstrapAvailable.appId
                : attachedAppId);
        }

        private void HandleDesktopOfflineMapLaunchRequested(
            IGPDesktopOfflineMapLaunchRequestedEvent offlineMapLaunchRequested)
        {
            if (isDestroyed || offlineMapLaunchRequested == null)
            {
                return;
            }

            var attachedAppId = desktopSessionClient?.CurrentSession?.AppId;
            if (offlineMapLaunchRequested.appId > 0 &&
                attachedAppId.HasValue &&
                attachedAppId.Value > 0 &&
                offlineMapLaunchRequested.appId != attachedAppId.Value)
            {
                return;
            }

            onOfflineMapLaunchRequested?.Invoke(offlineMapLaunchRequested);
        }

        private async Task RunDesktopHostedBootstrapRequestAsync(int? appId)
        {
            await TryRequestHostedBootstrapFromDesktopSessionAsync(appId);
        }

        private void HandleDesktopSessionError(string code, string message)
        {
            // 兼容旧事件：只有 code + message 时按码推断分类。
            HandleDesktopSessionErrorCore(
                string.IsNullOrWhiteSpace(code) ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED : code,
                message,
                IGPErrorCodes.ResolveCategory(code),
                channelState: null,
                attachState: null);
        }

        private void HandleDesktopSessionErrorDetailed(IGPSDKException error)
        {
            if (error == null)
            {
                return;
            }

            HandleDesktopSessionErrorCore(
                string.IsNullOrWhiteSpace(error.ErrorCode)
                    ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                    : error.ErrorCode!,
                error.Message,
                error.Category,
                error.DesktopChannelState,
                error.DesktopAttachState);
        }

        private void HandleDesktopSessionErrorCore(
            string code,
            string message,
            string? category,
            string? channelState,
            string? attachState)
        {
            MarkDesktopSessionUnavailable(code, message, category, channelState, attachState);

            // 按分类驱动不同的自愈：
            //  - channel：安排 desktop attach retry。
            //  - authorization：安排一次授权状态恢复。
            //  - validation：不重试，交给游戏侧修正配置。
            //  - runtime：同时排程 desktop attach 和授权恢复作为兜底。
            var resolvedCategory = string.IsNullOrWhiteSpace(category)
                ? IGPErrorCodes.ResolveCategory(code)
                : category!;

            if (resolvedCategory == IGPErrorCodes.CATEGORY_AUTHORIZATION)
            {
                ScheduleAuthorizationRecoverIfNeeded();
            }
            else if (resolvedCategory == IGPErrorCodes.CATEGORY_CHANNEL ||
                     resolvedCategory == IGPErrorCodes.CATEGORY_RUNTIME)
            {
                ScheduleDesktopAttachRetryIfNeeded();
                ScheduleAuthorizationRecoverIfNeeded();
            }

            if (!isDestroyed)
            {
                LogSdkError(
                    "desktop-session",
                    "error",
                    $"event=received code={FormatNetworkLogValue(desktopSessionLastErrorCode)} " +
                    $"category={FormatNetworkLogValue(resolvedCategory)} error={FormatNetworkLogValue(message)}");
                onError?.Invoke($"Desktop session error: {message}");
            }
        }
    }
}
