using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Serilog;

namespace ZB.MOM.WW.GRAccess.Cli.Session
{
    /// <summary>
    ///     Dedicated STA thread with a raw Win32 message pump for COM interop.
    ///     All GRAccess COM objects must be created and called on this thread.
    /// </summary>
    public sealed class StaComThread : IDisposable
    {
        private const uint WM_APP = 0x8000;
        private const uint PM_NOREMOVE = 0x0000;

        private static readonly ILogger Log = Serilog.Log.ForContext<StaComThread>();

        private readonly Thread _thread;
        private readonly TaskCompletionSource<bool> _ready = new TaskCompletionSource<bool>();
        private readonly ConcurrentQueue<Action> _workItems = new ConcurrentQueue<Action>();
        private volatile uint _nativeThreadId;
        private bool _disposed;

        public StaComThread()
        {
            _thread = new Thread(ThreadEntry)
            {
                Name = "GRAccess-STA",
                IsBackground = true
            };
            _thread.SetApartmentState(ApartmentState.STA);
        }

        /// <summary>
        ///     Starts the STA thread and waits until the message pump is running.
        /// </summary>
        public void Start()
        {
            _thread.Start();
            _ready.Task.GetAwaiter().GetResult();
            Log.Information("STA COM thread started (ThreadId={ThreadId})", _thread.ManagedThreadId);
        }

        /// <summary>
        ///     Marshals a synchronous action onto the STA thread and returns a Task
        ///     that completes when the action finishes.
        /// </summary>
        public Task RunAsync(Action action)
        {
            if (_disposed) throw new ObjectDisposedException(nameof(StaComThread));

            var tcs = new TaskCompletionSource<bool>();
            _workItems.Enqueue(() =>
            {
                try
                {
                    action();
                    tcs.TrySetResult(true);
                }
                catch (Exception ex)
                {
                    tcs.TrySetException(ex);
                }
            });
            PostThreadMessage(_nativeThreadId, WM_APP, IntPtr.Zero, IntPtr.Zero);
            return tcs.Task;
        }

        /// <summary>
        ///     Marshals a synchronous function onto the STA thread and returns
        ///     a Task&lt;T&gt; with the result.
        /// </summary>
        public Task<T> RunAsync<T>(Func<T> func)
        {
            if (_disposed) throw new ObjectDisposedException(nameof(StaComThread));

            var tcs = new TaskCompletionSource<T>();
            _workItems.Enqueue(() =>
            {
                try
                {
                    tcs.TrySetResult(func());
                }
                catch (Exception ex)
                {
                    tcs.TrySetException(ex);
                }
            });
            PostThreadMessage(_nativeThreadId, WM_APP, IntPtr.Zero, IntPtr.Zero);
            return tcs.Task;
        }

        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;

            try
            {
                if (_nativeThreadId != 0)
                    PostThreadMessage(_nativeThreadId, WM_APP + 1, IntPtr.Zero, IntPtr.Zero);
                _thread.Join(TimeSpan.FromSeconds(5));
            }
            catch (Exception ex)
            {
                Log.Warning(ex, "Error shutting down STA COM thread");
            }

            Log.Information("STA COM thread stopped");
        }

        private void ThreadEntry()
        {
            try
            {
                _nativeThreadId = GetCurrentThreadId();

                // Force message queue creation by peeking
                MSG msg;
                PeekMessage(out msg, IntPtr.Zero, 0, 0, PM_NOREMOVE);

                _ready.TrySetResult(true);

                // Run the message loop — blocks until WM_QUIT
                while (GetMessage(out msg, IntPtr.Zero, 0, 0) > 0)
                {
                    if (msg.message == WM_APP)
                    {
                        DrainQueue();
                    }
                    else if (msg.message == WM_APP + 1)
                    {
                        // Shutdown signal — drain remaining work then quit
                        DrainQueue();
                        PostQuitMessage(0);
                    }
                    else
                    {
                        TranslateMessage(ref msg);
                        DispatchMessage(ref msg);
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, "STA COM thread crashed");
                _ready.TrySetException(ex);
            }
        }

        private void DrainQueue()
        {
            while (_workItems.TryDequeue(out var workItem))
            {
                try
                {
                    workItem();
                }
                catch (Exception ex)
                {
                    Log.Error(ex, "Unhandled exception in STA work item");
                }
            }
        }

        #region Win32 PInvoke

        [StructLayout(LayoutKind.Sequential)]
        private struct MSG
        {
            public IntPtr hwnd;
            public uint message;
            public IntPtr wParam;
            public IntPtr lParam;
            public uint time;
            public POINT pt;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct POINT
        {
            public int x;
            public int y;
        }

        [DllImport("user32.dll")]
        private static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool TranslateMessage(ref MSG lpMsg);

        [DllImport("user32.dll")]
        private static extern IntPtr DispatchMessage(ref MSG lpMsg);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool PostThreadMessage(uint idThread, uint Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern void PostQuitMessage(int nExitCode);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);

        [DllImport("kernel32.dll")]
        private static extern uint GetCurrentThreadId();

        #endregion
    }
}
