
namespace Guru
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using UnityEngine;

    
    public class GuruAnalytics
    {
        // Plugin Version
        public const string Version = "1.11.0";
        
        public static readonly string Tag = "[ANU]";
        private static readonly string ActionName = "logger_error";
        
        private static IAnalyticsAgent _agent;
        
        public static IAnalyticsAgent Agent
        {
            get
            {
                if (_agent == null)
                {
    #if UNITY_EDITOR
                    _agent = new AnalyticsAgentStub();
    #elif UNITY_ANDROID
                    _agent = new AnalyticsAgentAndroid();
    #elif UNITY_IOS
                    _agent = new AnalyticsAgentIOS();
    #endif
                }
                return _agent;
            }
        }

        private static Dictionary<string, string> _userProperties;
        /// <summary>
        /// 用户属性缓存字典
        /// </summary>
        public static Dictionary<string, string> UserProperties
        {
            get
            {
                if (_userProperties == null)
                {
                    _userProperties = new Dictionary<string, string>(10);
                }
                return _userProperties;
            }
        }

        /// <summary>
        /// 错误 code 表
        /// </summary>
        public static List<int> ErrorCodeList = new List<int>();

        private static bool _autoSyncProperties = false;
        private static bool _enableErrorLog = false;
        
        /// <summary>
        /// 启动日志错误上报
        /// </summary>
        public static bool EnableErrorLog
        {
            get => _enableErrorLog;
            set
            {
                _enableErrorLog = value;
                if (_enableErrorLog) InitCallbacks(); // 激活错误日志回调
                if (Agent != null) Agent.EnableErrorLog = _enableErrorLog;
            }
        }

        #region 公用接口
        
        /// <summary>
        /// 初始化接口
        /// </summary>
        public static void Init(string appId, string deviceInfo, bool isDebug = false, 
            bool enableErrorLog = false, bool syncProperties = false)
        {
            Debug.Log($"{Tag} --- Guru Analytics [{Version}] initialing...");
            
            _autoSyncProperties = syncProperties;
            _enableErrorLog = enableErrorLog;
            Agent?.Init(appId, deviceInfo, isDebug);
            if(_enableErrorLog) InitCallbacks(); // 激活错误日志回调
        }

        /// <summary>
        /// 设置视图名称
        /// </summary>
        /// <param name="screenName"></param>
        public static void SetScreen(string screenName)
        {
            if (string.IsNullOrEmpty(screenName)) return;
            CacheUserProperty($"screen_name", screenName);
            Agent?.SetScreen(screenName);
        }

        /// <summary>
        /// 设置广告ID
        /// </summary>
        /// <param name="id"></param>
        public static void SetAdId(string id)
        {
            if (string.IsNullOrEmpty(id)) return;
            CacheUserProperty($"ad_id", id);
            Agent?.SetAdId(id);
        }

        /// <summary>
        /// 设置用户属性
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public static void SetUserProperty(string key, string value)
        {
            if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) return;
            CacheUserProperty(key, value); // 添加用户属性
            Agent?.SetUserProperty(key, value);
        }
        /// <summary>
        /// 设置Firebase ID
        /// </summary>
        /// <param name="id"></param>
        public static void SetFirebaseId(string id)
        {
            if (string.IsNullOrEmpty(id)) return;
            CacheUserProperty($"firebase_id", id);
            Agent?.SetFirebaseId(id);
        }

        /// <summary>
        /// 设置Adjust ID
        /// </summary>
        /// <param name="id"></param>
        public static void SetAdjustId(string id)
        {
            if (string.IsNullOrEmpty(id)) return;
            CacheUserProperty($"adjust_id", id);
            Agent?.SetAdjustId(id);
        }

        /// <summary>
        /// 设置设备ID
        /// </summary>
        /// <param name="deviceId"></param>
        public static void SetDeviceId(string deviceId)
        {
            if (string.IsNullOrEmpty(deviceId)) return;
            CacheUserProperty($"device_id", deviceId);
            Agent?.SetDeviceId(deviceId);
        }


        public static void SetAndroidID(string androidId)
        {
            if (string.IsNullOrEmpty(androidId)) return;
            CacheUserProperty(Analytics.PropertyAndroidID, androidId);
        }
        
        public static void SetIDFV(string idfv)
        {
            if (string.IsNullOrEmpty(idfv)) return;
            CacheUserProperty(Analytics.PropertyIDFV, idfv);
        }
        
        public static void SetIDFA(string idfa)
        {
            if (string.IsNullOrEmpty(idfa)) return;
            CacheUserProperty(Analytics.PropertyIDFA, idfa);
        }


        /// <summary>
        /// 设置用户ID
        /// </summary>
        /// <param name="uid"></param>
        public static void SetUid(string uid)
        {
            if (string.IsNullOrEmpty(uid)) return;
            CacheUserProperty($"uid", uid);
            Agent?.SetUid(uid);
        }

        /// <summary>
        /// 上报事件成功率
        /// </summary>
        public static void ReportEventSuccessRate() => Agent?.ReportEventSuccessRate();

        /// <summary>
        /// 上报打点事件
        /// </summary>
        /// <param name="eventName">事件名称</param>
        /// <param name="data">INT类型的值</param>
        public static void LogEvent(string eventName, Dictionary<string, dynamic> data = null)
        {
            if(_autoSyncProperties)
                UpdateAllUserProperties(); // 每次打点更新用户属性
            
            string raw = "";
            if (data != null && data.Count > 0)
            {
                raw = BuildParamsJson(data);
            }
            Debug.Log($"{Tag} event:{eventName} | raw: {raw}");
            Agent?.LogEvent(eventName, raw);
        }

        private static string BuildParamsString(Dictionary<string, dynamic> data)
        {
            string raw = "";
            List<string> strList = new List<string>(data.Count);
            foreach (var kvp in data)
            {
                strList.Add(BuildStringValue(kvp));
                raw = string.Join(",", strList);
            }
            return raw;
        }

        private static string BuildParamsJson(Dictionary<string, dynamic> data)
        {
            try
            {
                //  强制转换加入国家设置
                return JsonConvert.SerializeObject(data, new JsonSerializerSettings()
                {
                    Culture = new CultureInfo("en-US"),
                });
            }
            catch (Exception e)
            {
                Debug.LogError(e);
            }

            return "";
        }

        /// <summary>
        /// 构建带有类型格式的Str值
        /// </summary>
        /// <param name="kvp"></param>
        /// <returns></returns>
        private static string BuildStringValue(KeyValuePair<string, dynamic> kvp)
        {
            if (kvp.Value is int || kvp.Value is long)
            {
                return $"{kvp.Key}:i{kvp.Value}";
            }
            
            if (kvp.Value is double || kvp.Value is float)
            {
                double dValue = (double)((object)kvp.Value);
                return $"{kvp.Key}:d{dValue.ToString("F11", new CultureInfo("en-US"))}";  // 保留精度进行转换
            }

            return $"{kvp.Key}:s{kvp.Value}";
        }

        /// <summary>
        /// 设置太极02值
        /// </summary>
        /// <param name="value"></param>
        public static void SetTch02Value(double value)
        {
            Debug.Log($"{Tag} set tch_02_value:{value}");
            Agent?.SetTch02Value(value);
        }

        #endregion

        #region iOS独有接口

