using System;
using System.IO;
using System.Threading;
using System.Collections.Concurrent;
using System.Collections.Generic;
using GxMcp.Worker.Services;
using GxMcp.Worker.Helpers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Reflection;
using System.Windows.Forms;

namespace GxMcp.Worker
{
    class Program
    {
        public static readonly BlockingCollection<string> CommandQueue = new BlockingCollection<string>();
        public static readonly BlockingCollection<string> SdkCommandQueue = new BlockingCollection<string>();
        public static readonly ConcurrentQueue<Action> BackgroundQueue = new ConcurrentQueue<Action>();
        // PERFORMANCE (W-B3): signal that wakes the background worker immediately on Enqueue,
        // instead of busy-polling with Thread.Sleep(100). WaitOne(100) keeps a safety timeout
        // so the loop still re-checks shutdown state if a signal is missed.
        public static readonly AutoResetEvent BackgroundSignal = new AutoResetEvent(false);

        public static void EnqueueBackground(Action work)
        {
            if (work == null) return;
            BackgroundQueue.Enqueue(work);
            try { BackgroundSignal.Set(); } catch { }
        }
        private static readonly BlockingCollection<string> _outputQueue = new BlockingCollection<string>();
        private static readonly BlockingCollection<string> _errorQueue = new BlockingCollection<string>();
        private static CommandDispatcher _dispatcher;
        private static TextWriter _originalOut;
        private static TextWriter _originalError;
        private static StreamWriter _pipeWriter;
        private static Form _bridgeForm;
        private static readonly ManualResetEvent _uiReady = new ManualResetEvent(false);

