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

namespace IGP.UnitySDK.LyingBottle
{
    public enum IGPLyingBottleHttpMethod
    {
        Get = 0,
        Post = 1,
    }

    [Serializable]
    public sealed class IGPLyingBottleRouteSpec
    {
        public IGPLyingBottleRouteSpec(
            IGPLyingBottleHttpMethod method,
            string route,
            params string[] pathParamNames)
        {
            Method = method;
            Route = route ?? string.Empty;
            PathParamNames = pathParamNames ?? Array.Empty<string>();
        }

        public IGPLyingBottleHttpMethod Method { get; }
        public string Route { get; }
        public string[] PathParamNames { get; }
    }

    [Serializable]
    public sealed class IGPLyingBottleCommandResult
    {
        public string requestId = string.Empty;
        public bool success;
        public string code = string.Empty;
        public string message = string.Empty;
        public string contentJson = string.Empty;
    }

    public sealed class IGPLyingBottleException : Exception
    {
        public IGPLyingBottleException(
            string code,
            string message,
            string? upstreamJson = null)
            : base(message)
        {
            Code = code ?? string.Empty;
            UpstreamJson = upstreamJson;
        }

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

        public int? HttpStatus()
        {
            var match = Regex.Match(Code, "^LYING_BOTTLE_HTTP_(\\d+)$");
            return match.Success && int.TryParse(match.Groups[1].Value, out var status)
                ? status
                : null;
        }
    }

    public static class IGPLyingBottleRoutes
    {
        public const string StartupSync = "startup-sync";
        public const string Bottles = "bottles";
        public const string Bottle = "bottles/:subBottleId";
        public const string BottleConfig = "bottles/:subBottleId/config";
        public const string SessionStart = "bottles/:subBottleId/sessions/start";
        public const string SessionEnd = "bottles/:subBottleId/sessions/end";
        public const string CoinProgress = "bottles/:subBottleId/coin/progress";
        public const string CoinInfo = "bottles/:subBottleId/coin/info";
        public const string AbilityUpgrade = "bottles/:subBottleId/abilities/upgrade";
        public const string TicketPurchase = "bottles/:subBottleId/tickets/purchase";
        public const string GachaDraw = "bottles/:subBottleId/gacha/draw";
        public const string Inventory = "bottles/:subBottleId/inventory";
        public const string Records = "bottles/:subBottleId/records";

