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

namespace IGP.UnitySDK
{
    public partial class IGPRuntimeManager : IIGPMultiplayerAccessProvider
    {
        private const int DesktopHostedCapabilityFieldNumber = 4;
        private const int DesktopHostedBootstrapCommand = 18;
        private const string DesktopHostedBootstrapAvailableRequestId =
            "desktop-hosted-bootstrap-available";

        private readonly object roomAccessSync = new object();
        private HostedRoomHint? hostedRoomHint;
        private event Action? roomAccessAvailable;

        event Action? IIGPMultiplayerAccessProvider.RoomAccessAvailable
        {
            add
            {
                roomAccessAvailable += value;
                lock (roomAccessSync)
                {
                    if (hostedRoomHint != null) value?.Invoke();
                }
            }
            remove => roomAccessAvailable -= value;
        }

        async Task<HostedBootstrapContext?> IIGPMultiplayerAccessProvider.AcquireHostedBootstrapAsync(
            CancellationToken cancellationToken)
        {
            EnsureCapabilityRequestAvailable();
            var hint = GetUsableHostedRoomHint();
            if (hint == null) return null;
            return await RequestHostedBootstrapCoreAsync(hint, cancellationToken)
                .ConfigureAwait(false);
        }

        private async Task<HostedBootstrapContext> RequestHostedBootstrapCoreAsync(
            HostedRoomHint hint,
            CancellationToken cancellationToken)
        {
            var gateway = GetCapabilityHostGateway();
            if (!gateway.IsAttached ||
                !gateway.SupportsCapability(DesktopHostedCapabilityFieldNumber))
            {
                ClearHostedRoomHint();
                throw new IGPSDKException(
                    "Hosted room access is no longer available",
                    IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED);
            }

            using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
                cancellationToken,
                RuntimeCancellationToken);
            var result = await gateway.SendAsync(
                DesktopHostedBootstrapCommand,
                DesktopHostedCapabilityFieldNumber,
                JsonConvert.SerializeObject(new { appId = hint.AppId }),
                cancellationToken: linkedCts.Token).ConfigureAwait(false);
            EnsureDesktopCommandSucceeded(result);

            HostedBootstrapPayload payload;
            try
            {
                payload = JsonConvert.DeserializeObject<HostedBootstrapPayload>(result.ContentJson)
                    ?? new HostedBootstrapPayload();
            }
            catch (JsonException exception)
            {
                throw DirectProtocolError("Host Session hosted bootstrap response is invalid", exception);
            }

            if (payload.appId != hint.AppId ||
                string.IsNullOrWhiteSpace(payload.roomId) ||
                !string.Equals(payload.roomId, hint.RoomId, StringComparison.Ordinal) ||
                string.IsNullOrWhiteSpace(payload.launchTicket) ||
                string.IsNullOrWhiteSpace(payload.bootstrapEndpoint) ||
                string.IsNullOrWhiteSpace(payload.bootstrapSecret))
            {
                throw DirectProtocolError("Host Session hosted bootstrap response is incomplete");
            }

            return new HostedBootstrapContext(
                payload.roomId,
                payload.appId,
                payload.launchTicket,
                payload.bootstrapEndpoint,
                payload.bootstrapSecret,
                payload.generatedAt);
        }

        private HostedRoomHint? GetUsableHostedRoomHint()
        {
            lock (roomAccessSync)
            {
                if (hostedRoomHint == null) return null;
                var gateway = GetCapabilityHostGateway();
                var configuredAppId = ResolveHostAppId(null) ?? 0;
                if (!gateway.IsAttached ||
                    !gateway.SupportsCapability(DesktopHostedCapabilityFieldNumber) ||
                    configuredAppId <= 0 ||
                    configuredAppId != hostedRoomHint.AppId)
                {
                    hostedRoomHint = null;
                    return null;
                }
                return hostedRoomHint;
            }
        }

        private void HandleRoomAccessNotification(IGPDesktopSessionCommandResult notification)
        {
            if (!string.Equals(
                    notification.RequestId,
                    DesktopHostedBootstrapAvailableRequestId,
                    StringComparison.Ordinal))
            {
                return;
            }

            if (!notification.Success)
            {
                ClearHostedRoomHint();
                return;
            }

            try
            {
                var payload = JObject.Parse(notification.ContentJson);
                var appId = payload.Value<int?>("appId") ?? 0;
                var roomId = payload.Value<string>("roomId")?.Trim() ?? string.Empty;
                if (appId <= 0 || string.IsNullOrWhiteSpace(roomId))
                {
                    ClearHostedRoomHint();
                    return;
                }

                lock (roomAccessSync)
                {
                    hostedRoomHint = new HostedRoomHint(appId, roomId);
                }
                roomAccessAvailable?.Invoke();
            }
            catch (JsonException)
            {
                ClearHostedRoomHint();
            }
        }

        private void ClearHostedRoomHint()
        {
            lock (roomAccessSync)
            {
                hostedRoomHint = null;
            }
        }

        private sealed class HostedRoomHint
        {
            internal int AppId { get; }
            internal string RoomId { get; }

            internal HostedRoomHint(int appId, string roomId)
            {
                AppId = appId;
                RoomId = roomId;
            }
        }

        private sealed class HostedBootstrapPayload
        {
            public string roomId = string.Empty;
            public int appId;
            public string launchTicket = string.Empty;
            public string bootstrapEndpoint = string.Empty;
            public string bootstrapSecret = string.Empty;
            public string generatedAt = string.Empty;
        }
    }
}
