#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using IGP.UnitySDK.Abstractions;
using IGP.UnitySDK.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace IGP.UnitySDK
{
    public partial class IGPRuntimeManager
    {
        private const int DesktopUserContextCapabilityFieldNumber = 3;
        private IIGPHostCommandGateway? capabilityHostGatewayOverride;
        private IGPUserProfile? currentDesktopResolvedUserProfile;

        public IGPUserProfile? CurrentUserProfile
        {
            get
            {
                if (CanUseDesktopProfileProvider())
                {
                    var userId = currentDesktopUserContext!.userId.Trim();
                    if (currentDesktopResolvedUserProfile != null &&
                        string.Equals(
                            currentDesktopResolvedUserProfile.Id,
                            userId,
                            StringComparison.Ordinal))
                    {
                        return currentDesktopResolvedUserProfile.Clone();
                    }

                    return BuildDesktopProfile(
                        userId,
                        content: null,
                        currentDesktopUserProfile);
                }

                return GetDirectProfileSnapshot();
            }
        }

        /// <summary>
        /// Gets the current profile through the selected Host Session when its signed-in
        /// user provider is available; otherwise uses the explicit Direct session.
        /// </summary>
        public async Task<IGPUserProfile> GetCurrentUserProfileAsync(
            CancellationToken cancellationToken = default)
        {
            EnsureCapabilityRequestAvailable();
            if (CanUseDesktopProfileProvider())
            {
                var gateway = GetCapabilityHostGateway();
                using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
                    cancellationToken,
                    RuntimeCancellationToken);
                var result = await gateway.SendAsync(
                    (int)IGPDesktopSessionCommandType.GetDesktopUserProfile,
                    DesktopUserContextCapabilityFieldNumber,
                    cancellationToken: linkedCts.Token).ConfigureAwait(false);
                EnsureDesktopCommandSucceeded(result);

                JObject content;
                try
                {
                    content = JObject.Parse(result.ContentJson);
                }
                catch (JsonException exception)
                {
                    throw DirectProtocolError("Host user profile response is invalid", exception);
                }

                var profile = BuildDesktopProfile(
                    currentDesktopUserContext!.userId.Trim(),
                    content,
                    currentDesktopUserProfile);
                currentDesktopResolvedUserProfile = profile.Clone();
                return profile;
            }

            var root = await SendDirectObjectAsync(
                HttpMethod.Get,
                "/auth/session",
                body: null,
                authenticated: true,
                cancellationToken).ConfigureAwait(false);
            var directProfile = ParseDirectUserProfile(root);
            EnsureIdentityCompatibleWithDesktop(directProfile.Id);
            UpdateDirectProfile(directProfile);
            return directProfile.Clone();
        }

        /// <summary>
        /// Gets basic player profiles through the provider selected before the request.
        /// </summary>
        public async Task<IReadOnlyList<IGPPlayerProfileSummary>> GetPlayerProfilesAsync(
            IEnumerable<string> userIds,
            CancellationToken cancellationToken = default)
        {
            EnsureCapabilityRequestAvailable();
            var normalizedIds = NormalizeProfileIds(userIds);
            JToken responseToken;

            if (CanUseDesktopProfileProvider())
            {
                var gateway = GetCapabilityHostGateway();
                using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
                    cancellationToken,
                    RuntimeCancellationToken);
                var result = await gateway.SendAsync(
                    (int)IGPDesktopSessionCommandType.GetDesktopPlayerProfiles,
                    DesktopUserContextCapabilityFieldNumber,
                    JsonConvert.SerializeObject(new { userIds = normalizedIds }),
                    cancellationToken: linkedCts.Token).ConfigureAwait(false);
                EnsureDesktopCommandSucceeded(result);
                try
                {
                    responseToken = JToken.Parse(result.ContentJson);
                }
                catch (JsonException exception)
                {
                    throw DirectProtocolError("Host player profiles response is invalid", exception);
                }
            }
            else
            {
                responseToken = await SendDirectJsonAsync(
                    HttpMethod.Post,
                    "/users/profile-summaries/query",
                    new JObject { ["userIds"] = new JArray(normalizedIds) },
                    authenticated: true,
                    cancellationToken).ConfigureAwait(false);
            }

            return ParseProfileSummaries(responseToken, normalizedIds);
        }

        private bool CanUseDesktopProfileProvider()
        {
            var gateway = GetCapabilityHostGateway();
            return gateway.IsAttached &&
                gateway.SupportsCapability(DesktopUserContextCapabilityFieldNumber) &&
                currentDesktopUserContext != null &&
                !string.IsNullOrWhiteSpace(currentDesktopUserContext.userId) &&
                string.Equals(
                    currentDesktopUserContext.loginState,
                    "signedIn",
                    StringComparison.OrdinalIgnoreCase);
        }

        private IIGPHostCommandGateway GetCapabilityHostGateway()
        {
            return capabilityHostGatewayOverride ?? this;
        }

        private void EnsureCapabilityRequestAvailable()
        {
            if (isDestroyed || RuntimeCancellationToken.IsCancellationRequested)
            {
                throw new ObjectDisposedException(nameof(IGPRuntimeManager));
            }
        }

        private static void EnsureDesktopCommandSucceeded(IGPDesktopSessionCommandResult result)
        {
            if (result.Success)
            {
                return;
            }

            var code = string.IsNullOrWhiteSpace(result.Code)
                ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                : result.Code;
            var message = string.IsNullOrWhiteSpace(result.Message)
                ? "Host capability request failed"
                : result.Message;
            throw new IGPSDKException(message, code);
        }

        private static IGPUserProfile BuildDesktopProfile(
            string userId,
            JObject? content,
            IGPDesktopUserProfile? attachedProfile)
        {
            var avatar = content?["avatar"] as JObject;
            var avatarFrame = content?["avatarFrame"] as JObject;
            var nickname = content?.Value<string>("nickname")
                ?? attachedProfile?.nickname
                ?? string.Empty;
            var displayTag = content?.Value<string>("displayTag")
                ?? attachedProfile?.displayTag
                ?? string.Empty;
            var discriminator = content?.Value<int?>("discriminator")
                ?? ParseDiscriminator(displayTag);
            if (string.IsNullOrWhiteSpace(displayTag) &&
                !string.IsNullOrWhiteSpace(nickname) &&
                discriminator > 0)
            {
                displayTag = nickname + "#" + discriminator.ToString(CultureInfo.InvariantCulture);
            }

            return new IGPUserProfile
            {
                Id = userId,
                Nickname = nickname,
                Discriminator = discriminator,
                DisplayTag = displayTag,
                AvatarUrl = content?.Value<string>("avatarUrl")
                    ?? avatar?.Value<string>("imageUrl")
                    ?? string.Empty,
                AvatarFrameUrl = content?.Value<string>("avatarFrameUrl")
                    ?? avatarFrame?.Value<string>("imageUrl")
                    ?? string.Empty,
            };
        }

        private static int ParseDiscriminator(string displayTag)
        {
            if (string.IsNullOrWhiteSpace(displayTag))
            {
                return 0;
            }

            var separator = displayTag.LastIndexOf('#');
            return separator >= 0 &&
                separator + 1 < displayTag.Length &&
                int.TryParse(
                    displayTag.Substring(separator + 1),
                    NumberStyles.None,
                    CultureInfo.InvariantCulture,
                    out var discriminator)
                ? discriminator
                : 0;
        }

        private static string[] NormalizeProfileIds(IEnumerable<string> userIds)
        {
            if (userIds == null)
            {
                throw new ArgumentNullException(nameof(userIds));
            }

            var result = new List<string>();
            var seen = new HashSet<string>(StringComparer.Ordinal);
            foreach (var value in userIds)
            {
                var normalized = (value ?? string.Empty).Trim();
                if (normalized.Length == 0 || normalized.Length > 128)
                {
                    throw new ArgumentException(
                        "Each user ID must contain 1 to 128 characters",
                        nameof(userIds));
                }

                if (seen.Add(normalized))
                {
                    result.Add(normalized);
                }
            }

            if (result.Count < 1 || result.Count > 50)
            {
                throw new ArgumentException(
                    "Between 1 and 50 unique user IDs must be provided",
                    nameof(userIds));
            }

            return result.ToArray();
        }

        private static IReadOnlyList<IGPPlayerProfileSummary> ParseProfileSummaries(
            JToken token,
            IReadOnlyList<string> requestedIds)
        {
            if (!(token is JArray array))
            {
                throw DirectProtocolError("Player profiles response is invalid");
            }

            var byId = new Dictionary<string, IGPPlayerProfileSummary>(StringComparer.Ordinal);
            foreach (var item in array)
            {
                var id = item.Value<string>("id")?.Trim() ?? string.Empty;
                if (id.Length == 0 || byId.ContainsKey(id))
                {
                    continue;
                }

                byId[id] = new IGPPlayerProfileSummary
                {
                    Id = id,
                    Nickname = item.Value<string>("nickname") ?? string.Empty,
                    AvatarUrl = item.Value<string>("avatarUrl") ?? string.Empty,
                    AvatarFrameUrl = item.Value<string>("avatarFrameUrl") ?? string.Empty,
                };
            }

            var result = new List<IGPPlayerProfileSummary>(requestedIds.Count);
            foreach (var id in requestedIds)
            {
                if (byId.TryGetValue(id, out var profile))
                {
                    result.Add(profile);
                }
            }

            return result;
        }

        internal void ConfigureHostCapabilityGatewayForTests(IIGPHostCommandGateway? gateway)
        {
            capabilityHostGatewayOverride = gateway;
        }
    }
}