        public static readonly IGPLyingBottleRouteSpec StartupSyncSpec =
            Post(StartupSync);
        public static readonly IGPLyingBottleRouteSpec BottlesSpec =
            Get(Bottles);
        public static readonly IGPLyingBottleRouteSpec BottleSpec =
            Get(Bottle, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec BottleConfigSpec =
            Get(BottleConfig, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec SessionStartSpec =
            Post(SessionStart, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec SessionEndSpec =
            Post(SessionEnd, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec CoinProgressSpec =
            Post(CoinProgress, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec CoinInfoSpec =
            Post(CoinInfo, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec AbilityUpgradeSpec =
            Post(AbilityUpgrade, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec TicketPurchaseSpec =
            Post(TicketPurchase, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec GachaDrawSpec =
            Post(GachaDraw, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec InventorySpec =
            Get(Inventory, "subBottleId");
        public static readonly IGPLyingBottleRouteSpec RecordsSpec =
            Get(Records, "subBottleId");

        public static readonly IGPLyingBottleRouteSpec[] PlayerRoutes =
        {
            StartupSyncSpec,
            BottlesSpec,
            BottleSpec,
            BottleConfigSpec,
            SessionStartSpec,
            SessionEndSpec,
            CoinProgressSpec,
            CoinInfoSpec,
            AbilityUpgradeSpec,
            TicketPurchaseSpec,
            GachaDrawSpec,
            InventorySpec,
            RecordsSpec,
        };

        public static IGPLyingBottleRouteSpec Find(
            IGPLyingBottleHttpMethod method,
            string route)
        {
            foreach (var spec in PlayerRoutes)
            {
                if (spec.Method == method &&
                    string.Equals(spec.Route, route, StringComparison.Ordinal))
                {
                    return spec;
                }
            }

            throw new ArgumentException($"Unsupported Lying Bottle route: {method} {route}");
        }

        private static IGPLyingBottleRouteSpec Get(
            string route,
            params string[] pathParamNames)
        {
            return new IGPLyingBottleRouteSpec(
                IGPLyingBottleHttpMethod.Get,
                route,
                pathParamNames);
        }

        private static IGPLyingBottleRouteSpec Post(
            string route,
            params string[] pathParamNames)
        {
            return new IGPLyingBottleRouteSpec(
                IGPLyingBottleHttpMethod.Post,
                route,
                pathParamNames);
        }
    }

    public static class IGPLyingBottle
    {
        public const string DesktopCommand = "lyingBottleForward";

        public static async Task<TResponse> CallAsync<TResponse>(
            IGPRuntimeManager runtimeManager,
            IGPLyingBottleRouteSpec spec,
            IDictionary<string, string>? pathParams = null,
            IDictionary<string, string>? query = null,
            int? appIdOverride = null)
        {
            return await CallAsync<object, TResponse>(
                runtimeManager,
                spec,
                pathParams,
                body: null,
                query,
                appIdOverride);
        }

        public static async Task<TResponse> CallAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            IGPLyingBottleRouteSpec spec,
            IDictionary<string, string>? pathParams = null,
            TRequest? body = default,
            IDictionary<string, string>? query = null,
            int? appIdOverride = null)
        {
            if (runtimeManager == null)
            {
                throw new ArgumentNullException(nameof(runtimeManager));
            }

            ValidateRouteSpec(spec);
            ValidatePathParams(spec, pathParams);
            ValidateQuery(query);

            var payload = new Dictionary<string, object?>
            {
                ["method"] = spec.Method == IGPLyingBottleHttpMethod.Get ? "GET" : "POST",
                ["route"] = spec.Route,
            };

            if (pathParams != null && pathParams.Count > 0)
            {
                payload["pathParams"] = pathParams;
            }

            if (query != null && query.Count > 0)
            {
                payload["query"] = query;
            }

            if (spec.Method == IGPLyingBottleHttpMethod.Post && body != null)
            {
                payload["body"] = body;
            }

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

            return ParseResult<TResponse>(result);
        }

        public static async Task<TResponse> StartupSyncAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            TRequest body,
            int? appIdOverride = null)
        {
            return await CallAsync<TRequest, TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.StartupSyncSpec,
                body: body,
                appIdOverride: appIdOverride);
        }

        public static async Task<TResponse> GetBottlesAsync<TResponse>(
            IGPRuntimeManager runtimeManager,
            int? appIdOverride = null)
        {
            return await CallAsync<TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.BottlesSpec,
                appIdOverride: appIdOverride);
        }

        public static async Task<TResponse> GetBottleAsync<TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            int? appIdOverride = null)
        {
            return await CallAsync<TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.BottleSpec,
                PathParams("subBottleId", subBottleId),
                appIdOverride: appIdOverride);
        }

        public static async Task<TResponse> GetBottleConfigAsync<TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            int? appIdOverride = null)
        {
            return await CallAsync<TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.BottleConfigSpec,
                PathParams("subBottleId", subBottleId),
                appIdOverride: appIdOverride);
        }

        public static async Task<TResponse> StartSessionAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            TRequest body,
            int? appIdOverride = null)
        {
            return await CallWithSubBottleAsync<TRequest, TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.SessionStartSpec,
                subBottleId,
                body,
                appIdOverride);
        }

