using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using UnityEditor;
using UnityEngine;

namespace UnityAssistant
{
    [InitializeOnLoad]
    public static class BridgeServer
    {
        public const int Port = 6789;
        private static HttpListener _listener;
        private static Thread _thread;
        private static readonly ConcurrentQueue<Action> _mainThreadQueue = new ConcurrentQueue<Action>();
        private static volatile bool _running;
        private static long _requestCount;
        private static string _lastError = "";
        private static DateTime _startedAt;

        public static bool IsRunning => _running;
        public static long RequestCount => _requestCount;
        public static string LastError => _lastError;
        public static DateTime StartedAt => _startedAt;
        public static int PortNumber => Port;

        public static event Action OnStateChanged;

        static BridgeServer()
        {
            // [InitializeOnLoad] runs the static ctor on every domain reload, so
            // _running is already false here. This is the single resurrection point —
            // it fires fresh after every recompile, every Editor launch, every Play→Edit.
            EditorApplication.update += PumpMainThread;
            EditorApplication.quitting += Stop;
            AssemblyReloadEvents.beforeAssemblyReload += OnBeforeReload;

            EditorApplication.delayCall += DelayedStart;
        }

        private static void DelayedStart()
        {
            if (!BridgePrefs.AutoStart)
            {
                Debug.Log("[Gumo] Auto-start disabled; bridge stopped. Toggle from the Gumo menu.");
                return;
            }
            if (_running)
            {
                Debug.Log("[Gumo] Bridge already running, skipping auto-start.");
                return;
            }
            Start();
        }

        private static void OnBeforeReload() { Stop(); }

        public static void Start()
        {
            if (_running) return;

            // If a previous listener is still alive (e.g. stuck after a domain reload), tear it down.
            try { _listener?.Stop(); } catch { }
            try { _listener?.Close(); } catch { }
            _listener = null;

            Exception last = null;
            for (int attempt = 0; attempt < 5; attempt++)
            {
                try
                {
                    _listener = new HttpListener();
                    _listener.Prefixes.Add($"http://127.0.0.1:{Port}/");
                    _listener.Start();
                    _running = true;
                    _startedAt = DateTime.Now;
                    _lastError = "";
                    _thread = new Thread(Listen) { IsBackground = true, Name = "UnityAssistantBridge" };
                    _thread.Start();
                    Debug.Log($"[Gumo] Bridge listening on http://127.0.0.1:{Port}/");
                    OnStateChanged?.Invoke();
                    return;
                }
                catch (Exception e)
                {
                    last = e;
                    try { _listener?.Close(); } catch { }
                    _listener = null;
                    Thread.Sleep(150);
                }
            }
            _lastError = last?.Message ?? "unknown";
            Debug.LogError($"[Gumo] Failed to start bridge after retries: {_lastError}");
            OnStateChanged?.Invoke();
        }

        public static void Stop()
        {
            bool was = _running;
            _running = false;
            try { _listener?.Stop(); } catch { }
            try { _listener?.Close(); } catch { }
            _listener = null;
            // Wait for the listen thread to fully exit so the socket is released
            // before any subsequent Start() tries to rebind the port.
            var t = _thread;
            _thread = null;
            if (t != null && t.IsAlive)
            {
                try { t.Join(2000); } catch { }
            }
            if (was) OnStateChanged?.Invoke();
        }

        public static void Restart() { Stop(); Start(); }

        private static void Listen()
        {
            while (_running && _listener != null)
            {
                HttpListenerContext ctx;
                try { ctx = _listener.GetContext(); }
                catch { break; }
                ThreadPool.QueueUserWorkItem(_ => Handle(ctx));
            }
        }

        private static void Handle(HttpListenerContext ctx)
        {
            try
            {
                ctx.Response.AddHeader("Access-Control-Allow-Origin", "app://unity-assistant");
                ctx.Response.AddHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
                ctx.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, X-Unity-Assistant-Token");

                if (ctx.Request.HttpMethod == "OPTIONS")
                {
                    ctx.Response.StatusCode = 204;
                    ctx.Response.Close();
                    return;
                }

                Interlocked.Increment(ref _requestCount);

                string body = "";
                using (var sr = new StreamReader(ctx.Request.InputStream, Encoding.UTF8))
                    body = sr.ReadToEnd();

                string path = ctx.Request.Url.AbsolutePath.Trim('/').ToLower();

                if (path == "ping")
                {
                    if (!BridgeAuth.IsAuthorized(ctx.Request))
                    {
                        Respond(ctx, "{\"ok\":false,\"error\":\"unauthorized\"}", 401);
                        return;
                    }
                    Respond(ctx, "{\"ok\":true,\"version\":\"1.0.0\"}");
                    return;
                }

                if (path != "rpc")
                {
                    Respond(ctx, "{\"ok\":false,\"error\":\"unknown endpoint\"}", 404);
                    return;
                }

                if (!BridgeAuth.IsAuthorized(ctx.Request))
                {
                    Respond(ctx, "{\"ok\":false,\"error\":\"unauthorized\"}", 401);
                    return;
                }

                var done = new ManualResetEventSlim(false);
                string result = null;
                string error = null;

                _mainThreadQueue.Enqueue(() =>
                {
                    try { result = ToolDispatcher.Dispatch(body); }
                    catch (Exception e) { error = e.Message + "\n" + e.StackTrace; }
                    finally { done.Set(); }
                });

                if (!done.Wait(TimeSpan.FromSeconds(30)))
                {
                    Respond(ctx, "{\"ok\":false,\"error\":\"timeout\"}", 504);
                    return;
                }

                if (error != null)
                    Respond(ctx, "{\"ok\":false,\"error\":" + JsonUtil.Quote(error) + "}", 500);
                else
                    Respond(ctx, result ?? "{\"ok\":true}");
            }
            catch (Exception e)
            {
                try { Respond(ctx, "{\"ok\":false,\"error\":" + JsonUtil.Quote(e.Message) + "}", 500); }
                catch { }
            }
        }

        private static void Respond(HttpListenerContext ctx, string json, int code = 200)
        {
            try
            {
                ctx.Response.StatusCode = code;
                ctx.Response.ContentType = "application/json";
                var bytes = Encoding.UTF8.GetBytes(json);
                ctx.Response.ContentLength64 = bytes.Length;
                ctx.Response.OutputStream.Write(bytes, 0, bytes.Length);
                ctx.Response.Close();
            }
            catch { }
        }

        private static void PumpMainThread()
        {
            int budget = 16;
            while (budget-- > 0 && _mainThreadQueue.TryDequeue(out var action))
            {
                try { action(); }
                catch (Exception e) { Debug.LogError($"[Gumo] {e}"); }
            }
        }
    }
}
