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

namespace IGP.UnitySDK.CloudArchive
{
    [Serializable]
    public sealed class IGPCloudArchiveLoadResult
    {
        public string data = string.Empty;
        public string version = string.Empty;
    }

    [Serializable]
    public sealed class IGPCloudArchiveSaveResult
    {
        public string slot = string.Empty;
        public int size;
        public string version = string.Empty;
        public string updatedAt = string.Empty;
    }

    [Serializable]
    public sealed class IGPCloudArchiveSaveOptions
    {
        public string? baseVersion;
    }

    [Serializable]
    public sealed class IGPCloudArchiveErrorBody
    {
        public bool success;
        public object? code;
        public string message = string.Empty;
        public object? data;
        public string timestamp = string.Empty;
        public string path = string.Empty;
    }

    [Serializable]
    public sealed class IGPCloudArchiveForwardErrorBody
    {
        public int? httpStatus;
        public object? apiCode;
        public string apiMessage = string.Empty;
        public object? apiData;
        public object? rawBody;
    }

    [Serializable]
    public sealed class IGPCloudArchiveConflictData
    {
        public string currentVersion = string.Empty;
    }

    public sealed class IGPCloudArchiveException : Exception
    {
        public IGPCloudArchiveException(
            string code,
            string message,
            string? bodyJson = null,
            IGPCloudArchiveForwardErrorBody? forwardBody = null)
            : base(message)
        {
            Code = code ?? string.Empty;
            BodyJson = bodyJson;
            ForwardBody = forwardBody;
            HttpStatus = forwardBody?.httpStatus ?? ParseHttpStatus(Code);
            ApiCode = NormalizeApiCode(forwardBody?.apiCode);
            ApiData = forwardBody?.apiData;
            RawBody = forwardBody?.rawBody;
            Body = TryParseLegacyBody(RawBody);
            ApiMessage = string.IsNullOrWhiteSpace(forwardBody?.apiMessage)
                ? Body?.message
                : forwardBody?.apiMessage;
            Conflict = TryParseConflict(ApiData);
        }

        public string Code { get; }
        public string? BodyJson { get; }
        public IGPCloudArchiveForwardErrorBody? ForwardBody { get; }
        public IGPCloudArchiveErrorBody? Body { get; }
        public int? HttpStatus { get; }
        public int? ApiCode { get; }
        public object? ApiCodeValue => ForwardBody?.apiCode ?? Body?.code;
        public string? ApiMessage { get; }
        public object? ApiData { get; }
        public object? RawBody { get; }
        public IGPCloudArchiveConflictData? Conflict { get; }

        private static int? ParseHttpStatus(string code)
        {
            const string prefix = "CLOUD_ARCHIVE_HTTP_";
            if (string.IsNullOrEmpty(code) ||
                !code.StartsWith(prefix, StringComparison.Ordinal))
            {
                return null;
            }

            return int.TryParse(code.Substring(prefix.Length), out var status)
                ? status
                : null;
        }

        private static int? NormalizeApiCode(object? value)
        {
            if (value == null)
            {
                return null;
            }

            try
            {
                if (value is int intValue)
                {
                    return intValue;
                }

                if (value is long longValue &&
                    longValue <= int.MaxValue &&
                    longValue >= int.MinValue)
                {
                    return (int)longValue;
                }

                if (int.TryParse(Convert.ToString(value), out var parsed))
                {
                    return parsed;
                }
            }
            catch
            {
                return null;
            }

            return null;
        }

        internal static IGPCloudArchiveErrorBody? TryParseLegacyBody(object? rawBody)
        {
            if (rawBody == null)
            {
                return null;
            }

            try
            {
                if (rawBody is IGPCloudArchiveErrorBody typed)
                {
                    return typed;
                }

                if (rawBody is string rawString)
                {
                    return JsonConvert.DeserializeObject<IGPCloudArchiveErrorBody>(rawString);
                }

                return JsonConvert.DeserializeObject<IGPCloudArchiveErrorBody>(
                    JsonConvert.SerializeObject(rawBody));
            }
            catch
            {
                return null;
            }
        }

        private static IGPCloudArchiveConflictData? TryParseConflict(object? apiData)
        {
            if (apiData == null)
            {
                return null;
            }

            try
            {
                if (apiData is IGPCloudArchiveConflictData typed)
                {
                    return typed;
                }

                if (apiData is string rawString)
                {
                    return JsonConvert.DeserializeObject<IGPCloudArchiveConflictData>(rawString);
                }

                var json = JsonConvert.SerializeObject(apiData);
                return JsonConvert.DeserializeObject<IGPCloudArchiveConflictData>(json);
            }
            catch
            {
                return null;
            }
        }
    }

    public static class IGPCloudArchive
    {
        public const string DesktopCommand = "cloudArchiveForward";
        public const string Slot1 = "1";
        public const string Slot2 = "2";
        public const string Slot3 = "3";
        public const string Slot4 = "4";
        public const string Slot5 = "5";

        public static async Task<IGPCloudArchiveLoadResult> LoadCloudArchiveAsync(
            IGPRuntimeManager runtimeManager,
            string slot,
            int? appIdOverride = null)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            ValidateSlot(slot);
            var normalizedSlot = NormalizeSlot(slot);

            var payload = new Dictionary<string, object?>
            {
                ["method"] = "GET",
                ["slot"] = normalizedSlot,
            };

