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

namespace IGP.UnitySDK
{
    public partial class IGPRuntimeManager
    {
        private sealed class IGPDesktopHostSessionPlatformRuntime : IGPHostSessionPlatformRuntimeBase
        {
            private const double AttachRetryBackoffSeconds = 2.0;

            private DateTime? nextAttachRetryAtUtc;
            private bool attachRetryInFlight;

            internal IGPDesktopHostSessionPlatformRuntime(IGPRuntimeManager runtime)
                : base(runtime)
            {
            }

            public override IGPHostSessionPlatform Platform => IGPHostSessionPlatform.Desktop;
            public override string TransportName => "desktop-session";
            public override string ConnectionSource => "desktop_session";
            public override string AttachedReason => "desktop-session-attached";
            public override string DetachedReason => "desktop-session-detached";
            public override string LogEndpoint => IGPDesktopSessionEnvironment.ResolvePipeEndpoint(
                Runtime.config?.desktopPipeEndpoint,
                Runtime.SdkEnvironment);

            public override IIGPHostSessionTransport CreateTransport()
            {
                return new IGPDesktopNamedPipeTransport(LogEndpoint);
            }

            public override IGPDesktopSessionAttachRequest CreateAttachRequest(int appId)
            {
                var executablePath = IGPDesktopSessionEnvironment.ResolveExecutablePath(
                    Runtime.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);
                }

                if (Application.isEditor &&
                    string.IsNullOrWhiteSpace(Runtime.config?.desktopExecutablePathDebugOverride))
                {
                    Runtime.LogSdkInfo(
                        TransportName,
                        "attach",
                        "event=editor-executable-selected validation=app-id-and-account-permissions");
                }

                return CreateCommonAttachRequest(
                    appId,
                    executablePath,
                    GetCurrentProcessId());
            }

            public override async Task InitializeSessionAsync()
            {
                if (!ShouldAutoAttach)
                {
                    return;
                }

                if (!await Runtime.TryAttachHostSessionAsync())
                {
                    await TryLaunchAndAttachAsync(null);
                }
            }

            public override Task<bool> RecoverUnavailableSessionAsync(int? appIdOverride)
            {
                return TryLaunchAndAttachAsync(appIdOverride);
            }

            public override Task<bool> AuthorizeAsync()
            {
                return Runtime.TryAuthorizeDesktopAsync();
            }

            public override void Tick()
            {
                TryRecoverAttachIfNeeded();
                Runtime.TryRecoverAuthorizationStateIfNeeded();
            }

            public override void OnAttachSucceeded()
            {
                nextAttachRetryAtUtc = null;
            }

            public override void ScheduleAttachRecoveryIfNeeded()
            {
                if (Runtime.isDestroyed || !ShouldAutoAttach || !CanRetryAttach())
                {
                    nextAttachRetryAtUtc = null;
                    return;
                }

                nextAttachRetryAtUtc = DateTime.UtcNow.AddSeconds(AttachRetryBackoffSeconds);
            }

            public override void ScheduleAuthorizationRecoveryIfNeeded()
            {
                if (Runtime.isDestroyed)
                {
                    return;
                }

                if (Runtime.authorizationState == IGPAuthorizationState.AuthorizedOnline ||
                    Runtime.authorizationState == IGPAuthorizationState.Skipped)
                {
                    Runtime.nextAuthorizationRecoverAtUtc = null;
                    return;
                }

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

            public override void Dispose()
            {
                nextAttachRetryAtUtc = null;
            }

            private void TryRecoverAttachIfNeeded()
            {
                if (Runtime.isDestroyed || !ShouldAutoAttach || attachRetryInFlight)
                {
                    return;
                }

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

                if (Runtime.IsAttached)
                {
                    nextAttachRetryAtUtc = null;
                    return;
                }

                if (!CanRetryAttach())
                {
                    nextAttachRetryAtUtc = null;
                    return;
                }

                attachRetryInFlight = true;
                nextAttachRetryAtUtc = null;
                _ = RunAttachRetryAsync();
            }

            private async Task RunAttachRetryAsync()
            {
                try
                {
                    var attached = await Runtime.TryAttachHostSessionAsync();
                    if (!attached && !Runtime.isDestroyed && CanRetryAttach())
                    {
                        nextAttachRetryAtUtc = DateTime.UtcNow.AddSeconds(AttachRetryBackoffSeconds);
                    }
                    else if (attached)
                    {
                        ScheduleAuthorizationRecoveryIfNeeded();
                    }
                }
                catch (Exception exception)
                {
                    Runtime.LogSdkWarning(
                        TransportName,
                        "recovery",
                        $"event=attach-failed error={FormatNetworkLogValue(exception.Message)}");
                    if (!Runtime.isDestroyed && CanRetryAttach())
                    {
                        nextAttachRetryAtUtc = DateTime.UtcNow.AddSeconds(AttachRetryBackoffSeconds);
                    }
                }
                finally
                {
                    attachRetryInFlight = false;
                }
            }

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

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

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

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

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

                    if (!CanRetryAttach())
                    {
                        Runtime.LogSdkWarning(
                            TransportName,
                            "launch",
                            $"event=retry-stopped error={FormatNetworkLogValue(Runtime.desktopSessionLastErrorMessage)}");
                        return false;
                    }
                }

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

                Runtime.LogSdkWarning(
                    TransportName,
                    "launch",
                    $"event=retry-timeout error={FormatNetworkLogValue(Runtime.desktopSessionLastErrorMessage)}");
                return false;
            }

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

                if (IGPErrorCodes.IsValidationError(Runtime.desktopSessionLastErrorCode) ||
                    IGPErrorCodes.IsAuthorizationError(Runtime.desktopSessionLastErrorCode))
                {
                    return false;
                }

                return true;
            }

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

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