namespace Guru
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using UnityEngine;
    using System.Diagnostics.CodeAnalysis;
    
    public class GuruAnalytics
    {
        // Plugin Version
        private const string Version = "1.12.0";
        
        public static readonly string Tag = "[ANU]";
        private static readonly string ActionName = "logger_error";
        private const int EventPriorityDefault = 10;


        private static GuruAnalytics _instance;

        public static GuruAnalytics Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new GuruAnalytics();
                }
                return _instance;
            }
        }


        private static bool _isReady = false;
        
        public static bool IsReady => _isReady;
        
        private IAnalyticsAgent _agent;
        private IAnalyticsAgent Agent
        {
            get
            {
                if (_agent == null)
                {
#if UNITY_EDITOR
                    _agent = new AnalyticsAgentMock();
#elif UNITY_ANDROID
                    _agent = new AnalyticsAgentAndroid();
#elif UNITY_IOS
                    _agent = new AnalyticsAgentIOS();
#endif
                }
                
                if (_agent == null)
                {
                    throw new NotImplementedException("You Should Implement IAnalyticsAgent on platform first.");
                }
                
                return _agent;
            }
        }

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

        /// <summary>
        /// 错误 code 表
        /// </summary>
        private readonly List<int> _errorCodeList = new List<int>();
        private bool _enableErrorLog;

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

        #region 公用接口
        
        /// <summary>
        /// 初始化接口
        /// </summary>
        public void Init(string appId, string deviceInfo, Action onInitComplete, bool isDebug = false, string firebaseId = "")
        {
            Debug.Log($"{Tag} --- Guru Analytics [{Version}] initialing...");
            if (_isReady) return;

            if (Agent == null)
            {
                // Agent 不存在则抛异常
                throw new NotImplementedException($"{Tag} Agent is null, please check your implementation of IAnalyticsAgent.");
            }

            string groupId = "not_set";
            string baseUrl = "";
            string[] uploadIpAddress = null;
            bool enabelErrorLog = true;
            
            // 获取云控参数
            // TODO: 针对 GuruSDK 整体的云控值做一个分组的解决方案
            var guruInitParams = GuruAnalyticsConfigManager.GetInitParams();
            
            if (guruInitParams != null)
            {
                // 如果分组实验打开
                groupId = guruInitParams.groupId;
                baseUrl = guruInitParams.baseUrl;
                uploadIpAddress = guruInitParams.uploadIpAddress;
                enabelErrorLog = guruInitParams.enableErrorLog;
            }
            
            if (!string.IsNullOrEmpty(firebaseId))
                Agent.SetFirebaseId(firebaseId); // 需要提前设置 Firebase ID
            
            // 分组ID赋值
            _experimentGroupId = groupId;
            EnableErrorLog = enabelErrorLog;
            
            // 初始化参数
            Agent.Init(appId, deviceInfo, baseUrl, uploadIpAddress, onInitComplete, isDebug);
            
            _isReady = true;
            Debug.Log($"{Tag} --- Guru Analytics [{Version}] initialized.");
            Debug.Log($"{Tag} --- GroupId: {groupId}");
        }
        
        /// <summary>
        /// 设置视图名称
        /// </summary>
        /// <param name="screenName"></param>
        public void SetScreen(string screenName)
        {
            if (!_isReady) return;
            if (string.IsNullOrEmpty(screenName)) return;
            // CacheUserProperty($"screen_name", screenName);
            Agent.SetScreen(screenName);
        }

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

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

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

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


        public void SetAndroidId(string androidId)
        {
            if (!_isReady) return;
            if (string.IsNullOrEmpty(androidId)) return;
            // CacheUserProperty(Analytics.PropertyAndroidID, androidId);
        }
        
        public void SetIDFV(string idfv)
        {
            if (!_isReady) return;
            if (string.IsNullOrEmpty(idfv)) return;
            // CacheUserProperty(Analytics.PropertyIDFV, idfv);
        }
        
        public void SetIDFA(string idfa)
        {
            if (!_isReady) return;
            if (string.IsNullOrEmpty(idfa)) return;
            // CacheUserProperty(Analytics.PropertyIDFA, idfa);
        }


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

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

        /// <summary>
        /// 上报打点事件
        /// </summary>
        /// <param name="eventName">事件名称</param>
        /// <param name="data">INT类型的值</param>
        /// <param name="priority"></param>
        [SuppressMessage("ReSharper", "Unity.PerformanceCriticalCodeInvocation")]
        public void LogEvent(string eventName, Dictionary<string, dynamic> data = null, int priority = -1)
        {
            string raw = "";
            if (data != null && data.Count > 0)
            {
                raw = BuildParamsJson(data);
            }
            if (priority < 0) priority = EventPriorityDefault;
            Debug.Log($"{Tag} --- LogEvent GuruAnalytics:{eventName} | raw: {raw} | priority: {priority}");
            Agent.LogEvent(eventName, raw, priority);
        }

        /*
        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;
        }
        */

        [SuppressMessage("ReSharper", "Unity.PerformanceCriticalCodeInvocation")]
        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 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 void CacheUserProperty(string key, string value)
        {
            // bool needUpdate = !UserProperties.ContainsKey(key) || UserProperties[key] != value;
            UserProperties[key] = value;
            // if (needUpdate) UpdateAllUserProperties();
        }


        #endregion

        #region 日志回调

        private 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="raw"></param>
        private 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 void OnLoggerErrorEvent(int code, string errorInfo = "")
        {
            // Debug.Log($"{Tag} --- OnLoggerErrorEvent: code:{code}\t info:{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}");
            
            // Only for firebase GA
            Analytics.TrackEvent("dev_audit", parameters, new Analytics.EventSetting() { EnableFirebaseAnalytics = true });
        }


        private void 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);
            }
            catch (Exception)
            {
                string p = "\"msg\":\"";
                string m = json;
                if (json.Contains(p)) m = json.Substring(json.IndexOf(p, StringComparison.Ordinal) + p.Length);
                info = $"JsonEX:{m}";
                // Debug.Log($"{Tag} --- {info}");
                Analytics.LogCrashlytics(json, false);
                Analytics.LogCrashlytics(info);
                ReportCodeInfo(code, info);
            }
        }

        private 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:
                case AnalyticsCode.EVENT_LOOKUP:
                case AnalyticsCode.EVENT_SESSION_ACTIVE:    
                    canCatch = true;
                    break;
            }

            if (!canCatch && code is > 100 and <= 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)
        {
            Instance.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失败
        CRONET_INIT_EXCEPTION = 27,     // 开启Cronet报错
        
        ERROR_API = 101,                // 调用api出错
        ERROR_RESPONSE = 102,           // api返回结果错误
        ERROR_CACHE_CONTROL = 103,      // 设置cacheControl出错
        ERROR_DELETE_EXPIRED = 104,     // 删除过期事件出错
        ERROR_LOAD_MARK = 105,          // 从数据库取事件以及更改事件状态为正在上报出错
        ERROR_DNS = 106,                // dns 错误
        ERROR_ZIP = 107,                // zip 错误
        ERROR_DNS_CACHE = 108,          // zip 错误
        CRONET_INTERCEPTOR = 109,       // cronet拦截器
        
        EVENT_LOOKUP = 1003,
        EVENT_SESSION_ACTIVE = 1004,
    }
}

