#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;

using IGP.UnitySDK.Models;
using Newtonsoft.Json;

namespace IGP.UnitySDK
{
    /// <summary>
    /// Long-lived Windows named pipe client for room-independent desktop capabilities.
    /// </summary>
    public sealed class IGPDesktopSessionClient : IDisposable
    {
        private const int ConnectTimeoutMs = 5000;
        private const int MaxFramePayloadBytes = 8 * 1024 * 1024;

        private readonly object syncRoot = new object();
        private readonly string pipeEndpoint;
        private readonly SemaphoreSlim writeLock = new SemaphoreSlim(1, 1);
        private readonly Dictionary<string, TaskCompletionSource<IGPDesktopSessionCommandResult>> pendingRequests =
            new Dictionary<string, TaskCompletionSource<IGPDesktopSessionCommandResult>>();

        private NamedPipeClientStream? pipe;
        private CancellationTokenSource? lifetimeCts;
        private Task? readLoopTask;
        private TaskCompletionSource<IGPDesktopSessionAttachResponse>? attachCompletionSource;

        public IGPDesktopSessionClient(string pipeEndpoint)
        {
            this.pipeEndpoint = pipeEndpoint ?? string.Empty;
        }

        public bool IsAttached { get; private set; }

        public IGPDesktopSessionAttachResponse? CurrentSession { get; private set; }

        public event Action<string>? Detached;
        public event Action<string, string>? ErrorOccurred;
        public event Action<IGPAntiAddictionStatus>? AntiAddictionChanged;
        public event Action<IGPDesktopHostedBootstrapAvailableEvent>? HostedBootstrapAvailable;

        /// <summary>
        /// 携带完整错误上下文（category/channelState/attachState）的异常事件，
        /// 用于需要按分类自愈或按 channel/attach 状态调整重试策略的上层逻辑。
        /// </summary>
        public event Action<IGPSDKException>? ErrorDetailed;

        public async Task<IGPDesktopSessionAttachResponse> ConnectAsync(
            IGPDesktopSessionAttachRequest request,
            CancellationToken cancellationToken = default)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (string.IsNullOrWhiteSpace(pipeEndpoint))
            {
                throw new IGPSDKException("IGP desktop session endpoint is missing");
            }

            if (request.AppId <= 0)
            {
                throw new IGPSDKException("IGP appId is required for desktop session attach", IGP.UnitySDK.Models.IGPErrorCodes.ERR_APP_ID_REQUIRED);
            }

            await DisconnectAsync();

            var pipeName = ExtractPipeName(pipeEndpoint);
            pipe = new NamedPipeClientStream(
                ".",
                pipeName,
                PipeDirection.InOut,
                PipeOptions.Asynchronous);
            lifetimeCts = new CancellationTokenSource();
            attachCompletionSource = new TaskCompletionSource<IGPDesktopSessionAttachResponse>(
                TaskCreationOptions.RunContinuationsAsynchronously);

            try
            {
                await Task.Run(() => pipe.Connect(ConnectTimeoutMs), cancellationToken);
                cancellationToken.ThrowIfCancellationRequested();
                readLoopTask = ReadLoopAsync(pipe, lifetimeCts.Token);

                var attachPayload = IGPDesktopSessionProtocol.EncodeAttachRequest(request);
                await WriteFrameAsync(attachPayload, cancellationToken);

                using (cancellationToken.Register(() => attachCompletionSource.TrySetCanceled(cancellationToken)))
                {
                    var response = await attachCompletionSource.Task;
                    ValidateDesktopProtocolVersion(response);
                    CurrentSession = response;
                    return response;
                }
            }
            catch
            {
                await DisconnectAsync();
                throw;
            }
        }

        public async Task PingAsync(CancellationToken cancellationToken = default)
        {
            await SendCommandAsync(IGPDesktopSessionCommandType.PingDesktopSession, cancellationToken);
        }