        [STAThread]
        static void Main(string[] args)
        {
            try {
                // Force UTF-8 on worker stdio before capturing the original writers.
                Console.InputEncoding = System.Text.Encoding.UTF8;
                Console.OutputEncoding = new System.Text.UTF8Encoding(false);

                // ELITE: Start output threads first to capture all logs immediately
                StartOutputThreads();
                Console.SetOut(new QueueWriter(_outputQueue));
                Console.SetError(new QueueWriter(_errorQueue));

                // Ensure culture is Portuguese-Brazil for SDK character mapping
                try {
                    System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("pt-BR");
                    System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("pt-BR");
                } catch { }

                AppDomain.CurrentDomain.UnhandledException += (s, e) => {
                    try
                    {
                        var ex = e.ExceptionObject as Exception;
                        var proc = System.Diagnostics.Process.GetCurrentProcess();
                        long memMb = proc.WorkingSet64 / (1024 * 1024);
                        long privMb = proc.PrivateMemorySize64 / (1024 * 1024);
                        var uptime = DateTime.UtcNow - proc.StartTime.ToUniversalTime();
                        Logger.Error(
                            "[WORKER-CRASH] terminating=" + e.IsTerminating
                            + " memMB=" + memMb + " privMB=" + privMb
                            + " uptimeSec=" + (int)uptime.TotalSeconds
                            + " gcMemMB=" + (GC.GetTotalMemory(false) / (1024 * 1024))
                            + " threadCount=" + proc.Threads.Count
                            + " exType=" + (ex?.GetType().FullName ?? "<null>")
                            + " exMsg=" + (ex?.Message ?? "<null>")
                            + Environment.NewLine + "Stack: " + (ex?.ToString() ?? "<null>"));
                    }
                    catch { Logger.Error("FATAL UNHANDLED EXCEPTION: " + (e.ExceptionObject as Exception)?.ToString()); }
                };

                System.Threading.Tasks.TaskScheduler.UnobservedTaskException += (s, e) => {
                    Logger.Error("[WORKER-CRASH] UNOBSERVED TASK EXCEPTION: " + e.Exception?.ToString());
                    e.SetObserved();
                };

                WriteLine("WORKER_HANDSHAKE_START");
                Logger.Info("Worker process started (STA Mode).");

                // ELITE: Configuration Resolve Logic (Env > Local Config > Error)
                string gxPath = Environment.GetEnvironmentVariable("GX_PROGRAM_DIR");
                string kbPath = Environment.GetEnvironmentVariable("GX_KB_PATH");

                if (string.IsNullOrEmpty(gxPath) || string.IsNullOrEmpty(kbPath))
                {
                    var config = LoadLocalConfig();
                    if (config != null)
                    {
                        if (string.IsNullOrEmpty(gxPath)) gxPath = config["InstallationPath"]?.ToString();
                        if (string.IsNullOrEmpty(kbPath)) kbPath = config["KBPath"]?.ToString();
                    }
                }

                if (string.IsNullOrEmpty(gxPath)) 
                    throw new Exception("GX_PROGRAM_DIR not specified in environment or local config.json.");

                AppDomain.CurrentDomain.AssemblyResolve += (sender, resolveArgs) => {
                    try {
                        string assemblyName = new AssemblyName(resolveArgs.Name).Name + ".dll";
                        string assemblyPath = Path.Combine(gxPath, assemblyName);
                        if (File.Exists(assemblyPath)) return Assembly.LoadFrom(assemblyPath);
                    } catch { }
                    return null;
                };

                string pipeName = Environment.GetEnvironmentVariable("GX_MCP_PIPE");
                if (!string.IsNullOrEmpty(pipeName))
                {
                    try {
                        var pipeClient = new System.IO.Pipes.NamedPipeClientStream(".", pipeName, System.IO.Pipes.PipeDirection.InOut, System.IO.Pipes.PipeOptions.Asynchronous);
                        pipeClient.Connect(30000);
                        var writer = new StreamWriter(pipeClient, new System.Text.UTF8Encoding(false)) { AutoFlush = true };
                        var reader = new StreamReader(pipeClient, new System.Text.UTF8Encoding(false));
                        
                        _pipeWriter = writer;
                        Console.SetIn(reader);
                        Logger.Info($"[Worker] Connected to IPC Pipe {pipeName} successfully.");
                    } catch (Exception ex) {
                        Logger.Error($"[Worker] IPC Pipe Connection Error: {ex.Message}. Falling back to STDIO.");
                    }
                }

                InitializeSdk(gxPath);
                _dispatcher = CommandDispatcher.Instance;
                
                // Check command line arguments for --kb
                for (int i = 0; i < args.Length; i++)
                {
                    if (args[i] == "--kb" && i + 1 < args.Length)
                    {
                        kbPath = args[i + 1];
                        break;
                    }
                }

                if (!string.IsNullOrEmpty(kbPath))
                {
                    try {
                        Logger.Info($"Worker auto-opening KB: {kbPath}");
                        _dispatcher.GetKbService().OpenKB(kbPath);
                    } catch (Exception ex) {
                        Logger.Error($"Worker failed to auto-open KB: {ex.Message}");
                    }
                }

                Logger.Info("Worker SDK ready.");

                // Start External KB Watcher
                var watcher = new KbWatcherService(_dispatcher.GetKbService(), (name, type, time) => {
                    SendNotification("notifications/resources/updated", new {
                        name = name,
                        type = type,
                        updatedAt = time,
                        external = true
                    });
                });
                watcher.Start();

                var readerThread = new Thread(() => {
                    while (true) {
                        string line = Console.ReadLine();
                        if (line == null) break;
                        if (line.Trim().Equals("ping", StringComparison.OrdinalIgnoreCase) || line.Contains("\"method\":\"ping\"") || line.Contains("\"action\":\"Ping\""))
                        {
                            WriteLine("{\"jsonrpc\":\"2.0\",\"result\":{\"status\":\"Ready\"},\"id\":\"heartbeat\"}");
                            if (!line.Contains("\"method\"")) continue;
                        }
                        if (!string.IsNullOrWhiteSpace(line)) CommandQueue.Add(line);
                    }
                    CommandQueue.CompleteAdding();
                }) { IsBackground = true, Name = "HeartbeatReader" };
                readerThread.Start();

                                // DEDICATED SDK WORKER THREAD (STA with WinForms Bridge)
                var sdkWorker = new Thread(() => {
                    Logger.Info("SDK Worker Thread started (WinForms Bridge enabled).");
                    _bridgeForm = new Form { 
                        ShowInTaskbar = false, 
                        WindowState = FormWindowState.Minimized,
                        Visible = false 
                    };
                    
                    // The 'Quiet Mode' loop: process commands on the UI thread
                    var pollTimer = new System.Windows.Forms.Timer { Interval = 10 };
                    pollTimer.Tick += (s, e) => {
                        if (SdkCommandQueue.TryTake(out string line))
                        {
                            try { ProcessCommand(line); }
                            catch (Exception ex) { Logger.Error("SDK Command Error: " + ex.Message); }
                        }
                    };
                    
                    _bridgeForm.Load += (s, e) => {
                        pollTimer.Start();
                        _uiReady.Set();
                        Logger.Debug("WinForms Bridge Ready.");
                    };
                    
                    Application.Run(_bridgeForm);
                }) { IsBackground = true, Name = "SdkWorker", Priority = ThreadPriority.AboveNormal };
                sdkWorker.SetApartmentState(ApartmentState.STA);
                sdkWorker.Start();
                
                _uiReady.WaitOne(10000); // Wait for the bridge to come online

                // DEDICATED STA BACKGROUND TASK THREAD
                var backgroundWorker = new Thread(() => {
                    Logger.Info("Background STA Worker Thread started.");
                    while (!CommandQueue.IsCompleted) {
                        if (BackgroundQueue.TryDequeue(out var action)) {
                            try { action(); }
                            catch (Exception ex) { Logger.Error("Background Task Error: " + ex.Message); }
                        } else {
                            // PERFORMANCE (W-B3): wait for signal from EnqueueBackground; 100ms
                            // fallback re-checks CommandQueue.IsCompleted on shutdown.
                            BackgroundSignal.WaitOne(100);
                        }
                    }
                }) { IsBackground = true, Name = "BackgroundWorker" };
                backgroundWorker.SetApartmentState(ApartmentState.STA);
                backgroundWorker.Start();

                // MAIN DISPATCHER LOOP
                while (!CommandQueue.IsCompleted || CommandQueue.Count > 0)
                {
                    if (CommandQueue.TryTake(out string line, 100))
                    {
                        if (_dispatcher.IsThreadSafe(line))
                            System.Threading.Tasks.Task.Run(() => ProcessCommand(line));
                        else
                            SdkCommandQueue.Add(line);
                    }
                }

                Logger.Info("Input EOF reached. Shutting down...");
                SdkCommandQueue.CompleteAdding();
                while (!SdkCommandQueue.IsCompleted || SdkCommandQueue.Count > 0)
                {
                    Thread.Sleep(50);
                }
                Logger.Info("Worker shutting down safely.");
            } catch (Exception ex) {
                Logger.Error($"Main FATAL: {ex.Message}");
            }
        }

