#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;

namespace IGP.UnitySDK
{
    internal sealed class IGPMobileHostSessionBridge
    {
        private readonly object syncRoot = new object();
        private bool ready;
        private Action<string>? frameSender;
        private TaskCompletionSource<bool> readyCompletionSource = CreateReadyCompletionSource();

        private event Action<string>? FrameReceived;
        private event Action<string>? Closed;

        internal void SetFrameSender(Action<string>? sender)
        {
            lock (syncRoot)
            {
                frameSender = sender;
            }
        }

        internal bool TryOpen(Action<string> frameReceived, Action<string> closed)
        {
            lock (syncRoot)
            {
                if (!ready)
                {
                    return false;
                }

                FrameReceived += frameReceived;
                Closed += closed;
                return true;
            }
        }

        internal void Detach(Action<string> frameReceived, Action<string> closed)
        {
            lock (syncRoot)
            {
                FrameReceived -= frameReceived;
                Closed -= closed;
            }
        }

        internal void MarkReady()
        {
            TaskCompletionSource<bool> completionSource;
            lock (syncRoot)
            {
                ready = true;
                completionSource = readyCompletionSource;
            }

            completionSource.TrySetResult(true);
        }

        internal async Task WaitUntilReadyAsync(CancellationToken cancellationToken)
        {
            Task<bool> readyTask;
            lock (syncRoot)
            {
                if (ready)
                {
                    return;
                }

                readyTask = readyCompletionSource.Task;
            }

            var cancellationCompletionSource = new TaskCompletionSource<bool>(
                TaskCreationOptions.RunContinuationsAsynchronously);
            using (cancellationToken.Register(() =>
                cancellationCompletionSource.TrySetCanceled(cancellationToken)))
            {
                var completed = await Task.WhenAny(readyTask, cancellationCompletionSource.Task);
                if (completed == cancellationCompletionSource.Task)
                {
                    await cancellationCompletionSource.Task;
                }

                if (!await readyTask)
                {
                    throw new IGPSDKException(
                        "Mobile Host transport closed before becoming ready",
                        Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED);
                }
            }

            lock (syncRoot)
            {
                if (!ready)
                {
                    throw new IGPSDKException(
                        "Mobile Host transport is no longer ready",
                        Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED);
                }
            }
        }

        internal void SendFrame(string frameJson)
        {
            Action<string>? handler;
            lock (syncRoot)
            {
                if (!ready)
                {
                    throw new IGPSDKException(
                        "Mobile Host transport is not ready",
                        Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED);
                }

                handler = frameSender;
            }

            if (handler == null)
            {
                throw new IGPSDKException(
                    "No Mobile Host frame sender is registered",
                    Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED);
            }

            handler(frameJson);
        }

        internal void ReceiveFrame(string frameJson)
        {
            if (string.IsNullOrWhiteSpace(frameJson))
            {
                throw new ArgumentException("Mobile Host frame is required", nameof(frameJson));
            }

            Action<string>? handler;
            lock (syncRoot)
            {
                handler = FrameReceived;
            }

            if (handler == null)
            {
                throw new IGPSDKException(
                    "Mobile Host Session is not accepting frames",
                    Models.IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED);
            }

            handler(frameJson);
        }

        internal void Close(string reason)
        {
            Action<string>? handler;
            TaskCompletionSource<bool> readinessWaiter;
            lock (syncRoot)
            {
                ready = false;
                readinessWaiter = readyCompletionSource;
                readyCompletionSource = CreateReadyCompletionSource();
                handler = Closed;
            }

            readinessWaiter.TrySetResult(false);
            handler?.Invoke(string.IsNullOrWhiteSpace(reason)
                ? "mobile-host-session-closed"
                : reason);
        }

        private static TaskCompletionSource<bool> CreateReadyCompletionSource()
        {
            return new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
        }
    }
}