            AddAppId(payload, appIdOverride);
            var result = await runtimeManager.ForwardDesktopSessionCommandAsync(
                DesktopCommand,
                JsonConvert.SerializeObject(payload),
                appIdOverride);

            return ParseResult<IGPCloudArchiveLoadResult>(result);
        }

        public static async Task<IGPCloudArchiveSaveResult> SaveCloudArchiveAsync(
            IGPRuntimeManager runtimeManager,
            string slot,
            string data,
            IGPCloudArchiveSaveOptions? options = null,
            int? appIdOverride = null)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            ValidateSlot(slot);
            var normalizedSlot = NormalizeSlot(slot);
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            var body = new Dictionary<string, object?>
            {
                ["data"] = data,
            };

            if (options?.baseVersion != null)
            {
                body["baseVersion"] = options!.baseVersion;
            }

            var payload = new Dictionary<string, object?>
            {
                ["method"] = "PUT",
                ["slot"] = normalizedSlot,
                ["body"] = body,
            };

            AddAppId(payload, appIdOverride);
            var result = await runtimeManager.ForwardDesktopSessionCommandAsync(
                DesktopCommand,
                JsonConvert.SerializeObject(payload),
                appIdOverride);

            return ParseResult<IGPCloudArchiveSaveResult>(result);
        }

        public static void ValidateSlot(string slot)
        {
            if (string.IsNullOrWhiteSpace(slot))
            {
                throw new ArgumentException("Cloud Archive slot is required", nameof(slot));
            }

            if (!IsValidSlot(slot))
            {
                throw new ArgumentException(
                    "Cloud Archive slot must be one of \"1\", \"2\", \"3\", \"4\", or \"5\"",
                    nameof(slot));
            }
        }

        public static bool IsValidSlot(string slot)
        {
            return string.Equals(slot, Slot1, StringComparison.Ordinal) ||
                   string.Equals(slot, Slot2, StringComparison.Ordinal) ||
                   string.Equals(slot, Slot3, StringComparison.Ordinal) ||
                   string.Equals(slot, Slot4, StringComparison.Ordinal) ||
                   string.Equals(slot, Slot5, StringComparison.Ordinal);
        }

        private static string NormalizeSlot(string slot)
        {
            return slot;
        }

        private static void AddAppId(
            IDictionary<string, object?> payload,
            int? appIdOverride)
        {
            if (appIdOverride.HasValue && appIdOverride.Value > 0)
            {
                payload["appId"] = appIdOverride.Value;
            }
        }

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

            if (!result.Success)
            {
                var body = ParseForwardErrorBody(result.ContentJson, result.Code);
                throw new IGPCloudArchiveException(
                    string.IsNullOrWhiteSpace(result.Code)
                        ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                        : result.Code,
                    ResolveErrorMessage(result.Message, body),
                    result.ContentJson,
                    body);
            }

            if (string.IsNullOrWhiteSpace(result.ContentJson))
            {
                return default!;
            }

            var parsed = JsonConvert.DeserializeObject<TResponse>(result.ContentJson);
            return parsed == null ? default! : parsed;
        }

        private static IGPCloudArchiveForwardErrorBody? ParseForwardErrorBody(
            string? contentJson,
            string? commandCode)
        {
            if (string.IsNullOrWhiteSpace(contentJson))
            {
                return null;
            }

            try
            {
                var parsed = JsonConvert.DeserializeObject<IGPCloudArchiveForwardErrorBody>(contentJson);
                if (parsed != null &&
                    (parsed.httpStatus.HasValue ||
                     parsed.apiCode != null ||
                     !string.IsNullOrWhiteSpace(parsed.apiMessage) ||
                     parsed.apiData != null ||
                     parsed.rawBody != null))
                {
                    return parsed;
                }

                var legacy = JsonConvert.DeserializeObject<IGPCloudArchiveErrorBody>(contentJson);
                if (legacy == null)
                {
                    return null;
                }

                return new IGPCloudArchiveForwardErrorBody
                {
                    httpStatus = IGPCloudArchiveExceptionHttpStatus(commandCode),
                    apiCode = legacy.code,
                    apiMessage = legacy.message,
                    apiData = legacy.data,
                    rawBody = legacy,
                };
            }
            catch
            {
                return null;
            }
        }

        private static string ResolveErrorMessage(
            string? desktopMessage,
            IGPCloudArchiveForwardErrorBody? body)
        {
            if (!string.IsNullOrWhiteSpace(desktopMessage))
            {
                return desktopMessage;
            }

            if (!string.IsNullOrWhiteSpace(body?.apiMessage))
            {
                return body.apiMessage;
            }

            var legacy = IGPCloudArchiveException.TryParseLegacyBody(body?.rawBody);
            if (!string.IsNullOrWhiteSpace(legacy?.message))
            {
                return legacy.message;
            }

            return "Cloud Archive command failed";
        }

        private static int? IGPCloudArchiveExceptionHttpStatus(string? commandCode)
        {
            const string prefix = "CLOUD_ARCHIVE_HTTP_";
            if (string.IsNullOrEmpty(commandCode) ||
                !commandCode.StartsWith(prefix, StringComparison.Ordinal))
            {
                return null;
            }

            return int.TryParse(commandCode.Substring(prefix.Length), out var status)
                ? status
                : null;
        }
    }
}
