using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32;

namespace Decal.DenAgent
{
    /// <summary>
    /// Application context that manages the system tray icon and lobby injection timer.
    /// Replaces the MFC CTrayWnd class.
    /// </summary>
    internal sealed class TrayContext : ApplicationContext
    {
        private readonly NotifyIcon _trayIcon;
        private readonly Timer _lobbyTimer;
        private AgentForm _agentForm;

        // P/Invoke for window enumeration and DLL injection
        [DllImport("user32.dll")]
        private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern int GetClassName(IntPtr hWnd, char[] lpClassName, int nMaxCount);

        [DllImport("user32.dll")]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern IntPtr CreateSemaphore(IntPtr lpAttributes, int lInitialCount,
            int lMaximumCount, string lpName);

        [DllImport("kernel32.dll")]
        private static extern bool CloseHandle(IntPtr hObject);

        // LauncherHook P/Invoke
        [DllImport("LauncherHook.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern void LauncherHookEnable();

        [DllImport("LauncherHook.dll", CallingConvention = CallingConvention.StdCall)]
        private static extern void LauncherHookDisable();

        // Inject.DLL injection via CreateRemoteThread
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
            uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
            byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);

        [DllImport("kernel32.dll")]
        private static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes,
            uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

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

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

        [DllImport("kernel32.dll")]
        private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);

        [DllImport("kernel32.dll")]
        private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress,
            uint dwSize, uint dwFreeType);

        private const uint PROCESS_ALL_ACCESS = 0x001F0FFF;
        private const uint MEM_COMMIT = 0x1000;
        private const uint MEM_RELEASE = 0x8000;
        private const uint PAGE_READWRITE = 0x04;

        private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

        public TrayContext()
        {
            // Store agent path in registry
            StoreAgentPath();

            // Create tray icon
            _trayIcon = new NotifyIcon
            {
                Text = "Decal Agent",
                Icon = LoadTrayIcon(),
                Visible = true,
                ContextMenuStrip = CreateContextMenu()
            };
            _trayIcon.DoubleClick += (s, e) => ShowAgentForm();

            // Start timer to detect lobby windows for injection
            _lobbyTimer = new Timer { Interval = 1000 };
            _lobbyTimer.Tick += OnLobbyTimerTick;
            _lobbyTimer.Start();
        }

        private static System.Drawing.Icon LoadTrayIcon()
        {
            // Try to load custom icon; fall back to default app icon
            string iconPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "decal.ico");
            if (File.Exists(iconPath))
                return new System.Drawing.Icon(iconPath);

            return System.Drawing.SystemIcons.Application;
        }

        private ContextMenuStrip CreateContextMenu()
        {
            var menu = new ContextMenuStrip();
            menu.Items.Add("Configure", null, (s, e) => ShowAgentForm());
            menu.Items.Add(new ToolStripSeparator());
            menu.Items.Add("Exit", null, (s, e) => ExitApplication());
            return menu;
        }

        private void ShowAgentForm()
        {
            if (_agentForm == null || _agentForm.IsDisposed)
            {
                _agentForm = new AgentForm();
            }
            _agentForm.Show();
            _agentForm.BringToFront();
            _agentForm.Activate();
        }

        private void StoreAgentPath()
        {
            try
            {
                string agentPath = AppDomain.CurrentDomain.BaseDirectory;
                if (agentPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
                    agentPath = agentPath.TrimEnd(Path.DirectorySeparatorChar);

                using var key = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Decal\Agent");
                key?.SetValue("AgentPath", agentPath);
            }
            catch
            {
                // May fail without admin rights - that's okay, path may already be set
            }
        }

        private void OnLobbyTimerTick(object sender, EventArgs e)
        {
            // Enumerate windows looking for lobby/launcher windows to inject into
            EnumWindows(OnEnumWindow, IntPtr.Zero);
        }

        private bool OnEnumWindow(IntPtr hWnd, IntPtr lParam)
        {
            var className = new char[256];
            int len = GetClassName(hWnd, className, className.Length);
            string name = new string(className, 0, len);

            // Look for Asheron's Call lobby window class
            if (name != "ZoneLobbyWindow")
                return true;

            GetWindowThreadProcessId(hWnd, out uint processId);
            if (processId == 0)
                return true;

            // Check if already injected via named semaphore
            string semName = $"__LOBBYHOOK_{processId}";
            IntPtr hSem = CreateSemaphore(IntPtr.Zero, 0, 1, semName);
            if (hSem != IntPtr.Zero)
            {
                bool alreadyExists = Marshal.GetLastWin32Error() == 183; // ERROR_ALREADY_EXISTS
                CloseHandle(hSem);
                if (alreadyExists)
                    return true;
            }

            // Inject LauncherHook.DLL into the lobby process
            string agentPath = GetAgentPath();
            if (!string.IsNullOrEmpty(agentPath))
            {
                string hookDll = Path.Combine(agentPath, "LauncherHook.dll");
                if (File.Exists(hookDll))
                    InjectDll(processId, hookDll);
            }

            return true;
        }

        private static string GetAgentPath()
        {
            try
            {
                using var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Decal\Agent");
                return key?.GetValue("AgentPath") as string ?? "";
            }
            catch { return ""; }
        }

        private static bool InjectDll(uint processId, string dllPath)
        {
            IntPtr hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processId);
            if (hProcess == IntPtr.Zero)
                return false;

            try
            {
                byte[] pathBytes = System.Text.Encoding.Unicode.GetBytes(dllPath + '\0');
                uint pathSize = (uint)pathBytes.Length;

                IntPtr remoteMem = VirtualAllocEx(hProcess, IntPtr.Zero, pathSize, MEM_COMMIT, PAGE_READWRITE);
                if (remoteMem == IntPtr.Zero)
                    return false;

                if (!WriteProcessMemory(hProcess, remoteMem, pathBytes, pathSize, out _))
                {
                    VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
                    return false;
                }

                IntPtr kernel32 = GetModuleHandle("kernel32.dll");
                IntPtr loadLibW = GetProcAddress(kernel32, "LoadLibraryW");

                IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, 0,
                    loadLibW, remoteMem, 0, out _);

                if (hThread == IntPtr.Zero)
                {
                    VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
                    return false;
                }

                WaitForSingleObject(hThread, 10000);
                CloseHandle(hThread);
                VirtualFreeEx(hProcess, remoteMem, 0, MEM_RELEASE);
                return true;
            }
            finally
            {
                CloseHandle(hProcess);
            }
        }

        private void ExitApplication()
        {
            _lobbyTimer.Stop();
            _lobbyTimer.Dispose();
            _trayIcon.Visible = false;
            _trayIcon.Dispose();
            Application.Exit();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _lobbyTimer?.Dispose();
                _trayIcon?.Dispose();
                _agentForm?.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}
