#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
using IGP.UnitySDK.Models;

namespace IGP.UnitySDK
{
    public partial class IGPRuntimeManager
    {
        private int pendingHostGracefulShutdownRequests;
        public int AttachedAppId => hostSessionClient?.CurrentSession?.AppId ?? 0;

        public bool SupportsCapability(int capabilityFieldNumber)
        {
            return capabilityFieldNumber <= 0 ||
                (currentDesktopCapabilities?.SupportsCapability(capabilityFieldNumber) ?? false);
        }

        public async Task<IGPDesktopSessionCommandResult> SendAsync(
            int commandType,
            int capabilityFieldNumber = 0,
            string? contentJson = null,
            byte[]? contentBytes = null,
            CancellationToken cancellationToken = default)
        {
            var client = await EnsureHostSessionAsync(ResolveHostAppId(null));
            return await client.SendAsync(
                commandType,
                capabilityFieldNumber,
                contentJson,
                contentBytes,
                cancellationToken);
        }

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

            try
            {
                await AttachHostSessionAsync(appIdOverride);
                desktopSessionLastErrorCode = string.Empty;
                desktopSessionLastErrorMessage = string.Empty;
                return true;
            }
            catch (Exception ex)
            {
                CaptureHostAttachFailure(ex);
                LogSdkWarning(
                    HostSessionPlatformRuntime.TransportName,
                    "attach",
                    $"event=failed error={FormatNetworkLogValue(desktopSessionLastErrorMessage)}");
                return false;
            }
        }

        private async Task AttachHostSessionAsync(int? appIdOverride = null)
        {
            var appId = ResolveHostAppId(appIdOverride);
            if (!appId.HasValue)
            {
                throw new IGPSDKException(
                    "IGP appId is required before Host Session capabilities can be used",
                    IGPErrorCodes.ERR_APP_ID_REQUIRED);
            }

            var platformRuntime = HostSessionPlatformRuntime;
            var request = platformRuntime.CreateAttachRequest(appId.Value);

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

            await DetachHostSessionAsync();

            var transport = platformRuntime.CreateTransport();
            var client = new IGPHostSessionClient(transport);
            client.Detached += HandleHostSessionDetached;
            client.ErrorOccurred += HandleHostSessionError;
            client.AntiAddictionChanged += HandleDesktopAntiAddictionChanged;
            client.NotificationReceived += HandleHostNotificationReceived;
            client.OfflineMapLaunchRequested += HandleHostOfflineMapLaunchRequested;
            client.GracefulShutdownRequested += HandleHostGracefulShutdownRequested;

            try
            {
                var response = await client.ConnectAsync(request, RuntimeCancellationToken);
                runtimeInfo.CommitSaveRootPath(response.IgpSaveRoot);
                hostSessionClient = client;
                currentDesktopUserContext = new IGPDesktopUserContext
                {
                    userId = response.UserId,
                    accountId = response.AccountId,
                    loginState = response.LoginState,
                };
                currentDesktopCapabilities = response.Capabilities ?? new IGPDesktopCapabilitySet();
                currentDesktopUserProfile = response.UserProfile;
                HandleDesktopIdentityAttached(response.UserId, response.LoginState);
                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;
                PublishHostConnectionChanged(true, platformRuntime.AttachedReason);
                platformRuntime.OnAttachSucceeded();
            }
            catch
            {
                client.Detached -= HandleHostSessionDetached;
                client.ErrorOccurred -= HandleHostSessionError;
                client.AntiAddictionChanged -= HandleDesktopAntiAddictionChanged;
                client.NotificationReceived -= HandleHostNotificationReceived;
                client.OfflineMapLaunchRequested -= HandleHostOfflineMapLaunchRequested;
                client.GracefulShutdownRequested -= HandleHostGracefulShutdownRequested;
                client.Dispose();
                throw;
            }
        }

        private async Task DetachHostSessionAsync()
        {
            if (hostSessionClient == null)
            {
                ClearHostedRoomHint();
                currentDesktopUserContext = null;
                currentDesktopCapabilities = null;
                currentDesktopUserProfile = null;
                currentAntiAddictionStatus = null;
                return;
            }

            var client = hostSessionClient;
            var wasAttached = client.IsAttached;
            hostSessionClient = null;
            ClearHostedRoomHint();
            currentDesktopUserContext = null;
            currentDesktopCapabilities = null;
            currentDesktopUserProfile = null;
            currentAntiAddictionStatus = null;

            client.Detached -= HandleHostSessionDetached;
            client.ErrorOccurred -= HandleHostSessionError;
            client.AntiAddictionChanged -= HandleDesktopAntiAddictionChanged;
            client.NotificationReceived -= HandleHostNotificationReceived;
            client.OfflineMapLaunchRequested -= HandleHostOfflineMapLaunchRequested;
            client.GracefulShutdownRequested -= HandleHostGracefulShutdownRequested;
            await client.DisconnectAsync();
            if (wasAttached)
            {
                PublishHostConnectionChanged(
                    false,
                    HostSessionPlatformRuntime.DetachedReason);
            }
        }

