using System;
using System.Collections.Generic;
using Alibabacloud.Rum.Unity.Logs;
using Alibabacloud.Rum.Unity.Network;
using Alibabacloud.Rum.Unity.Rum;
using UnityEngine;

#if UNITY_ANDROID && !UNITY_EDITOR
using UnityEngine.Android;
#endif

namespace Alibabacloud.Rum.Unity.Android
{
    public class AndroidRum : IAlibabacloudRum
    {
        private readonly AndroidJavaObject _rum;
        private AlibabacloudOptions _options;

        public AndroidRum(AndroidJavaObject rum, AlibabacloudOptions options)
        {
            _rum = rum;
            _options = options;
        }
        
        /// <summary>
        /// Report exception (from Exception object) to Android RUM SDK
        /// </summary>
        public void ReportException(Exception exception)
        {
            if (exception == null)
            {
                _options.LogWarning("[ALIBABACLOUD] ReportException called with null exception");
                return;
            }

            if (_rum == null)
            {
                _options.LogError("ReportException aborted: RUM class not initialized");
                return;
            }

            try
            {
                // Extract exception information
                string exceptionType = exception.GetType().Name;
                string crashName = exception.Message ?? "No message";
                string crashMessage = exception.StackTrace ?? "No stack trace";
                
                // Call Android SDK method: reportException(String type, String screenType, String crashName, String crashMessage)
                _rum.CallStatic("reportException", exceptionType, AlibabacloudCommonParam.ScreenType, crashName, crashMessage);
            }
            catch (Exception e)
            {
                _options.LogError("ReportException failed: {0}\n{1}", e.Message, e.StackTrace);
            }
        }

        /// <summary>
        /// Report resource to Android RUM SDK
        /// </summary>
        public void ReportResource(string type,
            string url,
            string method,
            int statusCode,
            string errorMessage,
            bool success,
            TraceContext traceContext,
            Measuring measuring)
        {

            if (_rum == null)
            {
                return;
            }

            AndroidJavaObject traceContextObj = null;
            AndroidJavaObject measuringObj = null;
            try
            {
                // Convert TraceContext to Java object
                if (traceContext != null)
                { 
                    traceContextObj = traceContext.ToJavaObject();
                    
                    if (traceContextObj == null)
                    {
                        return;
                    }
                }
                else
                {
                    // Create a default TraceContext if none provided
                    var defaultTraceContext = new TraceContext(
                        System.Guid.NewGuid().ToString("N"),
                        System.Guid.NewGuid().ToString("N").Substring(0, 16),
                        Rum.TraceContextStandard.W3C);
                    traceContextObj = defaultTraceContext.ToJavaObject();
                    
                    if (traceContextObj == null)
                    {
                        return;
                    }
                }

                // Convert Measuring to Java object
                if (measuring != null)
                {
                    measuringObj = measuring.ToJavaObject();
                    
                    if (measuringObj == null)
                    {
                        return;
                    }
                }
                else
                {
                    // Create a default Measuring if none provided
                    var defaultMeasuring = new Measuring(0, 0);
                    measuringObj = defaultMeasuring.ToJavaObject();
                    
                    if (measuringObj == null)
                    {
                        return;
                    }
                }

                // Both objects must be non-null before calling JNI
                if (traceContextObj == null || measuringObj == null)
                {
                    return;
                }

                _rum.CallStatic(
                    "reportCustomResource",
                    type ?? "xhr",
                    url ?? string.Empty,
                    method ?? "GET",
                    statusCode,
                    errorMessage ?? string.Empty,
                    success,
                    "Unity",
                    traceContextObj,
                    measuringObj);
            }
            catch (Exception e)
            {
                _options.LogWarning($"ReportResource failed: {e.Message}\n{e.StackTrace}");
            }
            finally
            {
                traceContextObj?.Dispose();
                measuringObj?.Dispose();
            }
        }

        /// <summary>
        /// Report custom event to Android RUM SDK
        /// Maps to native Android SDK: setCustomEvent(String customName, String customValue, String customGroup, String customSnapshots, Map<String, Object> customExtraInfo)
        /// </summary>
        public void ReportEvent(string eventName, string group, double value,
            Dictionary<string, object> info)
        {
            if (_rum == null)
            {
                return;
            }

            AndroidJavaObject javaMap = null;
            try
            {
                if (info != null && info.Count > 0)
                {
                    javaMap = CreateHashMap(info);
                }

                // Map Unity SDK parameters to Android SDK:
                // eventName -> customName, value (as string) -> customValue, group -> customGroup, null -> customSnapshots, info -> customExtraInfo
                _rum.CallStatic("setCustomEvent", eventName, value.ToString(), group, null, javaMap);
            }
            catch (Exception e)
            {
                _options.LogError("SetCustomEvent failed: {0}\n{1}", e.Message, e.StackTrace);
            }
            finally
            {
                javaMap?.Dispose();
            }
        }

