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

namespace IGP.UnitySDK.GameKit
{
    public sealed class IGPShareException : Exception
    {
        public IGPShareException(
            string code,
            string message,
            string? upstreamJson = null)
            : base(message)
        {
            Code = code ?? string.Empty;
            UpstreamJson = upstreamJson;
        }

        public string Code { get; }
        public string? UpstreamJson { get; }

    }

    public static class IGPShare
    {
        public const int CurrentSchemaVersion = 1;
        public const int MaxImageBytes = 10 * 1024 * 1024;
        private const string PngMimeType = "image/png";
        private const string JpegMimeType = "image/jpeg";

        public static async Task<IGPShareResult> ShareAsync(
            IGPRuntimeManager runtimeManager,
            IGPShareRequest request)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            var appId = ResolveAppId(runtimeManager);
            ValidateRequest(request);

            var imageBytes = PackImages(
                request.images,
                out var imageDescriptors);

            var payload = new Dictionary<string, object?>
            {
                ["schemaVersion"] = CurrentSchemaVersion,
                ["appId"] = appId,
            };

            AddOptionalString(payload, "contentType", request.contentType);
            AddOptionalString(payload, "title", request.title);
            AddOptionalString(payload, "text", request.text);
            AddOptionalString(payload, "templateId", request.templateId);

            if (imageDescriptors.Length > 0)
            {
                payload["images"] = imageDescriptors;
            }

            if (request.data != null)
            {
                payload["data"] = request.data;
            }

            if (request.metadata != null)
            {
                payload["metadata"] = request.metadata;
            }

            var requestJson = JsonConvert.SerializeObject(
                payload,
                new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

            IGPDesktopSessionCommandResult result;
            try
            {
                result = await runtimeManager.RequestDesktopShareAsync(
                    requestJson,
                    imageBytes,
                    appId);
            }
            catch (IGPSDKException ex)
            {
                throw new IGPShareException(
                    string.IsNullOrWhiteSpace(ex.ErrorCode)
                        ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                        : ex.ErrorCode!,
                    string.IsNullOrWhiteSpace(ex.Message)
                        ? "Share command failed"
                        : ex.Message);
            }

            return ParseResult(result);
        }

        private static int ResolveAppId(IGPRuntimeManager runtimeManager)
        {
            var appId = runtimeManager.Config?.appId ?? 0;
            if (appId <= 0)
            {
                throw new IGPShareException(
                    IGPErrorCodes.ERR_APP_ID_REQUIRED,
                    "IGPConfig.appId is required before share can be used");
            }

            return appId;
        }

        private static void ValidateRequest(IGPShareRequest request)
        {
            if (request.schemaVersion != CurrentSchemaVersion)
            {
                throw new ArgumentException(
                    $"Share schemaVersion must be {CurrentSchemaVersion}",
                    nameof(request));
            }

            if (!HasShareContent(request))
            {
                throw new ArgumentException(
                    "Share request must include text, images, templateId, data, or metadata",
                    nameof(request));
            }
        }

        private static bool HasShareContent(IGPShareRequest request)
        {
            return !string.IsNullOrWhiteSpace(request.title) ||
                   !string.IsNullOrWhiteSpace(request.text) ||
                   !string.IsNullOrWhiteSpace(request.templateId) ||
                   request.data != null ||
                   request.metadata != null ||
                   (request.images != null && request.images.Length > 0);
        }

        private static byte[] PackImages(
            IGPShareImage[]? images,
            out IGPShareImageDescriptor[] descriptors)
        {
            if (images == null || images.Length == 0)
            {
                descriptors = Array.Empty<IGPShareImageDescriptor>();
                return Array.Empty<byte>();
            }

            var descriptorList = new List<IGPShareImageDescriptor>(images.Length);
            using (var stream = new MemoryStream())
            {
                long totalBytes = 0;
                for (var i = 0; i < images.Length; i++)
                {
                    var image = images[i];
                    ValidateImage(image, i);

                    var bytes = image.bytes;
                    totalBytes += bytes.Length;
                    if (totalBytes > MaxImageBytes)
                    {
                        throw new IGPShareException(
                            "SHARE_IMAGES_TOO_LARGE",
                            "Share image bytes must not exceed 10 MiB in total");
                    }

                    var offset = checked((int)stream.Position);
                    stream.Write(bytes, 0, bytes.Length);

                    descriptorList.Add(new IGPShareImageDescriptor
                    {
                        mimeType = image.mimeType,
                        byteOffset = offset,
                        byteLength = bytes.Length,
                        width = image.width,
                        height = image.height,
                        fileName = NormalizeOptionalString(image.fileName),
                        altText = NormalizeOptionalString(image.altText),
                    });
                }

                descriptors = descriptorList.ToArray();
                return stream.ToArray();
            }
        }

