using System;
using System.Runtime.InteropServices;
using UnityEngine;

namespace Alibabacloud.Rum.Unity
{
    /// <summary>
    /// Helper class to extract native IL2CPP stack traces from C# exceptions
    /// </summary>
    public static class Il2CppErrorHelper
    {
        /// <summary>
        /// Convert an exception to a native stack trace frames.
        /// </summary>
        /// <param name="exception">The C# exception.</param>
        /// <param name="frames">Out parameter containing the stack frames, or null if frames failed to fetch.</param>
        /// <param name="imageUuid">Out parameter containing the image UUID, or null if frames failed to fetch.</param>
        /// <param name="imageName">Out parameter containing the image name. May be null or empty.</param>
        /// <returns>True if native stack frames were successfully retrieved.</returns>
        public static bool GetNativeStackFrames(Exception exception, out IntPtr[] frames, out string imageUuid, out string imageName)
        {
            frames = null;
            imageUuid = null;
            imageName = null;

            if (exception == null)
            {
                return false;
            }

            var gchandle = GCHandle.Alloc(exception);
            var addresses = IntPtr.Zero;

            try
            {
                var handlePtr = GCHandle.ToIntPtr(gchandle);
                var targetAddress = GcHandleGetTarget(handlePtr);

                var numFrames = 0;
                GetStackFrames(targetAddress, out addresses, out numFrames, out imageUuid, out imageName);
                
                if (numFrames <= 0 || addresses == IntPtr.Zero)
                {
                    return false;
                }

                frames = new IntPtr[numFrames];
                Marshal.Copy(addresses, frames, 0, numFrames);
                
                // Check if first frame is null (likely a development build)
                if (frames.Length > 0 && frames[0] == IntPtr.Zero)
                {
                    return false;
                }

                return true;
            }
            catch (Exception e)
            {
                Debug.LogWarning($"[ALIBABACLOUD] Failed to get native stack frames: {e.Message}");
                return false;
            }
            finally
            {
                gchandle.Free();
                if (addresses != IntPtr.Zero)
                {
                    il2cpp_free(addresses);
                }
            }
        }

        private static void GetStackFrames(IntPtr exc, out IntPtr addresses, out int numFrames, out string imageUuid, out string imageName)
        {
            addresses = IntPtr.Zero;
            numFrames = 0;
            imageUuid = null;
            imageName = null;

            var imageUuidPtr = IntPtr.Zero;
            var imageNamePtr = IntPtr.Zero;
            
            try
            {
#if UNITY_2021_3_OR_NEWER
                il2cpp_native_stack_trace(exc, out addresses, out numFrames, out imageUuidPtr, out imageNamePtr);
                imageName = (imageNamePtr == IntPtr.Zero) ? null : Marshal.PtrToStringAnsi(imageNamePtr);
#else
                // Method expects a pre-allocated buffer for the UUID. Max length is 40 chars
                imageUuidPtr = il2cpp_alloc(41);
                il2cpp_native_stack_trace(exc, out addresses, out numFrames, imageUuidPtr);
                imageName = null;
#endif
                imageUuid = (imageUuidPtr == IntPtr.Zero) ? null : Marshal.PtrToStringAnsi(imageUuidPtr);
            }
            finally
            {
                if (imageUuidPtr != IntPtr.Zero)
                {
                    il2cpp_free(imageUuidPtr);
                }
                if (imageNamePtr != IntPtr.Zero)
                {
                    il2cpp_free(imageNamePtr);
                }
            }
        }

        private static IntPtr GcHandleGetTarget(IntPtr gchandle)
        {
#if UNITY_2023_OR_NEWER || UNITY_6000_0_OR_NEWER
            return il2cpp_gchandle_get_target(gchandle);
#else
            return il2cpp_gchandle_get_target(gchandle.ToInt32());
#endif
        }

        #region Unity IL2CPP C Interface

#if UNITY_2023_OR_NEWER || UNITY_6000_0_OR_NEWER
        [DllImport("__Internal")]
        private static extern IntPtr il2cpp_gchandle_get_target(IntPtr gchandle);
#else
        [DllImport("__Internal")]
        private static extern IntPtr il2cpp_gchandle_get_target(int gchandle);
#endif

#if UNITY_2021_3_OR_NEWER
        [DllImport("__Internal")]
        private static extern void il2cpp_native_stack_trace(IntPtr exc, out IntPtr addresses, out int numFrames, out IntPtr imageUUID, out IntPtr imageName);
#else
        [DllImport("__Internal")]
        private static extern IntPtr il2cpp_alloc(uint size);

        [DllImport("__Internal")]
        private static extern void il2cpp_native_stack_trace(IntPtr exc, out IntPtr addresses, out int numFrames, IntPtr imageUUID);
#endif

        [DllImport("__Internal")]
        private static extern void il2cpp_free(IntPtr ptr);

        #endregion
    }
}
