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

namespace Jovian.Logger {
    /// <summary>
    /// TCP server that receives log messages from RemoteLogSender on device builds.
    /// Managed by Custom Console — not intended for standalone use.
    /// </summary>
    internal static class RemoteLogReceiver {
        public const int DefaultPort = 9876;

        public struct RemoteLogEntry {
            public string message;
            public string stackTrace;
            public string timestamp;
            public int frameCount;
            public JovianLogType type;
            public LogCategory logCategory;
            public bool isCustomLog;
        }

        public struct RemoteWatchEntry {
            public string key;
            public string value;
            public LogCategory logCategory;
            public string timestamp;
            public int frameCount;
        }

        public static event Action<RemoteLogEntry> OnRemoteLog;
        public static event Action<RemoteWatchEntry> OnRemoteWatch;
        public static event Action<string> OnRemoteUnwatch;
        public static event Action<string> OnClientConnected;
        public static event Action OnClientDisconnected;

        private static TcpListener listener;
        private static Thread listenThread;
        private static volatile bool running;
        private static int port = DefaultPort;

        public static bool IsRunning => running;
        public static int Port => port;
        public static string ConnectedClientName { get; private set; } = "";
        public static bool HasClient { get; private set; }

        private static readonly ConcurrentQueue<Action> mainThreadQueue = new ConcurrentQueue<Action>();

        public static void Start(int listenPort = DefaultPort) {
            if (running) return;
            port = listenPort;

            try {
                listener = new TcpListener(IPAddress.Any, port);
                listener.Start();
                running = true;

                listenThread = new Thread(ListenLoop) {
                    IsBackground = true,
                    Name = "RemoteLogReceiver"
                };
                listenThread.Start();

                EditorApplication.update += ProcessMainThreadQueue;
                Debug.Log($"[RemoteLog] Listening on port {port}");
            } catch (Exception e) {
                Debug.LogError($"[RemoteLog] Failed to start listener: {e.Message}");
                running = false;
            }
        }

        public static void Stop() {
            running = false;
            HasClient = false;
            ConnectedClientName = "";

            try { listener?.Stop(); } catch { }
            listener = null;

            EditorApplication.update -= ProcessMainThreadQueue;
        }

        private static void ProcessMainThreadQueue() {
            while (mainThreadQueue.TryDequeue(out var action)) {
                action?.Invoke();
            }
        }

        private static void ListenLoop() {
            while (running) {
                try {
                    if (!listener.Pending()) {
                        Thread.Sleep(100);
                        continue;
                    }

                    var client = listener.AcceptTcpClient();
                    client.NoDelay = true;
                    client.ReceiveTimeout = 0;
                    HandleClient(client);
                } catch (SocketException) {
                    if (!running) break;
                } catch (ObjectDisposedException) {
                    break;
                }
            }
        }

        private static void HandleClient(TcpClient client) {
            var thread = new Thread(() => ClientReadLoop(client)) {
                IsBackground = true,
                Name = "RemoteLogClient"
            };
            thread.Start();
        }

        private static void ClientReadLoop(TcpClient client) {
            try {
                using var stream = client.GetStream();
                using var reader = new StreamReader(stream, Encoding.UTF8);

                HasClient = true;

                while (running && client.Connected) {
                    string line = reader.ReadLine();
                    if (line == null) break;
                    if (string.IsNullOrWhiteSpace(line)) continue;

                    ParseAndDispatch(line);
                }
            } catch (IOException) {
                // Connection lost
            } catch (ObjectDisposedException) {
                // Shutting down
            } finally {
                try { client.Close(); } catch { }
                HasClient = false;
                ConnectedClientName = "";
                mainThreadQueue.Enqueue(() => OnClientDisconnected?.Invoke());
            }
        }

        private static void ParseAndDispatch(string json) {
            try {
                // Check for handshake
                if (json.Contains("\"handshake\"")) {
                    string appName = ExtractJsonString(json, "app");
                    string platform = ExtractJsonString(json, "platform");
                    ConnectedClientName = $"{appName} ({platform})";
                    mainThreadQueue.Enqueue(() => OnClientConnected?.Invoke(ConnectedClientName));
                    return;
                }

                // Check for watch
                if (json.Contains("\"watch\"")) {
                    var watchEntry = new RemoteWatchEntry {
                        key = ExtractJsonString(json, "wk"),
                        value = ExtractJsonString(json, "wv"),
                        logCategory = (LogCategory)ExtractJsonInt(json, "c"),
                        timestamp = ExtractJsonString(json, "ts"),
                        frameCount = ExtractJsonInt(json, "fc"),
                    };
                    mainThreadQueue.Enqueue(() => OnRemoteWatch?.Invoke(watchEntry));
                    return;
                }

                // Check for unwatch
                if (json.Contains("\"unwatch\"")) {
                    string key = ExtractJsonString(json, "wk");
                    mainThreadQueue.Enqueue(() => OnRemoteUnwatch?.Invoke(key));
                    return;
                }

                var entry = new RemoteLogEntry {
                    message = ExtractJsonString(json, "m"),
                    stackTrace = ExtractJsonString(json, "s"),
                    timestamp = ExtractJsonString(json, "ts"),
                    frameCount = ExtractJsonInt(json, "fc"),
                    type = (JovianLogType)ExtractJsonInt(json, "t"),
                    logCategory = (LogCategory)ExtractJsonInt(json, "c"),
                    isCustomLog = ExtractJsonBool(json, "f"),
                };

                mainThreadQueue.Enqueue(() => OnRemoteLog?.Invoke(entry));
            } catch (Exception e) {
                Debug.LogWarning($"[RemoteLog] Failed to parse: {e.Message}");
            }
        }

        // Lightweight JSON field extractors (avoids dependency on full JSON parser for simple protocol)
        private static string ExtractJsonString(string json, string key) {
            string pattern = "\"" + key + "\":\"";
            int start = json.IndexOf(pattern, StringComparison.Ordinal);
            if (start < 0) return "";
            start += pattern.Length;

            var sb = new StringBuilder();
            for (int i = start; i < json.Length; i++) {
                char c = json[i];
                if (c == '\\' && i + 1 < json.Length) {
                    char next = json[i + 1];
                    switch (next) {
                        case '"': sb.Append('"'); i++; break;
                        case '\\': sb.Append('\\'); i++; break;
                        case 'n': sb.Append('\n'); i++; break;
                        case 'r': sb.Append('\r'); i++; break;
                        case 't': sb.Append('\t'); i++; break;
                        default: sb.Append(c); break;
                    }
                } else if (c == '"') {
                    break;
                } else {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }

        private static int ExtractJsonInt(string json, string key) {
            string pattern = "\"" + key + "\":";
            int start = json.IndexOf(pattern, StringComparison.Ordinal);
            if (start < 0) return 0;
            start += pattern.Length;

            int end = start;
            while (end < json.Length && (char.IsDigit(json[end]) || json[end] == '-')) end++;
            if (end == start) return 0;
            return int.TryParse(json.AsSpan(start, end - start), out int val) ? val : 0;
        }

        private static bool ExtractJsonBool(string json, string key) {
            string pattern = "\"" + key + "\":";
            int start = json.IndexOf(pattern, StringComparison.Ordinal);
            if (start < 0) return false;
            start += pattern.Length;
            return start < json.Length && json[start] == 't';
        }
    }
}
