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

namespace Alibabacloud.Rum.Unity.Windows
{
    /// <summary>
    /// Helper to ensure alibabacloud_rum.dll can be found at runtime.
    /// Unity standalone builds place plugins in GameName_Data/Plugins/ but DllImport
    /// may not search there automatically on some Unity versions.
    /// </summary>
    internal static class WindowsDllLoader
    {
        private const string DllName = "alibabacloud_rum";
        private static bool _loadAttempted;
        private static bool _loadSucceeded;

        /// <summary>
        /// Whether the native DLL was successfully loaded into the process.
        /// Use this to short-circuit P/Invoke calls when the DLL is missing,
        /// avoiding noisy DllNotFoundException spam at runtime.
        /// </summary>
        internal static bool IsAvailable => _loadSucceeded;

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr LoadLibrary(string lpFileName);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool SetDllDirectory(string lpPathName);

        /// <summary>
        /// Attempts to preload the native DLL from known locations.
        /// Call this before any P/Invoke to alibabacloud_rum.dll.
        /// </summary>
        internal static void EnsureDllLoaded()
        {
            if (_loadAttempted) return;
            _loadAttempted = true;

            // Check if already loaded
            if (GetModuleHandle(DllName) != IntPtr.Zero ||
                GetModuleHandle(DllName + ".dll") != IntPtr.Zero)
            {
                _loadSucceeded = true;
                return;
            }

            var tried = new System.Collections.Generic.List<string>();
            string dllPath = FindDllPath(tried);
            if (!string.IsNullOrEmpty(dllPath))
            {
                // Set DLL search directory so that dependency DLLs sitting next to
                // alibabacloud_rum.dll can also be resolved automatically.
                string dllDir = Path.GetDirectoryName(dllPath);
                if (!string.IsNullOrEmpty(dllDir))
                {
                    SetDllDirectory(dllDir);
                }

                IntPtr handle = LoadLibrary(dllPath);
                if (handle != IntPtr.Zero)
                {
                    _loadSucceeded = true;
                    Debug.Log($"[AlibabacloudRUM] Preloaded native DLL from: {dllPath}");
                }
                else
                {
                    int error = Marshal.GetLastWin32Error();
                    Debug.LogWarning(
                        $"[AlibabacloudRUM] Failed to preload DLL from {dllPath}. " +
                        $"Win32 Error: {error} " +
                        $"({(error == 126 ? "ERROR_MOD_NOT_FOUND - check VC++ Redistributable / dependent DLLs" : "see https://learn.microsoft.com/windows/win32/debug/system-error-codes")})");
                }
            }
            else
            {
                Debug.LogWarning(
                    $"[AlibabacloudRUM] Could not find {DllName}.dll. Searched paths:\n" +
                    string.Join("\n", tried.ConvertAll(p => $"  - {p}")));
            }
        }

        private static string FindDllPath(System.Collections.Generic.List<string> tried)
        {
            // Determine expected architecture subdirectory
            string archSubdir = IntPtr.Size == 8 ? "x86_64" : "x86";

            // Get the directory where the game executable resides
            string exeDir = Path.GetDirectoryName(Application.dataPath);
            if (string.IsNullOrEmpty(exeDir))
            {
                exeDir = Directory.GetCurrentDirectory();
            }

            string dataPath = Application.dataPath;
            string fileName = $"{DllName}.dll";

            // Build a list of candidate paths.
            //
            // IMPORTANT: order matters. Put the Unity-standard PluginImporter
            // deployment location FIRST. Unity's own P/Invoke resolver loads
            // the DLL from `<GameName>_Data/Plugins/<arch>/`; if we preload a
            // different copy (e.g. from build root), Windows ends up with two
            // separate DLL instances at different base addresses, which causes
            // hard crashes when pointers cross instances.
            string[] candidates = new[]
            {
                // Unity standard location (must match P/Invoke resolution target)
                Path.Combine(dataPath, "Plugins", archSubdir, fileName),
                Path.Combine(dataPath, "Plugins", fileName),
                Path.Combine(dataPath, fileName),
                // Same directory as the executable (legacy / manual deploy)
                Path.Combine(exeDir, fileName),
                // bin/ subdirectory (legacy layout)
                Path.Combine(dataPath, "Plugins", archSubdir, "bin", fileName),
                // MonoBleedingEdge (some Unity versions place natives here)
                Path.Combine(exeDir, "MonoBleedingEdge", "EmbedRuntime", fileName),
            };

            foreach (var path in candidates)
            {
                tried.Add(path);
                if (File.Exists(path))
                {
                    return path;
                }
            }

            // Fallback: recursively search Plugins folder
            string pluginsRoot = Path.Combine(dataPath, "Plugins");
            if (Directory.Exists(pluginsRoot))
            {
                try
                {
                    var found = Directory.GetFiles(pluginsRoot, fileName, SearchOption.AllDirectories);
                    if (found.Length > 0)
                    {
                        tried.Add($"(recursive scan of {pluginsRoot} found {found.Length} match)");
                        return found[0];
                    }
                    tried.Add($"(recursive scan of {pluginsRoot}: no match)");
                }
                catch (Exception e)
                {
                    tried.Add($"(recursive scan failed: {e.Message})");
                }
            }

            return null;
        }
    }
}