#if UNITY_IOS
        // 触发测试崩溃埋点
        public static void TestCrash() => AnalyticsAgentIOS.TestCrashEvent();
#endif

        #endregion

        #region 用户属性

        /// <summary>
        /// 记录用户属性
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private static void CacheUserProperty(string key, string value)
        {
            bool needUpdate = !UserProperties.ContainsKey(key) || UserProperties[key] != value;
            UserProperties[key] = value;
            // if (needUpdate) UpdateAllUserProperties();
        }


        private static void UpdateAllUserProperties()
        {
            if (UserProperties != null && UserProperties.Count > 0)
            {
                var keys = UserProperties.Keys.ToArray();
                int i = 0;
                string key = "";
                while (i < keys.Length)
                {
                    key = keys[i];
                    if(!string.IsNullOrEmpty(key)) SetUserProperty(key, UserProperties[key]);
                    i++;
                }
                keys = null;
            }
        }


        #endregion

        #region 日志回调

        private static void InitCallbacks()
        {
            try
            {
                GuruSDKCallback.RemoveCallback(OnSDKCallback);
                GuruSDKCallback.AddCallback(OnSDKCallback);
                if (Agent != null)
                    Agent.InitCallback(GuruSDKCallback.ObjectName, GuruSDKCallback.MethodName);
            }
            catch (Exception ex)
            {
                Analytics.LogCrashlytics(ex);
            }

        }


        /// <summary>
        /// 获取SDK回调
        /// </summary>
        /// <param name="msg"></param>
        private static void OnSDKCallback(string raw)
        {
            if (string.IsNullOrEmpty(raw)) return;
            if (!raw.Contains($"\"{ActionName}\"")) return; // 不对其他行为的日志进行过滤
            ParseWithJson(raw);
        }

        /// <summary>
        /// 上报错误信息
        /// </summary>
        /// <param name="code"></param>
        /// <param name="errorInfo"></param>
        private static void OnLoggerErrorEvent(int code, string errorInfo = "")
        {
            // Debug.Log($"{Tag} --- OnLoggerErrorEvent: code:{code}\tinfo:{errorInfo}");
            
            var codeString = ((AnalyticsCode)code).ToString();
            if (string.IsNullOrEmpty(codeString) || codeString == AnalyticsCode.Unknown.ToString()) codeString = $"ErrorCode:{code}";
            
            Dictionary<string, dynamic> parameters = new Dictionary<string, dynamic>()
            {
                {"item_category", "error_event"},
                {"item_name", codeString},
                {"country", IPMConfig.IPM_COUNTRY_CODE},
                {"network", Application.internetReachability.ToString()},
            };
            if (!string.IsNullOrEmpty(errorInfo))
            {
                // if (errorInfo.Contains("\"")) errorInfo = errorInfo.Replace("\"", "\\\"");
                
                int len = 96;
                if (errorInfo.Length > len) errorInfo = errorInfo.TrimStart().Substring(0, len);
            }
            else
            {
                errorInfo = "empty error info";
            }
            
            parameters["err"] = errorInfo;
            
            Debug.Log($"{Tag} ------ ErrorLogInfo:: code:{codeString}\tinfo:{errorInfo}");

#if !UNITY_EDITOR 
            // Only for firebase GA
            Analytics.LogEvent("dev_audit", parameters,
                new Analytics.EventSetting() { EnableFirebaseAnalytics = true });
#endif
         
        }


        private static bool ParseWithJson(string json)
        {
            Debug.Log($"{Tag} ------ ParseWithJson: json:\n{json}");
            
            int code = (int)AnalyticsCode.Unknown;
            string info = json;
            try
            {
                var dict = JsonConvert.DeserializeObject<JObject>(json);
                if(dict != null && dict.TryGetValue("data", out var jData))
                {
                    var j = jData.Value<JObject>();
                    if (j != null && j.TryGetValue("code", out var jCode))
                    {
                        code = jCode.Value<int>();
                        
                        if (j.TryGetValue("msg", out var jMsg))
                        {
                            info = jMsg.Value<string>();
                        }
                        else
                        {
                            info = $"wrong msg format: {json}";
                        }
                    }
                }
                else
                {
                    info = "no data property";
                }
                ReportCodeInfo(code, info);
                return true;
            }
            catch (Exception ex)
            {
                string p = "\"msg\":\"";
                string m = json;
                if (json.Contains(p)) m = json.Substring(json.IndexOf(p) + p.Length);
                info = $"JsonEX:{m}";
                // Debug.Log($"{Tag} --- {info}");
                Analytics.LogCrashlytics(json, false);
                Analytics.LogCrashlytics(info);
                ReportCodeInfo(code, info);
            }
            return false;
        }

        private static void ParseWithRaw(string raw)
        {
            int code = (int)AnalyticsCode.Unknown;
            string info = raw;
            
            //------- message send to unity ----------
            Debug.Log($"{Tag} get callback errorInfo:\n{raw}");

            string patten = "";
            string patten2 = "";
            int idx = 0;
            int idx2 = 0;
            int len = 0;


            patten = "msg\":\"";
            if (raw.Contains(patten))
            {

                info = raw.Substring(raw.IndexOf(patten, StringComparison.Ordinal) + patten.Length);
                if (!string.IsNullOrEmpty(info))
                {
                    if (info.StartsWith("\"")) info = info.Substring(1, info.Length - 1);
                    if (info.EndsWith("\"}}")) info = info.Replace("\"}}", "");
                }
                else
                {
                    info = "msg is null";
                }
            }
            else
            {
                info = "no msg property";
            }
            
            try
            {
          
                idx = raw.IndexOf(patten, StringComparison.Ordinal) + patten.Length;
                string act = raw.Substring(idx, ActionName.Length);
                if (act == ActionName)
                {
                    patten = "code\":";
                    patten2 = ",\"msg";
                    idx = raw.IndexOf(patten);
                    idx2 = raw.IndexOf(patten2);
                
                    len = idx2 - (idx + patten.Length);
                    if (len > 0)
                    {
                        string c = raw.Substring(idx + patten.Length, len);
                        int.TryParse(c, out code);
                    }
                    
                    // Catch target code to report errors
                    switch ((AnalyticsCode)code)
                    {
                        case AnalyticsCode.Network_Lost:
                        case AnalyticsCode.ERROR_API:
                        case AnalyticsCode.ERROR_RESPONSE:
                        case AnalyticsCode.ERROR_CACHE_CONTROL:
                        case AnalyticsCode.ERROR_DELETE_EXPIRED:
                        case AnalyticsCode.ERROR_LOAD_MARK:
                        case AnalyticsCode.ERROR_DNS:
                        case AnalyticsCode.ERROR_ZIP:
                            OnLoggerErrorEvent(code, info);
                            return;
                    }
                    
                    return;
                }
                
            }
            catch (Exception ex)
            {
                Debug.LogError($"{Tag} Catch ex: {ex}\tJson:{raw}");
                Analytics.LogCrashlytics(raw, false);
                Analytics.LogCrashlytics($"{Tag} --- Json:{raw}  Ex:{ex}");
                OnLoggerErrorEvent(code, info);
                return;
            }

            if (raw.Contains("msg"))
            {
                Analytics.LogCrashlytics(raw, false);
                Analytics.LogCrashlytics($"{Tag} --- format error:{raw}");
                OnLoggerErrorEvent(code, raw.Substring(raw.IndexOf("msg\":" ) + 5));
            }
        }


        private static void ReportCodeInfo(int code, string info)
        {
            var ac = (AnalyticsCode)code;
            Debug.Log($"{Tag} ------ Get Code And Info: code:{code}[{ac}]  \tinfo:{info}");
            bool canCatch = false;
            switch (ac)
            {
                case AnalyticsCode.Unknown:
                case AnalyticsCode.DELETE_EXPIRED:
                case AnalyticsCode.UPLOAD_FAIL:
                case AnalyticsCode.Network_Lost:
                case AnalyticsCode.CRONET_INIT_FAIL:
                case AnalyticsCode.CRONET_INIT_EXCEPTION:
                case AnalyticsCode.ERROR_API:
                case AnalyticsCode.ERROR_RESPONSE:
                case AnalyticsCode.ERROR_CACHE_CONTROL:
                case AnalyticsCode.ERROR_DELETE_EXPIRED:
                case AnalyticsCode.ERROR_LOAD_MARK:
                case AnalyticsCode.ERROR_DNS:
                case AnalyticsCode.ERROR_ZIP:
                case AnalyticsCode.ERROR_DNS_CACHE:
                case AnalyticsCode.CRONET_INTERCEPTOR:
                    canCatch = true;
                    break;
            }

            if (code > 100 && code <= 200)
            {
                // 100 < code <= 200
                canCatch = true;
            }

            if (ErrorCodeList != null && ErrorCodeList.Count > 0)
            {
                if (ErrorCodeList[0] == -1)
                {
                    canCatch = true;
                }
                else
                {
                    canCatch = ErrorCodeList.Contains(code);
                }
            }

            if(canCatch) OnLoggerErrorEvent(code, info);
        }

        #endregion

        #region UNIT_TEST


#if UNITY_EDITOR

        public static void TestOnCallback(string msg)
        {
            OnSDKCallback(msg);
        }

#endif

        #endregion
        
    }
    
    /// <summary>
    /// 网络状态枚举
    /// </summary>
    public enum AnalyticsCode
    {
        Unknown = -1,
        
        DELETE_EXPIRED = 12,
        UPLOAD_FAIL = 14,
        Network_Lost = 22,
        CRONET_INIT_FAIL = 26,
        CRONET_INIT_EXCEPTION = 27,
        
        ERROR_API = 101,
        ERROR_RESPONSE = 102,
        ERROR_CACHE_CONTROL = 103,
        ERROR_DELETE_EXPIRED = 104,
        ERROR_LOAD_MARK = 105,
        ERROR_DNS = 106,
        ERROR_ZIP = 107,
        ERROR_DNS_CACHE = 108,
        CRONET_INTERCEPTOR = 109,
    }
}

