﻿using UnityEngine;
using System;
using System.Collections.Generic;
#if UNITY_IOS && !UNITY_EDITOR
using System.Runtime.InteropServices;
#endif

namespace ir.metrix.unity
{
    public class Metrix
    {

    #if UNITY_ANDROID && !UNITY_EDITOR
        private static AndroidJavaClass metrixAttributionAndroid = new AndroidJavaClass("ir.metrix.attribution.unity.MetrixUnity");
        private static AndroidJavaClass metrixAnalyticsAndroid = new AndroidJavaClass("ir.metrix.analytics.unity.MetrixUnity");
        private static AndroidJavaClass metrixNotificationAndroid = new AndroidJavaClass("ir.metrix.notification.unity.MetrixUnity");
    #endif

    #if UNITY_IOS && !UNITY_EDITOR
        [DllImport ("__Internal")]
        private static extern void _Initialize(string appId);
        [DllImport ("__Internal")]
        private static extern void _NewEvent(string slug);
        [DllImport ("__Internal")]
        private static extern void _NewAttributedEvent(string slug, string customAttributes);
        [DllImport ("__Internal")]
        private static extern void _NewRevenue(string slug, double revenue);
        [DllImport ("__Internal")]
        private static extern void _NewRevenueCurrency(string slug, double revenue, int currency);
        [DllImport ("__Internal")]
        private static extern void _AddUserAttributes(string userAttrs);
        [DllImport ("__Internal")]
        private static extern void _SetPushToken(string pushToken);
        [DllImport ("__Internal")]
        private static extern void _SetStore(string storeName);
        [DllImport ("__Internal")]
        private static extern void _SetAppSecret(int secretId, long info1, long info2, long info3, long info4);
        [DllImport ("__Internal")]
        private static extern void _SetDefaultTracker(string trackerToken);
        [DllImport ("__Internal")]
        private static extern void _SetUserIdListener();
        [DllImport ("__Internal")]
        private static extern void _SetSessionNumberListener();
        [DllImport ("__Internal")]
        private static extern void _SetSessionIdListener();
        [DllImport ("__Internal")]
        private static extern void _SetOnDeeplinkResponseListener(bool shouldLaunchDeferredDeeplink);
        [DllImport ("__Internal")]
        private static extern void _SetOnAttributionChangedListener();
    #endif

        private static GameObject metrixManager = null;
        
        private static Action<string> deferredDeeplinkDelegate = null;
        private static Action<MetrixAttribution> userAttributionDelegate = null;
        private static Action<string> userIdDelegate = null;
        private static Action<string> sessionIdDelegate = null;
        private static Action<int> sessionNumberDelegate = null;   
        
        private static bool shouldLaunchDeferredDeeplink = true;

        // Not used in Android
        public static void Initialize(string appId)
        {
        #if UNITY_IOS && !UNITY_EDITOR
            _Initialize(appId);
        #endif
        }

        // Not used in Android
        public static void SetAppSecret(int secretId, long info1, long info2, long info3, long info4)
        {
        #if UNITY_IOS && !UNITY_EDITOR
            _SetAppSecret(secretId, info1, info2, info3, info4);
        #endif
        }

        // Not used in IOS
        public static void SetSignature(string signature)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAttributionAndroid.CallStatic("setSignature", signature);
        #endif
        }