        /// <summary>
        /// Creates a Java HashMap from Dictionary using AndroidJNI directly to avoid IL2CPP reflection issues.
        /// </summary>
        private AndroidJavaObject CreateHashMap(Dictionary<string, object> dictionary)
        {
            if (dictionary == null || dictionary.Count == 0)
            {
                return null;
            }

            AndroidJavaObject hashMap = null;
            IntPtr hashMapClass = IntPtr.Zero;
            IntPtr putMethod = IntPtr.Zero;
            IntPtr hashMapInstance = IntPtr.Zero;

            try
            {
                // Find HashMap class
                hashMapClass = AndroidJNI.FindClass("java/util/HashMap");
                if (hashMapClass == IntPtr.Zero)
                {
                    return null;
                }

                // Get constructor
                IntPtr constructor = AndroidJNI.GetMethodID(hashMapClass, "<init>", "()V");
                if (constructor == IntPtr.Zero)
                {
                    return null;
                }

                // Create instance
                hashMapInstance = AndroidJNI.NewObject(hashMapClass, constructor, new jvalue[0]);
                if (hashMapInstance == IntPtr.Zero)
                {
                    return null;
                }

                // Get put method: put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
                putMethod = AndroidJNI.GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
                if (putMethod == IntPtr.Zero)
                {
                    return null;
                }

                // Add entries
                foreach (var kv in dictionary)
                {
                    IntPtr keyPtr = IntPtr.Zero;
                    IntPtr valuePtr = IntPtr.Zero;

                    try
                    {
                        keyPtr = AndroidJNI.NewStringUTF(kv.Key);
                        string valueStr = kv.Value?.ToString() ?? string.Empty;
                        valuePtr = AndroidJNI.NewStringUTF(valueStr);

                        jvalue[] args = new jvalue[2];
                        args[0].l = keyPtr;
                        args[1].l = valuePtr;

                        AndroidJNI.CallObjectMethod(hashMapInstance, putMethod, args);
                    }
                    finally
                    {
                        if (keyPtr != IntPtr.Zero)
                        {
                            AndroidJNI.DeleteLocalRef(keyPtr);
                        }
                        if (valuePtr != IntPtr.Zero)
                        {
                            AndroidJNI.DeleteLocalRef(valuePtr);
                        }
                    }
                }

                // Create AndroidJavaObject wrapper
                hashMap = new AndroidJavaObject(hashMapInstance);
                return hashMap;
            }
            catch (Exception)
            {
                if (hashMapInstance != IntPtr.Zero)
                {
                    AndroidJNI.DeleteLocalRef(hashMapInstance);
                }
                return null;
            }
            finally
            {
                if (hashMapClass != IntPtr.Zero)
                {
                    AndroidJNI.DeleteLocalRef(hashMapClass);
                }
            }
        }

        /// <summary>
        /// Report Log to Android RUM SDK
        /// </summary>
        public void ReportLog(string logContent, string logName, string logLevel, string stackTrace, Dictionary<string, object> extraInfo)
        {
            if (_rum == null)
            {
                return;
            }

            AndroidJavaObject javaMap = null;
            try
            {
                if (extraInfo != null && extraInfo.Count > 0)
                {
                    javaMap = CreateHashMap(extraInfo);
                }

                // Call Android SDK method: setCustomLog(String customLogContent, String customName, String customLogLevel, String customSnapshots, Map<String, Object> customExtraInfo)
                _rum.CallStatic("setCustomLog", 
                    logContent ?? string.Empty,
                    logName ?? string.Empty,
                    logLevel ?? "Info",
                    stackTrace ?? string.Empty,
                    javaMap);
            }
            catch (Exception)
            {
                // Silently ignore JNI errors
            }
            finally
            {
                javaMap?.Dispose();
            }
        }

        /// <summary>
        /// Start view tracking
        /// Maps to native Android SDK: startView(String viewId, int loadTime, String viewName, String screenType)
        /// </summary>
        public void StartView(string key, string name, Dictionary<string, object> attributes)
        {
            if (_rum == null)
            {
                return;
            }

            try
            {
                // Extract loadTime from attributes if present (timestamp injected by processor)
                int loadTime = 0;
                if (attributes != null && attributes.ContainsKey("_alibabacloud.load_time"))
                {
                    if (attributes["_alibabacloud.load_time"] is long timestamp)
                    {
                        loadTime = (int)timestamp;
                    }
                }

                // Call Android SDK method: startView(String viewId, int loadTime, String viewName, String screenType)
                _rum.CallStatic("startView", 
                    key ?? string.Empty,
                    loadTime,
                    name ?? string.Empty,
                    AlibabacloudCommonParam.ScreenType);

            }
            catch (Exception e)
            {
                _options.LogError("StartView failed: {0}\n{1}", e.Message, e.StackTrace);
            }
        }

        /// <summary>
        /// Stop view tracking
        /// Maps to native Android SDK: stopView(String viewId, int loadTime, String screenType, Map<String, Object> map)
        /// </summary>
        public void StopView(string key, Dictionary<string, object> attributes)
        {
            if (_rum == null)
            {
                return;
            }

            AndroidJavaObject javaMap = null;
            try
            {
                // Convert attributes to Java HashMap (filtering out internal attributes)
                if (attributes != null && attributes.Count > 0)
                {
                    var filteredAttrs = new Dictionary<string, object>();
                    foreach (var kv in attributes)
                    {
                        // Skip internal attributes
                        if (kv.Key.StartsWith("_alibabacloud."))
                        {
                            continue;
                        }
                        filteredAttrs[kv.Key] = kv.Value;
                    }
                    javaMap = CreateHashMap(filteredAttrs);
                }
                
                // Call Android SDK method: stopView(String viewId, int loadTime, String screenType, Map<String, Object> map)
                _rum.CallStatic("stopView", 
                    key ?? string.Empty,
                    0,
                    AlibabacloudCommonParam.ScreenType,
                    javaMap);
            }
            catch (Exception)
            {
                // Silently ignore JNI errors
            }
            finally
            {
                javaMap?.Dispose();
            }
        }
    }
}