﻿#if UNITY_ANDROID && !UNITY_EDITOR
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using Platform101XP.Serialization;
using Platform101XP.Entities;

namespace Platform101XP.Platforms
{
    public class AndroidPlatform : IPlatform
    {
        const string LOG_TAG = "UnitySdkPlugin";

        private readonly AndroidJavaClass sdkPlatform;
        private readonly AndroidJavaClass eventListenerClass;
        private readonly AndroidJavaClass eventListenerBuilderClass;
        private readonly AndroidJavaClass advertisingListenerBuilderClass;

        public AndroidPlatform()
        {
            sdkPlatform = new AndroidJavaClass("com.platform101xp.sdk.Platform101XP");
            eventListenerClass = new AndroidJavaClass("com.platform101xp.plugin.Platform101XPEventListener");
            eventListenerBuilderClass = new AndroidJavaClass("com.platform101xp.plugin.Platform101XPEventListenerBuilder");
            advertisingListenerBuilderClass = new AndroidJavaClass("com.platform101xp.plugin.Platform101XPAdvertisingListenerBuilder");
        }

        public Token GetToken()
        {
            AndroidJavaObject javaToken = ObjectFromStaticCall("getToken");
            if (javaToken == null)
                return null;

            return new Token
            {
                accessToken = javaToken.Get<string>("accessToken"),
                expires = javaToken.Get<long>("expires"),
                type = javaToken.Get<string>("type"),
                provider = javaToken.Get<string>("provider"),
            };
        }

        public Account GetAccount()
        {
            AndroidJavaObject javaAccount = ObjectFromStaticCall("getAccount");
            if (javaAccount == null)
                return null;

            return new Account
            {
                accountId = javaAccount.Get<long>("accountId"),
                portalId = javaAccount.Get<long>("portalId"),
                userGameId = javaAccount.Get<long>("userGameId"),
                mobileId = javaAccount.Get<long>("mobileId"),
                userName = javaAccount.Get<string>("userName"),
                userPhotoUrl = javaAccount.Get<string>("userPhotoUrl"),
                isGuest = javaAccount.Get<bool>("isGuest"),
            };
        }

        public Product GetProduct(string productId)
        {
            var productJavaObject = ObjectFromStaticCall("getProductById", productId);
            if (productJavaObject != null)
            {
                var json = StringFromEventListenerStaticCall("productToJson", productJavaObject);
                Debug.Log($"[Android Wrapper] Product JSON: {json}");
                return string.IsNullOrEmpty(json) ? null : Json.Deserialize<Product>(json);
            }
            else
            {
                Debug.Log("[Android Wrapper] Product with such ID not found!");
                return null;
            }
        }

        public void Initialize()
        {
            // Setup advertising listener
            AndroidJavaObject advertisingListener = advertisingListenerBuilderClass.GetStatic<AndroidJavaObject>("listener");
            CallStaticWithParams("setAdvertisingListener", advertisingListener);

            // Setup event listener
            AndroidJavaObject eventListener = eventListenerBuilderClass.GetStatic<AndroidJavaObject>("listener");
            CallStaticWithParams("initialize", eventListener);
        }

        public void ShowTroublesDialog()
        {
            CallStaticMethod("showTroublesDialog");
        }

        public void Authorize()
        {
            CallStaticMethod("authorize");
        }

        public void ShowProfile()
        {
            CallStaticMethod("showProfile");
        }

        public void ApplicationReview()
        {
            CallStaticMethod("forcedApplicationReview");
        }

        public void ForcedApplicationReview()
        {
            CallStaticMethod("forcedApplicationReview");
        }

        public void ApplicationInvite()
        {
            CallStaticMethod("applicationInvite");
        }

        public void PurchaseProduct(string productId, string serverId, Dictionary<string, string> properties)
        {
            LogEvent("PurchaseProduct Stub productId:" + productId);
            AndroidJavaObject product = ObjectFromStaticCall("getProductById", productId);
            AndroidJavaObject purchaseProperties = null;
            if (properties != null)
                purchaseProperties = ConvertDictionaryToMap(properties);
            if (product != null)
            {
                CallStaticWithParams("purchaseProduct", product, serverId, purchaseProperties);
            }
        }

        public void SendInviteLink(string eventName)
        {
            CallStaticWithParams("sendInviteLink", eventName);
        }