        public static async Task<TResponse> EndSessionAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            TRequest body,
            int? appIdOverride = null)
        {
            return await CallWithSubBottleAsync<TRequest, TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.SessionEndSpec,
                subBottleId,
                body,
                appIdOverride);
        }

        public static async Task<TResponse> ReportCoinProgressAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            TRequest body,
            int? appIdOverride = null)
        {
            return await CallWithSubBottleAsync<TRequest, TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.CoinProgressSpec,
                subBottleId,
                body,
                appIdOverride);
        }

        public static async Task<TResponse> GetCoinInfoAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            TRequest body,
            int? appIdOverride = null)
        {
            return await CallWithSubBottleAsync<TRequest, TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.CoinInfoSpec,
                subBottleId,
                body,
                appIdOverride);
        }

        public static async Task<TResponse> UpgradeAbilityAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            TRequest body,
            int? appIdOverride = null)
        {
            return await CallWithSubBottleAsync<TRequest, TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.AbilityUpgradeSpec,
                subBottleId,
                body,
                appIdOverride);
        }

        public static async Task<TResponse> PurchaseTicketsAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            TRequest body,
            int? appIdOverride = null)
        {
            return await CallWithSubBottleAsync<TRequest, TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.TicketPurchaseSpec,
                subBottleId,
                body,
                appIdOverride);
        }

        public static async Task<TResponse> DrawGachaAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            TRequest body,
            int? appIdOverride = null)
        {
            return await CallWithSubBottleAsync<TRequest, TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.GachaDrawSpec,
                subBottleId,
                body,
                appIdOverride);
        }

        public static async Task<TResponse> GetInventoryAsync<TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            int? appIdOverride = null)
        {
            return await CallAsync<TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.InventorySpec,
                PathParams("subBottleId", subBottleId),
                appIdOverride: appIdOverride);
        }

        public static async Task<TResponse> GetRecordsAsync<TResponse>(
            IGPRuntimeManager runtimeManager,
            string subBottleId,
            int? appIdOverride = null)
        {
            return await CallAsync<TResponse>(
                runtimeManager,
                IGPLyingBottleRoutes.RecordsSpec,
                PathParams("subBottleId", subBottleId),
                appIdOverride: appIdOverride);
        }

        public static Dictionary<string, string> PathParams(
            string key,
            string value)
        {
            return new Dictionary<string, string>
            {
                [key] = value,
            };
        }

        public static Dictionary<string, string> Query(
            string key,
            string value)
        {
            return new Dictionary<string, string>
            {
                [key] = value,
            };
        }

        private static async Task<TResponse> CallWithSubBottleAsync<TRequest, TResponse>(
            IGPRuntimeManager runtimeManager,
            IGPLyingBottleRouteSpec spec,
            string subBottleId,
            TRequest body,
            int? appIdOverride)
        {
            return await CallAsync<TRequest, TResponse>(
                runtimeManager,
                spec,
                PathParams("subBottleId", subBottleId),
                body,
                appIdOverride: appIdOverride);
        }

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

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

            if (typeof(TResponse) == typeof(string))
            {
                return (TResponse)(object)(result.ContentJson ?? string.Empty);
            }

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

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

        private static void ValidateRouteSpec(IGPLyingBottleRouteSpec spec)
        {
            if (spec == null)
            {
                throw new ArgumentNullException(nameof(spec));
            }

            foreach (var allowed in IGPLyingBottleRoutes.PlayerRoutes)
            {
                if (allowed.Method == spec.Method &&
                    string.Equals(allowed.Route, spec.Route, StringComparison.Ordinal))
                {
                    return;
                }
            }

            throw new ArgumentException($"Unsupported Lying Bottle route: {spec.Method} {spec.Route}");
        }

        private static void ValidatePathParams(
            IGPLyingBottleRouteSpec spec,
            IDictionary<string, string>? pathParams)
        {
            foreach (var paramName in spec.PathParamNames)
            {
                if (pathParams == null ||
                    !pathParams.TryGetValue(paramName, out var value) ||
                    string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentException(
                        $"Lying Bottle route requires path param: {paramName}",
                        nameof(pathParams));
                }
            }
        }

        private static void ValidateQuery(IDictionary<string, string>? query)
        {
            if (query == null)
            {
                return;
            }

            foreach (var item in query)
            {
                if (string.IsNullOrWhiteSpace(item.Key))
                {
                    throw new ArgumentException("Lying Bottle query keys must be non-empty strings", nameof(query));
                }

                if (item.Value == null)
                {
                    throw new ArgumentException("Lying Bottle query values must be strings", nameof(query));
                }
            }
        }
    }
}