        public async Task<IGPDesktopSessionCommandResult> UnlockAchievementAsync(
            string achievementKey,
            string? eventId = null,
            long? occurredAtUnixMs = null,
            int? appId = null,
            CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrWhiteSpace(achievementKey))
            {
                throw new ArgumentException("Achievement key is required", nameof(achievementKey));
            }

            var payload = JsonConvert.SerializeObject(new
            {
                eventId,
                occurredAt = occurredAtUnixMs,
                appId,
            });

            return await SendCommandAsync(
                IGPDesktopSessionCommandType.UnlockAchievement,
                cancellationToken,
                stringValue: achievementKey,
                contentJson: payload);
        }

        public async Task<IGPDesktopSessionCommandResult> ReportAchievementProgressAsync(
            string achievementKey,
            double progressValue,
            string? progressSourceKey = null,
            string? eventId = null,
            long? occurredAtUnixMs = null,
            int? appId = null,
            CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrWhiteSpace(achievementKey))
            {
                throw new ArgumentException("Achievement key is required", nameof(achievementKey));
            }

            var payload = JsonConvert.SerializeObject(new
            {
                progressValue,
                progressSourceKey,
                eventId,
                occurredAt = occurredAtUnixMs,
                appId,
            });

            return await SendCommandAsync(
                IGPDesktopSessionCommandType.ReportAchievementProgress,
                cancellationToken,
                stringValue: achievementKey,
                contentJson: payload);
        }

        public async Task<IGPDesktopSessionCommandResult> ClearAchievementsAsync(
            int? appId = null,
            CancellationToken cancellationToken = default)
        {
            var payload = appId.HasValue && appId.Value > 0
                ? JsonConvert.SerializeObject(new
                {
                    appId,
                })
                : null;

            return await SendCommandAsync(
                IGPDesktopSessionCommandType.ClearAchievements,
                cancellationToken,
                contentJson: payload);
        }

        public async Task<IGPDesktopCapabilitySet> GetDesktopCapabilitiesAsync(
            CancellationToken cancellationToken = default)
        {
            var result = await SendCommandAsync(
                IGPDesktopSessionCommandType.GetDesktopCapabilities,
                cancellationToken);

            if (string.IsNullOrWhiteSpace(result.ContentJson))
            {
                return CurrentSession?.Capabilities ?? new IGPDesktopCapabilitySet();
            }

            return JsonConvert.DeserializeObject<IGPDesktopCapabilitySet>(result.ContentJson)
                ?? new IGPDesktopCapabilitySet();
        }

        public async Task<IGPDesktopAuthorizationRequestResult> RequestGameAuthorizationAsync(
            int? appId = null,
            CancellationToken cancellationToken = default)
        {
            var payload = appId.HasValue && appId.Value > 0
                ? JsonConvert.SerializeObject(new
                {
                    appId = appId.Value,
                })
                : null;

            var result = await SendCommandAsync(
                IGPDesktopSessionCommandType.RequestGameAuthorization,
                cancellationToken,
                contentJson: payload);

            if (string.IsNullOrWhiteSpace(result.ContentJson))
            {
                return new IGPDesktopAuthorizationRequestResult();
            }

            return JsonConvert.DeserializeObject<IGPDesktopAuthorizationRequestResult>(result.ContentJson)
                ?? new IGPDesktopAuthorizationRequestResult();
        }

        public async Task<IGPDesktopHostedBootstrapContext> RequestHostedBootstrapAsync(
            int? appId = null,
            CancellationToken cancellationToken = default)
        {
            var payload = appId.HasValue && appId.Value > 0
                ? JsonConvert.SerializeObject(new
                {
                    appId = appId.Value,
                })
                : null;

            var result = await SendCommandAsync(
                IGPDesktopSessionCommandType.RequestHostedBootstrap,
                cancellationToken,
                contentJson: payload);

            if (string.IsNullOrWhiteSpace(result.ContentJson))
            {
                return new IGPDesktopHostedBootstrapContext();
            }

            return JsonConvert.DeserializeObject<IGPDesktopHostedBootstrapContext>(result.ContentJson)
                ?? new IGPDesktopHostedBootstrapContext();
        }

        public async Task<IGPDesktopUserAvatarInfo> GetDesktopUserAvatarInfoAsync(
            CancellationToken cancellationToken = default)
        {
            var result = await SendCommandAsync(
                IGPDesktopSessionCommandType.GetDesktopUserAvatarInfo,
                cancellationToken);

            return ParseCommandPayload(
                result.ContentJson,
                () => new IGPDesktopUserAvatarInfo());
        }

        public async Task<IGPDesktopUserAvatarImage> GetDesktopUserAvatarAsync(
            CancellationToken cancellationToken = default)
        {
            var result = await SendCommandAsync(
                IGPDesktopSessionCommandType.GetDesktopUserAvatar,
                cancellationToken);

            return new IGPDesktopUserAvatarImage
            {
                info = ParseCommandPayload(
                    result.ContentJson,
                    () => new IGPDesktopUserAvatarInfo()),
                pngBytes = result.ContentBytes ?? Array.Empty<byte>(),
            };
        }

        public async Task<IGPAntiAddictionStatus> GetDesktopAntiAddictionStatusAsync(
            CancellationToken cancellationToken = default)
        {
            var result = await SendCommandAsync(
                IGPDesktopSessionCommandType.GetDesktopAntiAddictionStatus,
                cancellationToken);

            return ParseCommandPayload(
                result.ContentJson,
                () => CurrentSession?.AntiAddictionStatus ?? new IGPAntiAddictionStatus());
        }

        public async Task<IGPAntiAddictionRealNameWebSession> CreateDesktopAntiAddictionRealNameUrlAsync(
            int? appId = null,
            CancellationToken cancellationToken = default)
        {
            var payload = appId.HasValue && appId.Value > 0
                ? JsonConvert.SerializeObject(new
                {
                    appId = appId.Value,
                })
                : null;

            var result = await SendCommandAsync(
                IGPDesktopSessionCommandType.CreateDesktopAntiAddictionRealNameUrl,
                cancellationToken,
                contentJson: payload);

            return ParseCommandPayload(
                result.ContentJson,
                () => new IGPAntiAddictionRealNameWebSession());
        }

        public async Task<IGPDesktopSessionCommandResult> ForwardNamedCommandAsync(
            string command,
            string contentJson,
            CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrWhiteSpace(command))
            {
                throw new ArgumentException("Desktop session command is required", nameof(command));
            }

            if (string.Equals(command, "lyingBottleForward", StringComparison.Ordinal))
            {
                if (!(CurrentSession?.Capabilities?.lyingBottle ?? false))
                {
                    throw new IGPSDKException(
                        "Desktop session capability is not available: lyingBottle",
                        IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_CAPABILITY_MISSING,
                        IGP.UnitySDK.Models.IGPErrorCodes.CATEGORY_VALIDATION,
                        desktopChannelState: null,
                        desktopAttachState: null);
                }
            }

            return await SendNamedCommandAsync(command, cancellationToken, contentJson);
        }

        public async Task DisconnectAsync()
        {
            var localPipe = pipe;
            var localLifetimeCts = lifetimeCts;
            var localReadLoopTask = readLoopTask;
            pipe = null;
            lifetimeCts = null;
            readLoopTask = null;
            IsAttached = false;
            CurrentSession = null;

            if (localLifetimeCts != null)
            {
                try
                {
                    localLifetimeCts.Cancel();
                }
                catch
                {
                    // Ignore cancellation failures during shutdown.
                }
            }

            attachCompletionSource?.TrySetCanceled();
            attachCompletionSource = null;

            FailPendingRequests(new IGPSDKException("IGP desktop session closed"));

            if (localPipe != null)
            {
                try
                {
                    localPipe.Dispose();
                }
                catch
                {
                    // Ignore dispose failures.
                }
            }

            if (localReadLoopTask != null)
            {
                try
                {
                    await localReadLoopTask;
                }
                catch
                {
                    // Read loop exceptions are surfaced through events/TCS.
                }
            }

            if (localLifetimeCts != null)
            {
                try
                {
                    localLifetimeCts.Dispose();
                }
                catch
                {
                    // Ignore dispose failures during shutdown.
                }
            }
        }

        public void Dispose()
        {
            _ = DisconnectAsync().ContinueWith(_ =>
            {
                try
                {
                    writeLock.Dispose();
                }
                catch
                {
                    // Ignore dispose failures during shutdown.
                }
            }, TaskScheduler.Default);
        }

        private async Task<IGPDesktopSessionCommandResult> SendCommandAsync(
            IGPDesktopSessionCommandType command,
            CancellationToken cancellationToken,
            string? stringValue = null,
            string? contentJson = null)
        {
            if (pipe == null || !IsAttached)
            {
                throw new IGPSDKException("IGP desktop session is not attached", IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED);
            }

            EnsureCommandCapability(command);

            var requestId = Guid.NewGuid().ToString("N");
            var completionSource = new TaskCompletionSource<IGPDesktopSessionCommandResult>(
                TaskCreationOptions.RunContinuationsAsynchronously);

            lock (syncRoot)
            {
                pendingRequests[requestId] = completionSource;
            }

            try
            {
                var payload = IGPDesktopSessionProtocol.EncodeCommandRequest(
                    requestId,
                    command,
                    stringValue,
                    contentJson);
                await WriteFrameAsync(payload, cancellationToken);

                using (cancellationToken.Register(() => completionSource.TrySetCanceled(cancellationToken)))
                {
                    var result = await completionSource.Task;
                    if (!result.Success)
                    {
                        var failureCode = string.IsNullOrWhiteSpace(result.Code)
                            ? IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                            : result.Code;
                        var failureMessage = string.IsNullOrWhiteSpace(result.Message)
                            ? $"Desktop session command failed: {command}"
                            : result.Message;
                        throw new IGPSDKException(
                            failureMessage,
                            failureCode,
                            IGP.UnitySDK.Models.IGPErrorCodes.ResolveCategory(failureCode),
                            desktopChannelState: null,
                            desktopAttachState: null);
                    }

                    return result;
                }
            }
            catch
            {
                lock (syncRoot)
                {
                    pendingRequests.Remove(requestId);
                }

                throw;
            }
        }

        private async Task<IGPDesktopSessionCommandResult> SendNamedCommandAsync(
            string command,
            CancellationToken cancellationToken,
            string? contentJson = null)
        {
            if (pipe == null || !IsAttached)
            {
                throw new IGPSDKException("IGP desktop session is not attached", IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED);
            }

            var requestId = Guid.NewGuid().ToString("N");
            var completionSource = new TaskCompletionSource<IGPDesktopSessionCommandResult>(
                TaskCreationOptions.RunContinuationsAsynchronously);

            lock (syncRoot)
            {
                pendingRequests[requestId] = completionSource;
            }

            try
            {
                var payload = IGPDesktopSessionProtocol.EncodeNamedCommandRequest(
                    requestId,
                    command,
                    contentJson);
                await WriteFrameAsync(payload, cancellationToken);

                using (cancellationToken.Register(() => completionSource.TrySetCanceled(cancellationToken)))
                {
                    var result = await completionSource.Task;
                    if (!result.Success)
                    {
                        var failureCode = string.IsNullOrWhiteSpace(result.Code)
                            ? IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                            : result.Code;
                        var failureMessage = string.IsNullOrWhiteSpace(result.Message)
                            ? $"Desktop session command failed: {command}"
                            : result.Message;
                        throw new IGPSDKException(
                            failureMessage,
                            failureCode,
                            IGP.UnitySDK.Models.IGPErrorCodes.ResolveCategory(failureCode),
                            desktopChannelState: null,
                            desktopAttachState: null);
                    }

                    return result;
                }
            }
            catch
            {
                lock (syncRoot)
                {
                    pendingRequests.Remove(requestId);
                }

                throw;
            }
        }

        private async Task WriteFrameAsync(byte[] payload, CancellationToken cancellationToken)
        {
            if (pipe == null)
            {
                throw new IGPSDKException("IGP desktop session pipe is not connected");
            }

            var frame = EncodeFrame(payload);
            await writeLock.WaitAsync(cancellationToken);
            try
            {
                await pipe.WriteAsync(frame, 0, frame.Length, cancellationToken);
                await pipe.FlushAsync(cancellationToken);
            }
            finally
            {
                writeLock.Release();
            }
        }

        private async Task ReadLoopAsync(Stream stream, CancellationToken cancellationToken)
        {
            try
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    var payload = await ReadFrameAsync(stream, cancellationToken);
                    if (payload == null)
                    {
                        break;
                    }

                    var message = IGPDesktopSessionProtocol.DecodeServerMessage(payload);
                    HandleServerMessage(message);
                }
            }
            catch (OperationCanceledException)
            {
                // Expected when disconnecting.
            }
            catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested)
            {
                // Expected when the pipe is disposed during shutdown.
            }
            catch (IOException) when (cancellationToken.IsCancellationRequested)
            {
                // Expected when the pipe is disposed during shutdown.
            }
            catch (Exception ex)
            {
                attachCompletionSource?.TrySetException(ex);
                FailPendingRequests(ex);
                var bridgeError = new IGPSDKException(
                    ex.Message,
                    "DESKTOP_SESSION_BRIDGE_ERROR",
                    IGP.UnitySDK.Models.IGPErrorCodes.CATEGORY_CHANNEL,
                    desktopChannelState: "failed",
                    desktopAttachState: IsAttached ? "attached" : "idle");
                ErrorOccurred?.Invoke("DESKTOP_SESSION_BRIDGE_ERROR", ex.Message);
                ErrorDetailed?.Invoke(bridgeError);
            }
            finally
            {
                if (IsAttached)
                {
                    IsAttached = false;
                    Detached?.Invoke("desktop-session-closed");
                }
            }
        }

        private void HandleServerMessage(IGPDesktopSessionServerMessage message)
        {
            switch (message.Type)
            {
                case IGPDesktopSessionServerMessageType.Attached:
                    if (message.Attached == null)
                    {
                        return;
                    }

                    CurrentSession = message.Attached;
                    IsAttached = true;
                    attachCompletionSource?.TrySetResult(message.Attached);
                    return;
                case IGPDesktopSessionServerMessageType.AntiAddictionChanged:
                    if (message.AntiAddictionChanged == null)
                    {
                        return;
                    }

                    if (CurrentSession != null)
                    {
                        CurrentSession.AntiAddictionStatus = message.AntiAddictionChanged;
                    }
                    AntiAddictionChanged?.Invoke(message.AntiAddictionChanged);
                    return;
                case IGPDesktopSessionServerMessageType.HostedBootstrapAvailable:
                    if (message.HostedBootstrapAvailable == null)
                    {
                        return;
                    }

                    HostedBootstrapAvailable?.Invoke(message.HostedBootstrapAvailable);
                    return;
                case IGPDesktopSessionServerMessageType.CommandResult:
                    if (message.CommandResult == null)
                    {
                        return;
                    }

                    TaskCompletionSource<IGPDesktopSessionCommandResult>? requestCompletion = null;
                    lock (syncRoot)
                    {
                        if (pendingRequests.TryGetValue(message.CommandResult.RequestId, out requestCompletion))
                        {
                            pendingRequests.Remove(message.CommandResult.RequestId);
                        }
                    }

                    requestCompletion?.TrySetResult(message.CommandResult);
                    return;
                case IGPDesktopSessionServerMessageType.Detached:
                    IsAttached = false;
                    attachCompletionSource?.TrySetException(
                        new IGPSDKException(
                            message.Detached?.Reason ?? "IGP desktop session detached",
                            IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED));
                    FailPendingRequests(new IGPSDKException(
                        message.Detached?.Reason ?? "IGP desktop session detached",
                        IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED));
                    Detached?.Invoke(message.Detached?.Reason ?? "desktop-session-detached");
                    return;
                case IGPDesktopSessionServerMessageType.Error:
                    var code = message.Error?.Code ?? IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED;
                    var errorMessage = message.Error?.Message ?? "IGP desktop session error";
                    var category = string.IsNullOrWhiteSpace(message.Error?.Category)
                        ? IGP.UnitySDK.Models.IGPErrorCodes.ResolveCategory(code)
                        : message.Error!.Category;
                    var channelState = message.Error?.ChannelState;
                    var attachState = message.Error?.AttachState;
                    var detailedError = new IGPSDKException(
                        errorMessage,
                        code,
                        category,
                        string.IsNullOrEmpty(channelState) ? null : channelState,
                        string.IsNullOrEmpty(attachState) ? null : attachState);
                    attachCompletionSource?.TrySetException(detailedError);
                    FailPendingRequests(detailedError);
                    ErrorOccurred?.Invoke(code, errorMessage);
                    ErrorDetailed?.Invoke(detailedError);
                    return;
                default:
                    throw new IGPSDKException($"Unsupported desktop session message type: {message.Type}");
            }
        }

        private void FailPendingRequests(Exception error)
        {
            List<TaskCompletionSource<IGPDesktopSessionCommandResult>> pending;
            lock (syncRoot)
            {
                pending = new List<TaskCompletionSource<IGPDesktopSessionCommandResult>>(pendingRequests.Values);
                pendingRequests.Clear();
            }

            foreach (var item in pending)
            {
                item.TrySetException(error);
            }
        }

        private static T ParseCommandPayload<T>(string? contentJson, Func<T> fallbackFactory)
        {
            if (!string.IsNullOrWhiteSpace(contentJson))
            {
                var parsed = JsonConvert.DeserializeObject<T>(contentJson);
                if (parsed != null)
                {
                    return parsed;
                }
            }

            return fallbackFactory();
        }

        private static void ValidateDesktopProtocolVersion(IGPDesktopSessionAttachResponse response)
        {
            if (string.IsNullOrWhiteSpace(response.DesktopProtocolVersion))
            {
                throw new IGPSDKException(
                    "desktop 版本过旧，请升级 desktop",
                    IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_PROTOCOL_INCOMPATIBLE,
                    IGP.UnitySDK.Models.IGPErrorCodes.CATEGORY_VALIDATION,
                    desktopChannelState: "reachable",
                    desktopAttachState: "rejected");
            }

            if (!string.Equals(
                    response.DesktopProtocolVersion,
                    IGPDesktopSessionProtocol.SupportedProtocolVersion,
                    StringComparison.Ordinal))
            {
                throw new IGPSDKException(
                    "SDK 和 desktop 协议版本不兼容，请升级 SDK 或 desktop",
                    IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_PROTOCOL_INCOMPATIBLE,
                    IGP.UnitySDK.Models.IGPErrorCodes.CATEGORY_VALIDATION,
                    desktopChannelState: "reachable",
                    desktopAttachState: "rejected");
            }
        }

        private void EnsureCommandCapability(IGPDesktopSessionCommandType command)
        {
            var capabilities = CurrentSession?.Capabilities;
            var supported = true;
            var capabilityName = string.Empty;

            switch (command)
            {
                case IGPDesktopSessionCommandType.UnlockAchievement:
                case IGPDesktopSessionCommandType.ReportAchievementProgress:
                case IGPDesktopSessionCommandType.ClearAchievements:
                    supported = capabilities?.achievements ?? false;
                    capabilityName = "achievements";
                    break;
                case IGPDesktopSessionCommandType.RequestGameAuthorization:
                    supported = capabilities?.gameAuthorization ?? false;
                    capabilityName = "gameAuthorization";
                    break;
                case IGPDesktopSessionCommandType.RequestHostedBootstrap:
                    supported = capabilities?.hostedBootstrap ?? false;
                    capabilityName = "hostedBootstrap";
                    break;
                case IGPDesktopSessionCommandType.GetDesktopUserAvatarInfo:
                case IGPDesktopSessionCommandType.GetDesktopUserAvatar:
                    supported = capabilities?.userContext ?? false;
                    capabilityName = "userContext";
                    break;
                case IGPDesktopSessionCommandType.GetDesktopAntiAddictionStatus:
                case IGPDesktopSessionCommandType.GetDesktopAntiAddictionEvent:
                case IGPDesktopSessionCommandType.GetDesktopAntiAddictionAgeRange:
                case IGPDesktopSessionCommandType.GetDesktopAntiAddictionRemainingTime:
                case IGPDesktopSessionCommandType.GetDesktopAntiAddictionProfile:
                case IGPDesktopSessionCommandType.CreateDesktopAntiAddictionRealNameUrl:
                    supported = capabilities?.antiAddiction ?? false;
                    capabilityName = "antiAddiction";
                    break;
            }

            if (!supported)
            {
                throw new IGPSDKException(
                    $"Desktop session capability is not available: {capabilityName}",
                    IGP.UnitySDK.Models.IGPErrorCodes.ERR_DESKTOP_SESSION_CAPABILITY_MISSING,
                    IGP.UnitySDK.Models.IGPErrorCodes.CATEGORY_VALIDATION,
                    desktopChannelState: null,
                    desktopAttachState: null);
            }
        }

        private static string ExtractPipeName(string endpoint)
        {
            const string pipePrefix = @"\\.\pipe\";
            if (endpoint.StartsWith(pipePrefix, StringComparison.OrdinalIgnoreCase))
            {
                return endpoint.Substring(pipePrefix.Length);
            }

            return endpoint;
        }

        private static byte[] EncodeFrame(byte[] payload)
        {
            var frame = new byte[4 + payload.Length];
            var lengthBytes = BitConverter.GetBytes(payload.Length);
            Buffer.BlockCopy(lengthBytes, 0, frame, 0, 4);
            Buffer.BlockCopy(payload, 0, frame, 4, payload.Length);
            return frame;
        }

        private static async Task<byte[]?> ReadFrameAsync(Stream stream, CancellationToken cancellationToken)
        {
            var header = await ReadExactAsync(stream, 4, cancellationToken, allowEndOfStream: true);
            if (header == null)
            {
                return null;
            }

            var payloadLength = BitConverter.ToInt32(header, 0);
            if (payloadLength <= 0 || payloadLength > MaxFramePayloadBytes)
            {
                throw new IGPSDKException("Invalid desktop session frame length");
            }

            return await ReadExactAsync(stream, payloadLength, cancellationToken, allowEndOfStream: false);
        }

        private static async Task<byte[]?> ReadExactAsync(
            Stream stream,
            int length,
            CancellationToken cancellationToken,
            bool allowEndOfStream)
        {
            var buffer = new byte[length];
            var offset = 0;

            while (offset < length)
            {
                var read = await stream.ReadAsync(buffer, offset, length - offset, cancellationToken);
                if (read <= 0)
                {
                    if (offset == 0 && allowEndOfStream)
                    {
                        return null;
                    }

                    throw new IGPSDKException("Unexpected end of desktop session stream");
                }

                offset += read;
            }

            return buffer;
        }
    }
}