        public void GetSocialFriends()
        {
            AndroidJavaObject snType =
     new AndroidJavaClass("com.platform101xp.authorize.social_networks.utils.Platform101XPSNType").GetStatic<AndroidJavaObject>("FACEBOOK");
            CallStaticWithParams("getSocialFriends", snType);
        }

        public void SendEventToAnalytic(string eventName, Dictionary<string, string> eventProperties)
        {
            AndroidJavaObject eventMap = null;
            if (eventProperties != null)
                eventMap = ConvertDictionaryToMap(eventProperties);
            CallStaticWithParams("analyticsTrack", eventName, eventMap);
        }

        public void LogEvent(string message)
        {
            new AndroidJavaClass("android.util.Log").CallStatic<int>("d", LOG_TAG, message);
        }

        public void ShowMessage(string message)
        {
            AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
            activity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
            {
                AndroidJavaObject alertBuilder = new AndroidJavaObject("android.app.AlertDialog$Builder", activity); ;
                alertBuilder.Call<AndroidJavaObject>("setMessage", message)
                     .Call<AndroidJavaObject>("setCancelable", true)
                     .Call<AndroidJavaObject>("create")
                     .Call("show");
            }));
        }

        public void FinishPurchase(PurchaseResult purchaseResult)
        {
            if (purchaseResult != null)
            {
                AndroidJavaObject internalPurchase = new AndroidJavaObject(
                    "com.android.billingclient.api.Purchase",
                    purchaseResult.originalJson,
                    purchaseResult.signature
                );
                AndroidJavaObject purchase = new AndroidJavaObject(
                    "com.platform101xp.billing.dto.Platform101XPGooglePurchase",
                    purchaseResult.transactionId,
                    internalPurchase
                );
                CallStaticWithParams("finishPurchase", purchase);
            }
            else
            {
                LogEvent("billingPurchase is null, please check PurchaseResult: " + purchaseResult);
            }
        }

        public void RestoreProducts()
        {
            CallStaticMethod("restoreProducts");
        }

        public void PrefetchProduct(string productId)
        {
            // Not implemented.
        }

        public void GetProducts(List<string> productIds)
        {
            AndroidJavaObject productIdsList = null;
            if (productIds != null)
            {
                productIdsList = GetConvertedStringList(productIds);
            }

            CallStaticWithParams("getProducts", productIdsList);
        }

        public void CrashLog(string tag, string text)
        {
            CallStaticWithParams("crashLog", tag, text);
        }

        public void ShowWebStore(Dictionary<string, string> purchaseProperties, List<string> allowedLocale)
        {
            AndroidJavaObject jPurchaseProperties = null;
            if (purchaseProperties != null)
            {
                jPurchaseProperties = ConvertDictionaryToMap(purchaseProperties);
            }

            if (allowedLocale != null)
            {
                AndroidJavaObject jAllowedLocale = GetConvertedStringList(allowedLocale);
                CallStaticWithParams("showWebStore", jPurchaseProperties, jAllowedLocale);
            }
            else
            {
                CallStaticWithParams("showWebStore", jPurchaseProperties);
            }
        }

        public void CheckMobilePurchase(string itemName)
        {
            CallStaticWithParams("checkMobilePurchase", itemName);
        }

        public void TrackSentryEvent(string eventName, Dictionary<string, object> values)
        {
            var jMap = ConvertDictionaryToMap(values);
            CallStaticWithParams("analyticsSentryTrack", eventName, jMap);
        }

        public void TrackSentryException(Exception exception)
        {
            string managed = exception?.ToString() ?? "null";
            string message = $"Managed exception forwarded from Unity exception:\n{managed}";
            var jException = new AndroidJavaObject("java.lang.Exception", message);
            CallStaticWithParams("analyticsSentryTrack", jException);
        }

        // MARK: - Advertising

        public void LoadRewardedAd(string id)
        {
            CallStaticWithParams("loadRewardedAd", id);
        }

        public void ShowRewardedAd()
        {
            CallStaticMethod("showRewardedAd");
        }

        public bool IsRewardedVideoLoaded()
        {
            return CallStaticMethodWithReturnBool("isRewardedVideoLoaded");
        }



        public void LoadInterstitialAd(string id)
        {
            CallStaticWithParams("loadInterstitialAd", id);
        }

        public void ShowInterstitialAd()
        {
            CallStaticMethod("showInterstitialAd");
        }

        public bool IsInterstitialAdLoaded()
        {
            return CallStaticMethodWithReturnBool("isInterstitialAdLoaded");
        }

        public void LoadBannerAd(string id)
        {
            CallStaticWithParams("loadBannerAd", id);
        }

        public void ShowBannerAd()
        {
            CallStaticMethod("showBannerAd");
        }

        public void HideBannerAd()
        {
            CallStaticMethod("hideBannerAd");
        }

        public bool IsBannerAdLoaded()
        {
            return CallStaticMethodWithReturnBool("isBannerAdLoaded");
        }



        public void LoadRewardedInterstitialAd(string id)
        {
            CallStaticWithParams("loadRewardedInterstitialAd", id);
        }

        public void ShowRewardedInterstitialAd()
        {
            CallStaticMethod("showRewardedInterstitialAd");
        }



        public void ResetConsentInformation()
        {
            CallStaticMethod("resetConsentInformation");
        }

        // MARK: - Private JNI code

        private void CallStaticMethod(string methodName)
        {
            if (sdkPlatform == null)
            {
                Debug.Log("sdkPlatform is null");
                return;
            }

            try
            {
                sdkPlatform.CallStatic(methodName);
            }
            catch (Exception e)
            {
                Debug.LogError($"Exception occurred while calling static method {methodName}: {e.Message}");
            }
        }

        private bool CallStaticMethodWithReturnBool(string methodName)
        {
            if (sdkPlatform == null)
            {
                Debug.Log("sdkPlatform is null");
                return false;
            }

            try
            {
                return sdkPlatform.CallStatic<bool>(methodName);
            }
            catch (Exception e)
            {
                Debug.LogError($"Exception occurred while calling static method {methodName}: {e.Message}");
                return false;
            }
        }

        private void CallStaticWithParams(string methodName, params object[] args)
        {
            if (sdkPlatform == null)
            {
                Debug.Log("sdkPlatform is null");
                return;
            }

            try
            {
                sdkPlatform.CallStatic(methodName, args);
            }
            catch (Exception e)
            {
                Debug.LogError($"Exception occurred while calling static method {methodName} with parameters: {e.Message}");
            }
        }

        private AndroidJavaObject ObjectFromStaticCall(string methodName, params object[] args)
        {
            if (sdkPlatform == null)
            {
                Debug.Log("sdkPlatform is null");
                return null;
            }
            else
            {
                return sdkPlatform.CallStatic<AndroidJavaObject>(methodName, args);
            }
        }

        private string StringFromEventListenerStaticCall(string methodName, params object[] args)
        {
            if (eventListenerClass == null)
            {
                Debug.LogError("The platform helper is null!");
                return null;
            }
            else
            {
                return eventListenerClass.CallStatic<string>(methodName, args);
            }
        }

        private AndroidJavaObject ConvertDictionaryToMap(Dictionary<string, string> dictionary)
        {
            AndroidJavaObject map = new AndroidJavaObject("java.util.HashMap");
            System.IntPtr putMethod = AndroidJNIHelper.GetMethodID(map.GetRawClass(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
            foreach (var entry in dictionary)
            {
                AndroidJNI.CallObjectMethod(
                    map.GetRawObject(),
                    putMethod,
                    AndroidJNIHelper.CreateJNIArgArray(new object[] { entry.Key, entry.Value })
                );
            }
            return map;
        }

        private Dictionary<string, object> ConvertMapToDictionary(AndroidJavaObject map)
        {
            Dictionary<string, object> result = new Dictionary<string, object>();
            AndroidJavaObject entrySet = map.Call<AndroidJavaObject>("entrySet");
            AndroidJavaObject iteratorEntrySet = entrySet.Call<AndroidJavaObject>("iterator");
            while (iteratorEntrySet.Call<bool>("hasNext"))
            {
                AndroidJavaObject entry = iteratorEntrySet.Call<AndroidJavaObject>("next");
                string key = entry.Call<string>("getKey");
                AndroidJavaObject valueJO = entry.Call<AndroidJavaObject>("getValue");
                object value = CastAndroidJavaObject(valueJO);
                result.Add(key, value);
            }
            return result;
        }

        private object CastAndroidJavaObject(AndroidJavaObject input)
        {
            string className = input.Call<AndroidJavaObject>("getClass").Call<string>("getSimpleName");
            object result;
            switch (className)
            {
                case "String":
                    result = input.Call<string>("toString");
                    break;
                case "Boolean":
                    result = input.Call<bool>("booleanValue");
                    break;
                case "Integer":
                    result = input.Call<int>("intValue");
                    break;
                case "Long":
                    result = input.Call<long>("longValue");
                    break;
                default:
                    result = className;
                    break;
            }
            return result;
        }

        private AndroidJavaObject GetConvertedStringList(List<string> inputList)
        {
            AndroidJavaObject list = new AndroidJavaObject("java.util.ArrayList");
            System.IntPtr addMethod = AndroidJNIHelper.GetMethodID(list.GetRawClass(), "add", "(Ljava/lang/Object;)Z");
            foreach (string item in inputList)
            {
                AndroidJNI.CallBooleanMethod(list.GetRawObject(), addMethod, AndroidJNIHelper.CreateJNIArgArray(new string[] { item }));
            }
            return list;
        }

        // Convert C# Dictionary<string, object> -> java.util.Map<String, Object>
        private AndroidJavaObject ConvertDictionaryToMap(IDictionary<string, object> dict)
        {
            var jMap = new AndroidJavaObject("java.util.HashMap");
            if (dict == null) return jMap;

            foreach (var kv in dict)
            {
                var jVal = ToJavaValue(kv.Value);
                jMap.Call<AndroidJavaObject>("put", kv.Key, jVal);
            }
            return jMap;
        }

        // Convert C# IList/array -> java.util.ArrayList
        private AndroidJavaObject ConvertListToArrayList(IEnumerable list)
        {
            var jList = new AndroidJavaObject("java.util.ArrayList");
            if (list == null) return jList;

            foreach (var item in list)
            {
                var jItem = ToJavaValue(item);
                jList.Call<bool>("add", jItem);
            }
            return jList;
        }

        // Box supported primitives and recurse into collections
        // Returns either an AndroidJavaObject or a string (never a plain C# primitive)
        private object ToJavaValue(object value)
        {
            if (value == null) return null;

            switch (value)
            {
                case string s:
                    return s; // java.lang.String automatically
                case bool b:
                    return new AndroidJavaObject("java.lang.Boolean", b);
                case byte by:
                    return new AndroidJavaObject("java.lang.Byte", by);
                case sbyte sby:
                    return new AndroidJavaObject("java.lang.Byte", (byte)sby);
                case short sh:
                    return new AndroidJavaObject("java.lang.Short", sh);
                case ushort ush:
                    return new AndroidJavaObject("java.lang.Integer", (int)ush);
                case int i:
                    return new AndroidJavaObject("java.lang.Integer", i);
                case uint ui:
                    return new AndroidJavaObject("java.lang.Long", (long)ui);
                case long l:
                    return new AndroidJavaObject("java.lang.Long", l);
                case ulong ul: // If small enough, fit in Long; else BigInteger
                    if (ul <= long.MaxValue)
                        return new AndroidJavaObject("java.lang.Long", (long)ul);
                    else
                        return new AndroidJavaObject("java.math.BigInteger", ul.ToString());
                case float f:
                    return new AndroidJavaObject("java.lang.Float", f);
                case double d:
                    return new AndroidJavaObject("java.lang.Double", d);
                case decimal dec:
                    return new AndroidJavaObject("java.math.BigDecimal", dec.ToString());
                case char c:
                    return c.ToString(); // treat as 1-char String
                case IDictionary<string, object> nestedDict:
                    return ConvertDictionaryToMap(nestedDict);
                case IDictionary nonGenericDict:
                    {
                        var temp = new Dictionary<string, object>();
                        foreach (DictionaryEntry de in nonGenericDict)
                            temp[de.Key?.ToString() ?? "null"] = de.Value;
                        return ConvertDictionaryToMap(temp);
                    }
                case IEnumerable enumerable when value is not string:
                    return ConvertListToArrayList(enumerable);
                default:
                    return value.ToString(); // Fallback: Stringify
            }
        }
    }
}
#endif
