#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 int code;
        public string message = string.Empty;
        public object? data;
        public string timestamp = string.Empty;
        public string path = string.Empty;
    }

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

    public sealed class IGPCloudArchiveException : Exception
    {
        public IGPCloudArchiveException(
            string code,
            string message,
            string? bodyJson = null,
            IGPCloudArchiveErrorBody? body = null)
            : base(message)
        {
            Code = code ?? string.Empty;
            BodyJson = bodyJson;
            Body = body;
            HttpStatus = ParseHttpStatus(Code);
            ApiCode = body?.code;
            Conflict = TryParseConflict(body);
        }

        public string Code { get; }
        public string? BodyJson { get; }
        public IGPCloudArchiveErrorBody? Body { get; }
        public int? HttpStatus { get; }
        public int? ApiCode { 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 IGPCloudArchiveConflictData? TryParseConflict(
            IGPCloudArchiveErrorBody? body)
        {
            if (body == null || body.data == null)
            {
                return null;
            }

            try
            {
                if (body.data is IGPCloudArchiveConflictData typed)
                {
                    return typed;
                }

                var json = JsonConvert.SerializeObject(body.data);
                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 = ParseErrorBody(result.ContentJson);
                throw new IGPCloudArchiveException(
                    string.IsNullOrWhiteSpace(result.Code)
                        ? IGPErrorCodes.ERR_DESKTOP_SESSION_REQUIRED
                        : result.Code,
                    string.IsNullOrWhiteSpace(result.Message)
                        ? body?.message ?? "Cloud Archive command failed"
                        : result.Message,
                    result.ContentJson,
                    body);
            }

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

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

        private static IGPCloudArchiveErrorBody? ParseErrorBody(string? contentJson)
        {
            if (string.IsNullOrWhiteSpace(contentJson))
            {
                return null;
            }

            try
            {
                return JsonConvert.DeserializeObject<IGPCloudArchiveErrorBody>(contentJson);
            }
            catch
            {
                return null;
            }
        }
    }
}