        private static void ValidateImage(IGPShareImage image, int index)
        {
            if (image == null)
            {
                throw new ArgumentException(
                    $"Share image at index {index} is null",
                    nameof(image));
            }

            if (!IsAllowedImageMimeType(image.mimeType))
            {
                throw new ArgumentException(
                    $"Share image at index {index} must use image/png or image/jpeg",
                    nameof(image));
            }

            if (image.bytes == null || image.bytes.Length == 0)
            {
                throw new ArgumentException(
                    $"Share image at index {index} must contain bytes",
                    nameof(image));
            }

            if (image.width.HasValue && image.width.Value <= 0)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(image.width),
                    "Share image width must be greater than 0 when provided");
            }

            if (image.height.HasValue && image.height.Value <= 0)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(image.height),
                    "Share image height must be greater than 0 when provided");
            }
        }

        private static bool IsAllowedImageMimeType(string? mimeType)
        {
            return string.Equals(mimeType, PngMimeType, StringComparison.OrdinalIgnoreCase) ||
                   string.Equals(mimeType, JpegMimeType, StringComparison.OrdinalIgnoreCase);
        }

        private static void AddOptionalString(
            IDictionary<string, object?> payload,
            string key,
            string? value)
        {
            var normalized = NormalizeOptionalString(value);
            if (normalized != null)
            {
                payload[key] = normalized;
            }
        }

        private static string? NormalizeOptionalString(string? value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return null;
            }

            return value.Trim();
        }

        private static IGPShareResult ParseResult(IGPDesktopSessionCommandResult result)
        {
            if (result == null)
            {
                throw new IGPShareException(
                    IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED,
                    "Desktop session returned no command result");
            }

            if (!result.Success)
            {
                throw new IGPShareException(
                    string.IsNullOrWhiteSpace(result.Code)
                        ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                        : result.Code,
                    string.IsNullOrWhiteSpace(result.Message)
                        ? "Share command failed"
                        : result.Message,
                    result.ContentJson);
            }

            if (string.IsNullOrWhiteSpace(result.ContentJson))
            {
                return new IGPShareResult
                {
                    success = true,
                    message = string.IsNullOrWhiteSpace(result.Message) ? "ok" : result.Message,
                    contentJson = string.Empty,
                };
            }

            try
            {
                var token = JToken.Parse(result.ContentJson);
                if (token.Type != JTokenType.Object)
                {
                    throw new IGPShareException(
                        "SHARE_INVALID_RESPONSE",
                        "Share response contentJson must be a JSON object",
                        result.ContentJson);
                }

                var parsed = token.ToObject<IGPShareResult>() ?? new IGPShareResult();
                if (((JObject)token)["success"] == null)
                {
                    parsed.success = true;
                }

                if (string.IsNullOrWhiteSpace(parsed.message))
                {
                    parsed.message = string.IsNullOrWhiteSpace(result.Message) ? "ok" : result.Message;
                }

                parsed.contentJson = result.ContentJson;
                return parsed;
            }
            catch (IGPShareException)
            {
                throw;
            }
            catch (JsonException ex)
            {
                throw new IGPShareException(
                    "SHARE_INVALID_RESPONSE",
                    $"Share response contentJson is invalid JSON: {ex.Message}",
                    result.ContentJson);
            }
        }

        [Serializable]
        private sealed class IGPShareImageDescriptor
        {
            public string mimeType = string.Empty;
            public int byteOffset;
            public int byteLength;
            public int? width;
            public int? height;
            public string? fileName;
            public string? altText;
        }
    }
}