        private static void InitializeSdk(string gxPath)
        {
            try {
                Logger.Debug($"Setting current directory to {gxPath}");
                Directory.SetCurrentDirectory(gxPath);
                
                var archAsm = Assembly.LoadFrom(Path.Combine(gxPath, "Artech.Architecture.Common.dll"));
                var contextServiceType = archAsm.GetType("Artech.Architecture.Common.Services.ContextService");
                contextServiceType?.GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);

                var blAsm = Assembly.LoadFrom(Path.Combine(gxPath, "Artech.Architecture.BL.Framework.dll"));
                var blCommonType = blAsm.GetType("Artech.Architecture.BL.Framework.Services.CommonServices");
                blCommonType?.GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);

                var uiAsm = Assembly.LoadFrom(Path.Combine(gxPath, "Artech.Architecture.UI.Framework.dll"));
                var uiType = uiAsm.GetType("Artech.Architecture.UI.Framework.Services.UIServices");
                uiType?.GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);

                var commonAsm = Assembly.LoadFrom(Path.Combine(gxPath, "Artech.Genexus.Common.dll"));
                var initType = commonAsm.GetType("Artech.Genexus.Common.KBModelObjectsInitializer");
                initType?.GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);

                var connAsm = Assembly.LoadFrom(Path.Combine(gxPath, "Connector.dll"));
                var connType = connAsm.GetType("Artech.Core.Connector");
                connType?.GetMethod("Initialize", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);
                connType?.GetMethod("Start", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);
                
                var kbBaseType = archAsm.GetType("Artech.Architecture.Common.Objects.KnowledgeBase");
                var factoryProp = kbBaseType?.GetProperty("KBFactory", BindingFlags.Public | BindingFlags.Static);
                if (factoryProp != null) {
                    var factoryType = connAsm.GetType("Connector.KBFactory");
                    if (factoryType != null) {
                        factoryProp.SetValue(null, Activator.CreateInstance(factoryType));
                        Logger.Info("KBFactory Linked successfully.");
                    }
                }
                
                Logger.Info("Full SDK Initialization SUCCESS.");
            } catch (Exception ex) { 
                Logger.Error("CRITICAL Init Error: " + ex.Message); 
            }
        }

        private static void ProcessCommand(string line)
        {
            try {
                var obj = JObject.Parse(line);
                string idJson = obj["id"]?.ToString() ?? "null";
                string method = obj["method"]?.ToString();
                string correlationId = obj["params"]?["correlationId"]?.ToString() ?? "n/a";
                Logger.Info($"[WORKER] Command: {method} ({idJson}) [cid:{correlationId}]");
                string result = _dispatcher.Dispatch(line);
                SendResponse(result, idJson);
            } catch (Exception ex) { Logger.Error("ProcessCommand Error: " + ex.Message); }
        }

        private static void SendResponse(string result, string id)
        {
            try {
                object resultObj;
                try { resultObj = JToken.Parse(result); } catch { resultObj = result; }
                var response = new { jsonrpc = "2.0", result = resultObj, id = id };
                WriteLine(JsonConvert.SerializeObject(response, Formatting.None));
            } catch (Exception ex) { Logger.Error("SendResponse Error: " + ex.Message); }
        }

        public static void SendNotification(string method, object @params)
        {
            try {
                var notification = new { jsonrpc = "2.0", method = method, @params = @params };
                WriteLine(JsonConvert.SerializeObject(notification, Formatting.None));
            } catch (Exception ex) { Logger.Error("SendNotification Error: " + ex.Message); }
        }

        public static void WriteLine(string line) => _outputQueue.Add(line);
        public static void WriteError(string line) => _errorQueue.Add(line);

        private static void StartOutputThreads()
        {
            _originalOut = Console.Out;
            _originalError = Console.Error;

            var outThread = new Thread(() => {
                foreach (var line in _outputQueue.GetConsumingEnumerable()) {
                    try { 
                        if (_pipeWriter != null) {
                            _pipeWriter.WriteLine(line);
                            _pipeWriter.Flush();
                        } else {
                            _originalOut.WriteLine(line); 
                            _originalOut.Flush(); 
                        }
                    } catch { }
                }
            }) { IsBackground = true, Name = "OutputWriter" };
            outThread.Start();

            var errThread = new Thread(() => {
                foreach (var line in _errorQueue.GetConsumingEnumerable()) {
                    try { _originalError.WriteLine(line); _originalError.Flush(); } catch { }
                }
            }) { IsBackground = true, Name = "ErrorWriter" };
            errThread.Start();
        }

        private static JObject LoadLocalConfig()
        {
            try {
                string exeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                string configPath = Path.Combine(exeDir, "config.json");
                if (File.Exists(configPath)) return JObject.Parse(File.ReadAllText(configPath));
            } catch { }
            return null;
        }
    }

    // PERFORMANCE (W-A5): the previous implementation took a lock per-character on every
    // Console.Write call. Every IPC line — JSON-RPC responses that can be tens of KB — paid
    // that cost. Here Write(string) and WriteLine(string) acquire the lock once per call and
    // handle the '\n'/'\r' split internally. Write(char) is preserved for completeness but no
    // longer the hot path.
    public class QueueWriter : TextWriter
    {
        private readonly BlockingCollection<string> _queue;
        private readonly System.Text.StringBuilder _buffer = new System.Text.StringBuilder();

        public QueueWriter(BlockingCollection<string> queue) { _queue = queue; }
        public override System.Text.Encoding Encoding => System.Text.Encoding.UTF8;

        public override void Write(char value)
        {
            if (value == '\r') return;
            lock (_buffer)
            {
                if (value == '\n')
                {
                    _queue.Add(_buffer.ToString());
                    _buffer.Clear();
                }
                else
                {
                    _buffer.Append(value);
                }
            }
        }

        public override void Write(string value)
        {
            if (string.IsNullOrEmpty(value)) return;
            lock (_buffer)
            {
                AppendInternal(value);
            }
        }

        public override void WriteLine(string value)
        {
            lock (_buffer)
            {
                if (!string.IsNullOrEmpty(value)) AppendInternal(value);
                _queue.Add(_buffer.ToString());
                _buffer.Clear();
            }
        }

        // Caller MUST hold _buffer lock.
        private void AppendInternal(string value)
        {
            int start = 0;
            for (int i = 0; i < value.Length; i++)
            {
                char c = value[i];
                if (c == '\r') continue;
                if (c == '\n')
                {
                    if (i > start) _buffer.Append(value, start, i - start);
                    _queue.Add(_buffer.ToString());
                    _buffer.Clear();
                    start = i + 1;
                }
            }
            if (start < value.Length)
            {
                // Append the trailing partial line (no terminator yet) in one shot,
                // skipping any stray '\r' characters between start and end.
                for (int j = start; j < value.Length; j++)
                {
                    char c = value[j];
                    if (c != '\r') _buffer.Append(c);
                }
            }
        }
    }
}