        private async Task<IGPHostSessionClient> EnsureHostSessionAsync(int? appIdOverride = null)
        {
            EnsureSDKInitialized();

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

            if (await TryAttachHostSessionAsync(appIdOverride))
            {
                return hostSessionClient!;
            }

            if (await HostSessionPlatformRuntime.RecoverUnavailableSessionAsync(appIdOverride))
            {
                return hostSessionClient!;
            }

            throw CreateHostUnavailableException();
        }

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

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

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

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

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

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

        private void MarkHostSessionUnavailable(
            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!;
            }

            hostSessionPlatformRuntime?.ScheduleAttachRecoveryIfNeeded();
        }

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

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

        private void HandleHostSessionDetached(string reason)
        {
            ClearHostedRoomHint();
            currentAntiAddictionStatus = null;
            MarkHostSessionUnavailable(
                IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                string.IsNullOrWhiteSpace(reason) ? "Host Session detached" : reason,
                IGPErrorCodes.CATEGORY_CHANNEL,
                channelState: "disconnected",
                attachState: "idle");

            HostSessionPlatformRuntime.ScheduleAttachRecoveryIfNeeded();
            HostSessionPlatformRuntime.ScheduleAuthorizationRecoveryIfNeeded();
            PublishHostConnectionChanged(false, reason);

            if (!isDestroyed)
            {
                LogSdkWarning(
                    HostSessionPlatformRuntime.TransportName,
                    "detach",
                    $"event=detached error={FormatNetworkLogValue(desktopSessionLastErrorMessage)}");
            }
        }

        private void HandleHostNotificationReceived(IGPDesktopSessionCommandResult notification)
        {
            HandleRoomAccessNotification(notification);
            if (!isDestroyed)
            {
                hostNotificationReceived?.Invoke(notification);
            }
        }

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

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

            OfflineMapLaunchRequested?.Invoke(offlineMapLaunchRequested);
        }

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

            Interlocked.Increment(ref pendingHostGracefulShutdownRequests);
        }

        private void DrainHostGracefulShutdownRequests()
        {
            var pendingRequests = Interlocked.Exchange(ref pendingHostGracefulShutdownRequests, 0);
            if (isDestroyed)
            {
                return;
            }

            for (var index = 0; index < pendingRequests; index += 1)
            {
                GracefulShutdownRequested?.Invoke();
            }
        }

        private void HandleHostSessionError(IGPSDKException error)
        {
            var code = string.IsNullOrWhiteSpace(error.ErrorCode)
                ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                : error.ErrorCode!;
            var message = error.Message;
            // 兼容旧事件：只有 code + message 时按码推断分类。
            HandleHostSessionErrorCore(
                string.IsNullOrWhiteSpace(code) ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED : code,
                message,
                error.Category,
                error.DesktopChannelState,
                error.DesktopAttachState);
        }

        private void HandleHostSessionErrorCore(
            string code,
            string message,
            string? category,
            string? channelState,
            string? attachState)
        {
            MarkHostSessionUnavailable(code, message, category, channelState, attachState);

            // Recovery policy is selected by error category; the platform runtime
            // decides whether attach or authorization recovery is supported.
            var resolvedCategory = string.IsNullOrWhiteSpace(category)
                ? IGPErrorCodes.ResolveCategory(code)
                : category!;

            if (resolvedCategory == IGPErrorCodes.CATEGORY_AUTHORIZATION)
            {
                HostSessionPlatformRuntime.ScheduleAuthorizationRecoveryIfNeeded();
            }
            else if (resolvedCategory == IGPErrorCodes.CATEGORY_CHANNEL ||
                     resolvedCategory == IGPErrorCodes.CATEGORY_RUNTIME)
            {
                HostSessionPlatformRuntime.ScheduleAttachRecoveryIfNeeded();
                HostSessionPlatformRuntime.ScheduleAuthorizationRecoveryIfNeeded();
            }

            if (!isDestroyed)
            {
                LogSdkError(
                    HostSessionPlatformRuntime.TransportName,
                    "error",
                    $"event=received code={FormatNetworkLogValue(desktopSessionLastErrorCode)} " +
                    $"category={FormatNetworkLogValue(resolvedCategory)} error={FormatNetworkLogValue(message)}");
                ErrorOccurred?.Invoke(new IGPSDKException(
                    $"Host Session error: {message}",
                    code,
                    resolvedCategory,
                    channelState,
                    attachState));
            }
        }

        private void PublishHostConnectionChanged(bool isConnected, string reason)
        {
            if (desktopConnectionEventState == isConnected)
            {
                return;
            }

            desktopConnectionEventState = isConnected;
            if (!isDestroyed)
            {
                ConnectionChanged?.Invoke(new IGPConnectionChangedEvent(
                    isConnected,
                    HostSessionPlatformRuntime.ConnectionSource,
                    reason));
            }
        }
    }
}