        public static void SetStore(string storeName)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAttributionAndroid.CallStatic("setStore", storeName);
        #elif UNITY_IOS && !UNITY_EDITOR
            _SetStore(storeName);
        #endif
        }
        
        // Not used in Android
        public static void SetDefaultTracker(string trackerToken)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAttributionAndroid.CallStatic("setDefaultTracker", trackerToken);
        #elif UNITY_IOS && !UNITY_EDITOR
            _SetDefaultTracker(trackerToken);
        #endif
        }

        public static void SetSessionNumberListener(Action<int> callback)
        {
            if (metrixManager == null)
            {
                metrixManager = new GameObject("MetrixManager");
                UnityEngine.Object.DontDestroyOnLoad(metrixManager);
                metrixManager.AddComponent<MetrixMessageHandler>();
            }

            sessionNumberDelegate = callback;
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setSessionNumberListener");
        #elif UNITY_IOS && !UNITY_EDITOR
            _SetSessionNumberListener();
        #endif
        }
        
        public static void SetSessionIdListener(Action<string> callback)
        {
            if (metrixManager == null)
            {
                metrixManager = new GameObject("MetrixManager");
                UnityEngine.Object.DontDestroyOnLoad(metrixManager);
                metrixManager.AddComponent<MetrixMessageHandler>();
            }

            sessionIdDelegate = callback;
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setSessionIdListener");
        #elif UNITY_IOS && !UNITY_EDITOR
            _SetSessionIdListener();
        #endif
        }
        
        public static void NewEvent(string slug)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
			metrixAnalyticsAndroid.CallStatic("newEvent", slug);
        #elif UNITY_IOS && !UNITY_EDITOR
            _NewEvent(slug);
        #endif
        }
        
        // Not used in IOS
        public static void NewEventByName(string name)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
			metrixAnalyticsAndroid.CallStatic("newEventByName", name);
        #endif
        }
        
        public static void NewEvent(string slug, Dictionary<string, object> customAttributes)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("newEvent", slug, ConvertDictionaryToMap(customAttributes));
        #elif UNITY_IOS && !UNITY_EDITOR
            _NewAttributedEvent(slug, ConvertDictionaryToString(customAttributes));
        #endif
        }
        
        // Not used in IOS
        public static void NewEventByName(string name, Dictionary<string, object> customAttributes)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("newEventByName", name, ConvertDictionaryToMap(customAttributes));
        #endif
        }
        
        public static void NewRevenue(string slug, double revenue)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("newRevenue", slug, revenue);
        #elif UNITY_IOS && !UNITY_EDITOR
            _NewRevenue(slug, revenue);
        #endif
        }
        
        // Not used in IOS
        public static void NewRevenueByName(string name, double revenue)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("newRevenueByName", name, revenue);
        #endif
        }
        
        public static void NewRevenue(string slug, double revenue, int currency)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            string cr = "IRR";
            if (currency == 0)
            {
                cr = "IRR";
            }
            else if (currency == 1)
            {
                cr = "USD";
            }
            else if (currency == 2)
            {
                cr = "EUR";
            }
            metrixAnalyticsAndroid.CallStatic("newRevenueCurrency", slug, revenue, cr);
        #elif UNITY_IOS && !UNITY_EDITOR
            _NewRevenueCurrency(slug, revenue, currency);    
        #endif
        }
        
        // Not used in IOS
        public static void NewRevenueByName(string name, double revenue, int currency)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            string cr = "IRR";
            if (currency == 0)
            {
                cr = "IRR";
            }
            else if (currency == 1)
            {
                cr = "USD";
            }
            else if (currency == 2)
            {
                cr = "EUR";
            }
            metrixAnalyticsAndroid.CallStatic("newRevenueByNameCurrency", name, revenue, cr);
        #endif
        }
        
        public static void SetUserAttributes(Dictionary<string, object> userAttrs)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserAttributes", ConvertDictionaryToMap(userAttrs));
        #elif UNITY_IOS && !UNITY_EDITOR
            _SetUserAttributes(ConvertDictionaryToString(userAttrs));
        #endif
        }
        
        // Not used in IOS
        public static void SetUserCustomId(string id)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserCustomId", id);
        #endif
        }

        // Not used in IOS
        public static void DeleteUserCustomId()
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("deleteUserCustomId");
        #endif
        }

        // Not used in IOS
        public static void SetUserFirstName(string firstName)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserFirstName", firstName);
        #endif
        }

        // Not used in IOS
        public static void SetUserLastName(string lastName)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserLastName", lastName);
        #endif
        }

        // Not used in IOS
        public static void SetUserPhoneNumber(string phoneNumber)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserPhoneNumber", phoneNumber);
        #endif
        }

        // Not used in IOS
        public static void SetUserHashedPhoneNumber(string hash)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserHashedPhoneNumber", hash);
        #endif
        }

        // Not used in IOS
        public static void SetUserEmail(string email)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserEmail", email);
        #endif
        }

        // Not used in IOS
        public static void SetUserHashedEmail(string hash)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserHashedEmail", hash);
        #endif
        }

        // Not used in IOS
        public static void SetUserCountry(string country)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserCountry", country);
        #endif
        }

        // Not used in IOS
        public static void SetUserCity(string city)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserCity", city);
        #endif
        }

        // Not used in IOS
        public static void SetUserRegion(string region)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserRegion", region);
        #endif
        }

        // Not used in IOS
        public static void SetUserLocality(string locality)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserLocality", locality);
        #endif
        }

        // Not used in IOS
        public static void SetUserGender(string gender)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserGender", gender);
        #endif
        }

        // Not used in IOS
        public static void SetUserBirthday(long birthday)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAnalyticsAndroid.CallStatic("setUserBirthday", birthday);
        #endif
        }

        // Not used in IOS
        public static void EnableNotifications()
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixNotificationAndroid.CallStatic("enableNotifications");
        #endif
        }

        // Not used in IOS
        public static void DisableNotifications()
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixNotificationAndroid.CallStatic("disableNotifications");
        #endif
        }

        // Not used in IOS
        public static void CreateNotificationDefaultChannel()
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixNotificationAndroid.CallStatic("createNotificationDefaultChannel");
        #endif
        }

        // Not used in IOS
        public static void LoadInAppMessages()
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixNotificationAndroid.CallStatic("loadInAppMessages");
        #endif
        }

        // Not used in IOS
        public static void PauseInAppMessages(bool pause)
        {
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixNotificationAndroid.CallStatic("pauseInAppMessages", pause);
        #endif
        }

        public static void SetShouldLaunchDeeplink(bool launch)
        {
            shouldLaunchDeferredDeeplink = launch;
        }
        
        public static void SetUserIdListener(Action<string> callback)
        {
            if (metrixManager == null)
            {
                metrixManager = new GameObject("MetrixManager");
                UnityEngine.Object.DontDestroyOnLoad(metrixManager);
                metrixManager.AddComponent<MetrixMessageHandler>();
            }

            userIdDelegate = callback;
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAttributionAndroid.CallStatic("setUserIdListener");
        #elif UNITY_IOS && !UNITY_EDITOR
            _SetUserIdListener();
        #endif
        }

        public static void SetOnDeeplinkResponseListener(Action<string> callback)
        {
            if (metrixManager == null)
            {
                metrixManager = new GameObject("MetrixManager");
                UnityEngine.Object.DontDestroyOnLoad(metrixManager);
                metrixManager.AddComponent<MetrixMessageHandler>();
            }

            deferredDeeplinkDelegate = callback;
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAttributionAndroid.CallStatic("setOnDeeplinkResponseListener", shouldLaunchDeferredDeeplink);
        #elif UNITY_IOS && !UNITY_EDITOR
            _SetOnDeeplinkResponseListener(shouldLaunchDeferredDeeplink);
        #endif
        }

        public static void SetOnAttributionChangedListener(Action<MetrixAttribution> callback)
        {
            if (metrixManager == null)
            {
                metrixManager = new GameObject("MetrixManager");
                UnityEngine.Object.DontDestroyOnLoad(metrixManager);
                metrixManager.AddComponent<MetrixMessageHandler>();
            }

            userAttributionDelegate = callback;
        #if UNITY_ANDROID && !UNITY_EDITOR
            metrixAttributionAndroid.CallStatic("setOnAttributionChangedListener");
        #elif UNITY_IOS && !UNITY_EDITOR
            _SetOnAttributionChangedListener();
        #endif
        }

        public static void OnDeferredDeeplink(string uri)
        {
            if (deferredDeeplinkDelegate != null)
            {
                deferredDeeplinkDelegate(uri);
            }
        }
        
        public static void OnReceiveUserIdListener(string userId)
        {
            if (userIdDelegate != null)
            {
                userIdDelegate(userId);
            }
        }

        public static void SessionIDChangeListener(string sessionId)
        {
            if (sessionIdDelegate != null)
            {
                sessionIdDelegate(sessionId);
            }
        }

        public static void SessionNumberChangeListener(string sessionNumber)
        {
            if (sessionNumberDelegate != null)
            {
                sessionNumberDelegate(Int32.Parse(sessionNumber));
            }
        }
        
        public static void OnAttributionChangeListener(string attributionDataString)
        {
            if (userAttributionDelegate != null)
            {
                userAttributionDelegate(new MetrixAttribution(attributionDataString));
            }
        }

        private static string ConvertDictionaryToString(IDictionary<string, object> parameters)
        {
            string parametersString = "";
            foreach (KeyValuePair<string, object> kvp in parameters)
            {
                parametersString = parametersString + kvp.Key;
                parametersString = parametersString + "=";
                parametersString = parametersString + Convert.ToString(kvp.Value);
                parametersString = parametersString + "\n";
            }

            return parametersString;
        }
        
        private static AndroidJavaObject ConvertDictionaryToMap(IDictionary<string, object> parameters)
        {
            AndroidJavaObject javaMap = new AndroidJavaObject("java.util.HashMap");
            // GetMethodID for "put" method: put(Object key, Object value)
            IntPtr putMethod = AndroidJNIHelper.GetMethodID(
                javaMap.GetRawClass(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"
            );

            object[] args = new object[2];
            foreach (KeyValuePair<string, object> kvp in parameters)
            {
                using (AndroidJavaObject k = new AndroidJavaObject("java.lang.String", kvp.Key))
                {
                    object valueToPut;

                    // Handle different C# types and wrap them in AndroidJavaObject if necessary
                    if (kvp.Value is string stringValue)
                    {
                        // Strings can be passed directly. Unity handles conversion to Java String.
                        valueToPut = stringValue; 
                    }
                    else if (kvp.Value is int intValue)
                    {
                        // Wrap int in Java's Integer class
                        valueToPut = new AndroidJavaObject("java.lang.Integer", intValue);
                    }
                    else if (kvp.Value is long longValue)
                    {
                        // Wrap long in Java's Long class
                        valueToPut = new AndroidJavaObject("java.lang.Long", longValue);
                    }
                    else if (kvp.Value is bool boolValue)
                    {
                        // Wrap bool in Java's Boolean class
                        valueToPut = new AndroidJavaObject("java.lang.Boolean", boolValue);
                    }
                    else if (kvp.Value is float floatValue)
                    {
                        // Wrap float in Java's Float class
                        valueToPut = new AndroidJavaObject("java.lang.Float", floatValue);
                    }
                    else if (kvp.Value is double doubleValue)
                    {
                        // Wrap double in Java's Double class
                        valueToPut = new AndroidJavaObject("java.lang.Double", doubleValue);
                    }
                    else if (kvp.Value is AndroidJavaObject ajo)
                    {
                        // If it's already an AndroidJavaObject, use it directly
                        valueToPut = ajo;
                    }
                    else
                    {
                        // Log an error for unsupported types to help with debugging
                        UnityEngine.Debug.LogError($"Metrix: Unsupported value type for AndroidJNI conversion: {kvp.Value?.GetType()} for key: {kvp.Key}");
                        continue; // Skip this entry to prevent crash
                    }

                    args[0] = k;
                    args[1] = valueToPut;

                    // Call the put method
                    AndroidJNI.CallObjectMethod(
                        javaMap.GetRawObject(), putMethod, AndroidJNIHelper.CreateJNIArgArray(args)
                    );
                }
            }

            return javaMap;
        }
    }
}