using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppNotifications;
using Microsoft.Windows.AppLifecycle;
using Wino.Mail.WinUI.Activation;

namespace Wino.Mail.WinUI;

public class Program
{
    private const string SingleInstanceKey = "WinoMailSingleInstance";
    private const string ForceAlternateModeSignalEventName = "Local\\WinoMailForceAlternateMode";
    private const int VkControl = 0x11;

    private static bool _forceAlternateModeOnLaunch;
    private static EventWaitHandle? _forceAlternateModeSignalHandle;

    [STAThread]
    static int Main(string[] args)
    {
        WinRT.ComWrappersSupport.InitializeComWrappers();
        bool isRedirect = DecideRedirection();

        if (!isRedirect)
        {
            Application.Start((p) =>
            {
                var context = new DispatcherQueueSynchronizationContext(
                    DispatcherQueue.GetForCurrentThread());
                SynchronizationContext.SetSynchronizationContext(context);
                var app = new App();
                _ = app.HandleInitialActivationAsync();
            });
        }

        return 0;
    }

    private static bool DecideRedirection()
    {
        bool isRedirect = false;
        AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
        _forceAlternateModeOnLaunch = args.Kind == ExtendedActivationKind.Launch && IsCtrlKeyDown();

        AppInstance keyInstance = AppInstance.FindOrRegisterForKey(SingleInstanceKey);

        if (keyInstance.IsCurrent)
        {
            EnsureAlternateModeOverrideSignalHandle();
            keyInstance.Activated += OnActivated;
        }
        else
        {
            isRedirect = true;

            if (_forceAlternateModeOnLaunch)
            {
                SignalForceAlternateMode();
            }

            RedirectActivationTo(args, keyInstance);
        }

        return isRedirect;
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern IntPtr CreateEvent(
        IntPtr lpEventAttributes, bool bManualReset,
        bool bInitialState, string lpName);

    [DllImport("kernel32.dll")]
    private static extern bool SetEvent(IntPtr hEvent);

    [DllImport("ole32.dll")]
    private static extern uint CoWaitForMultipleObjects(
        uint dwFlags, uint dwMilliseconds, ulong nHandles,
        IntPtr[] pHandles, out uint dwIndex);

    [DllImport("user32.dll")]
    static extern bool SetForegroundWindow(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern short GetAsyncKeyState(int vKey);

    private static IntPtr redirectEventHandle = IntPtr.Zero;

    public static bool TryConsumeCurrentProcessAlternateModeOverride()
    {
        if (!_forceAlternateModeOnLaunch)
            return false;

        _forceAlternateModeOnLaunch = false;
        return true;
    }

    public static bool TryConsumeRedirectedAlternateModeOverride()
    {
        try
        {
            if (_forceAlternateModeSignalHandle != null)
            {
                return _forceAlternateModeSignalHandle.WaitOne(0);
            }

            if (!EventWaitHandle.TryOpenExisting(ForceAlternateModeSignalEventName, out EventWaitHandle? signal))
                return false;

            using (signal)
            {
                return signal.WaitOne(0);
            }
        }
        catch
        {
            return false;
        }
    }

    private static bool IsCtrlKeyDown() => (GetAsyncKeyState(VkControl) & 0x8000) != 0;

    private static void EnsureAlternateModeOverrideSignalHandle()
    {
        if (_forceAlternateModeSignalHandle != null)
            return;

        try
        {
            _forceAlternateModeSignalHandle = new EventWaitHandle(false, EventResetMode.AutoReset, ForceAlternateModeSignalEventName);
        }
        catch
        {
            _forceAlternateModeSignalHandle = null;
        }
    }

    private static void SignalForceAlternateMode()
    {
        try
        {
            if (EventWaitHandle.TryOpenExisting(ForceAlternateModeSignalEventName, out EventWaitHandle? signal))
            {
                using (signal)
                {
                    signal.Set();
                }

                return;
            }

            using EventWaitHandle fallbackSignal = new(false, EventResetMode.AutoReset, ForceAlternateModeSignalEventName);
            fallbackSignal.Set();
        }
        catch
        {
            // Ignore signaling failures and continue with normal activation redirection.
        }
    }

    // Do the redirection on another thread, and use a non-blocking
    // wait method to wait for the redirection to complete.
    public static void RedirectActivationTo(AppActivationArguments args,
                                            AppInstance keyInstance)
    {
        redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null!);
        Task.Run(() =>
        {
            keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
            SetEvent(redirectEventHandle);
        });

        uint CWMO_DEFAULT = 0;
        uint INFINITE = 0xFFFFFFFF;
        _ = CoWaitForMultipleObjects(
           CWMO_DEFAULT, INFINITE, 1,
           [redirectEventHandle], out uint handleIndex);

        if (ShouldBringWindowToForegroundAfterRedirection(args))
        {
            Process process = Process.GetProcessById((int)keyInstance.ProcessId);
            SetForegroundWindow(process.MainWindowHandle);
        }
    }

    private static void OnActivated(object? sender, AppActivationArguments args)
    {
        // When a new instance tries to launch, this event fires in the existing instance.
        // We need to notify the App to handle the activation (e.g., bring window to front, handle protocol).
        if (Application.Current is App app)
        {
            app.HandleRedirectedActivation(args);
        }
    }

    private static bool ShouldBringWindowToForegroundAfterRedirection(AppActivationArguments args)
    {
        if (args.Kind == ExtendedActivationKind.AppNotification &&
            args.Data is AppNotificationActivatedEventArgs toastArgs)
        {
            return ToastActivationResolver.TryParse(toastArgs.Argument, out var toastArguments)
                ? ToastActivationResolver.ShouldBringToForeground(toastArguments)
                : true;
        }

        if (args.Kind == ExtendedActivationKind.Launch &&
            args.Data is Windows.ApplicationModel.Activation.ILaunchActivatedEventArgs launchArgs &&
            ToastActivationResolver.TryParse(launchArgs.Arguments, out var launchToastArguments))
        {
            return ToastActivationResolver.ShouldBringToForeground(launchToastArguments);
        }

        return true;
    }
}
