// WebSocket.cs
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Decal.Adapter;
using Newtonsoft.Json;
using uTank2;

namespace MosswartMassacre
{
    internal static class SessionInfo
    {
    
        internal static readonly Guid Guid = Guid.NewGuid();

        internal static readonly string GuidString = Guid.ToString("N");
    }
    // 1) The envelope type for incoming commands
    public class CommandEnvelope
    {
        [JsonProperty("player_name")]
        public string PlayerName { get; set; }

        [JsonProperty("command")]
        public string Command { get; set; }
    }

    public static class WebSocket
    {
        // ─── configuration ──────────────────────────
        private static readonly Uri WsEndpoint = new Uri("wss://overlord.snakedesert.se/websocket/");
        private const string SharedSecret = "your_shared_secret";
        private const int IntervalSec = 5;
        internal static string SessionId = "";
        private static IPluginLogger _logger;
        private static IGameStats _gameStats;

        // ─── cached prismatic taper count ─── (now handled by PluginCore event system)

        // ─── runtime state ──────────────────────────
        private static ClientWebSocket _ws;
        private static CancellationTokenSource _cts;
        private static bool _enabled;
        private static readonly SemaphoreSlim _sendLock = new SemaphoreSlim(1, 1);

        /// <summary>
        /// Fires when a valid CommandEnvelope arrives for this character.
        /// </summary>
        public static event Action<CommandEnvelope> OnServerCommand;

        /// <summary>
        /// Fires when any other typed JSON message arrives from the server
        /// (e.g. share_vital_update, share_cast_attempt). Passes the raw JSON
        /// string — the handler is responsible for parsing based on the "type" field.
        /// </summary>
        public static event Action<string> OnServerMessage;

        // ─── public API ─────────────────────────────

        public static void SetLogger(IPluginLogger logger) => _logger = logger;
        public static void SetGameStats(IGameStats gameStats) => _gameStats = gameStats;

        public static void Start()
        {
            if (_enabled) return;
            _enabled = true;
            _cts = new CancellationTokenSource();

            _logger?.Log("[WebSocket] connecting…");
            _ = Task.Run(ConnectAndLoopAsync);

        }

        public static void Stop()
        {
            if (!_enabled) return;
            _enabled = false;

            _cts.Cancel();
            _ws?.Abort();
            _ws?.Dispose();
            _ws = null;

            _logger?.Log("[WebSocket] DISABLED");
        }

        // ─── connect / receive / telemetry loop ──────────────────────

        private static async Task ConnectAndLoopAsync()
        {
            while (_enabled && !_cts.IsCancellationRequested)
            {
                try
                {
                    // 1) Establish connection
                    _ws = new ClientWebSocket();
                    _ws.Options.SetRequestHeader("X-Plugin-Secret", SharedSecret);
                    await _ws.ConnectAsync(WsEndpoint, _cts.Token);
                    _logger?.Log("[WebSocket] CONNECTED");
                    SessionId = $"{CoreManager.Current.CharacterFilter.Name}-{DateTime.UtcNow:yyyyMMdd-HHmmss}";

        // ─── Register this socket under our character name ───
        var registerEnvelope = new
                    {
                        type = "register",
                        player_name = CoreManager.Current.CharacterFilter.Name
                    };
                    var regJson = JsonConvert.SerializeObject(registerEnvelope);
                    await SendEncodedAsync(regJson, _cts.Token);
                    _logger?.Log("[WebSocket] REGISTERED");

                    // ─── Re-subscribe to vital sharing on every (re)connect ───
                    // The backend evicts subscribers on disconnect, so after a
                    // reconnect we need to re-announce or our share_* messages
                    // won't be forwarded to other clients.
                    try
                    {
                        if (PluginSettings.IsInitialized && PluginSettings.Instance.VitalSharingEnabled)
                        {
                            var tag = PluginSettings.Instance.CharTag;
                            var tags = string.IsNullOrWhiteSpace(tag)
                                ? new string[0]
                                : new[] { tag };
                            var subEnvelope = new
                            {
                                type = "share_subscribe",
                                timestamp = DateTime.UtcNow.ToString("o"),
                                character_name = CoreManager.Current.CharacterFilter.Name,
                                tags = tags,
                            };
                            await SendEncodedAsync(JsonConvert.SerializeObject(subEnvelope), _cts.Token);
                            _logger?.Log("[WebSocket] Vital sharing re-subscribed");
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger?.Log($"[WebSocket] Re-subscribe error: {ex.Message}");
                    }

                    var buffer = new byte[4096];

                    // 2) Fire-and-forget receive loop
                    var receiveTask = Task.Run(async () =>
                    {
                        while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
                        {
                            WebSocketReceiveResult result;
                            try
                            {
                                result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cts.Token);
                            }
                            catch (OperationCanceledException)
                            {
                                break;
                            }
                            catch (Exception ex)
                            {
                                _logger?.Log($"[WebSocket] receive error: {ex.Message}");
                                break;
                            }

                            if (result.MessageType == WebSocketMessageType.Close)
                                break;

                            var msg = Encoding.UTF8.GetString(buffer, 0, result.Count).Trim();

                            // 3) Peek at the message type to route appropriately
                            Newtonsoft.Json.Linq.JObject root = null;
                            try
                            {
                                root = Newtonsoft.Json.Linq.JObject.Parse(msg);
                            }
                            catch (JsonException)
                            {
                                continue;  // skip malformed JSON
                            }

                            var msgType = (string)root["type"];

                            // Route share_* messages via OnServerMessage — the tracker filters
                            // out messages from its own character, so no player_name check here
                            if (!string.IsNullOrEmpty(msgType) && msgType.StartsWith("share_"))
                            {
                                try { OnServerMessage?.Invoke(msg); }
                                catch (Exception ex) { _logger?.Log($"[WebSocket] OnServerMessage error: {ex.Message}"); }
                                continue;
                            }

                            // 4) Otherwise treat as CommandEnvelope filtered by character name
                            CommandEnvelope env;
                            try
                            {
                                env = root.ToObject<CommandEnvelope>();
                            }
                            catch (JsonException)
                            {
                                continue;
                            }

                            if (env != null && string.Equals(
                                env.PlayerName,
                                CoreManager.Current.CharacterFilter.Name,
                                StringComparison.OrdinalIgnoreCase))
                            {
                                // Fire event immediately - let PluginCore handle threading
                                OnServerCommand?.Invoke(env);
                            }
                        }
                    });

                    // 5) Inline telemetry loop
                    _logger?.Log("[WebSocket] Starting telemetry loop");
                    while (_ws.State == WebSocketState.Open && !_cts.Token.IsCancellationRequested)
                    {
                        try
                        {
                            var json = BuildPayloadJson();
                            await SendEncodedAsync(json, _cts.Token);
                        }
                        catch (Exception ex)
                        {
                            _logger?.Log($"[WebSocket] Telemetry failed: {ex.Message}");
                            break; // Exit telemetry loop on failure
                        }

                        try
                        {
                            await Task.Delay(TimeSpan.FromSeconds(IntervalSec), _cts.Token);
                        }
                        catch (OperationCanceledException)
                        {
                            _logger?.Log("[WebSocket] Telemetry loop cancelled");
                            break;
                        }
                    }

                    // Log why telemetry loop exited
                    _logger?.Log($"[WebSocket] Telemetry loop ended - State: {_ws?.State}, Cancelled: {_cts.Token.IsCancellationRequested}");

                    // Wait for receive loop to finish
                    await receiveTask;
                }
                catch (OperationCanceledException)
                {
                    _logger?.Log("[WebSocket] Connection cancelled");
                    break;
                }
                catch (Exception ex)
                {
                    _logger?.Log($"[WebSocket] Connection error: {ex.Message}");
                }
                finally
                {
                    var finalState = _ws?.State.ToString() ?? "null";
                    _logger?.Log($"[WebSocket] Cleaning up connection - Final state: {finalState}");
                    _ws?.Abort();
                    _ws?.Dispose();
                    _ws = null;
                }

                // Pause before reconnecting
                if (_enabled)
                {
                    _logger?.Log("[WebSocket] Reconnecting in 2 seconds...");
                    try { await Task.Delay(2000, CancellationToken.None); } catch { }
                }
            }
        }


        // ─── fire-and-forget chat sender ────────────────────

        public static async Task SendChatTextAsync(int colorIndex, string chatText)
        {
            var envelope = new
            {
                type = "chat",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                text = chatText,
                color = colorIndex
                
            };
            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }
        public static async Task SendSpawnAsync(string nsCoord, string ewCoord, string zCoord, string monster)
        {
            var envelope = new
            {
                type = "spawn",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                mob = monster,
                ns = nsCoord,
                ew = ewCoord,
                z = zCoord
            };
            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendPortalAsync(string nsCoord, string ewCoord, string zCoord, string portalName)
        {
            var envelope = new
            {
                type = "portal",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                portal_name = portalName,
                ns = nsCoord,
                ew = ewCoord,
                z = zCoord
            };
            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }
        public static async Task SendRareAsync(string rare)
        {
            var coords = Coordinates.Me;
            var envelope = new
            {
                type = "rare",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                name = rare,
                ew = coords.EW,
                ns = coords.NS,
                z = coords.Z

            };
            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendFullInventoryAsync(List<Mag.Shared.MyWorldObject> inventory)
        {
            var envelope = new
            {
                type = "full_inventory",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                item_count = inventory.Count,
                items = inventory

            };
            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendInventoryDeltaAsync(string action, Mag.Shared.MyWorldObject item)
        {
            var envelope = new
            {
                type = "inventory_delta",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                action = action,
                item = item
            };
            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendInventoryRemoveAsync(int itemId)
        {
            var envelope = new
            {
                type = "inventory_delta",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                action = "remove",
                item_id = itemId
            };
            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendVitalsAsync(object vitalsData)
        {
            var json = JsonConvert.SerializeObject(vitalsData);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        /// <summary>
        /// Send a vital-sharing event (share_vital_update, share_cast_attempt, etc.)
        /// These are forwarded by the backend to all other opted-in plugin clients
        /// so VTank's state can be synchronized across machines.
        /// </summary>
        public static async Task SendVitalShareAsync(object payload)
        {
            var json = JsonConvert.SerializeObject(payload);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendCombatStatsAsync(object payload)
        {
            var json = JsonConvert.SerializeObject(payload);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendCharacterStatsAsync(object statsData)
        {
            var json = JsonConvert.SerializeObject(statsData);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendEquipmentCantripStateAsync(object[] items)
        {
            var envelope = new
            {
                type = "equipment_cantrip_state",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                items = items
            };

            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendNearbyObjectsAsync(string json)
        {
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendDungeonMapAsync(string json)
        {
            await SendEncodedAsync(json, CancellationToken.None);
        }

        public static async Task SendQuestDataAsync(string questName, string countdown)
        {
            var envelope = new
            {
                type = "quest",
                timestamp = DateTime.UtcNow.ToString("o"),
                character_name = CoreManager.Current.CharacterFilter.Name,
                quest_name = questName,
                countdown = countdown
            };
            var json = JsonConvert.SerializeObject(envelope);
            await SendEncodedAsync(json, CancellationToken.None);
        }

        // ─── shared send helper with locking ───────────────

        private static async Task SendEncodedAsync(string text, CancellationToken token)
        {
            await _sendLock.WaitAsync(token);
            try
            {
                if (_ws == null || _ws.State != WebSocketState.Open)
                    return;

                var bytes = Encoding.UTF8.GetBytes(text);
                await _ws.SendAsync(new ArraySegment<byte>(bytes),
                                    WebSocketMessageType.Text,
                                    true,
                                    token);
            }
            catch (Exception ex)
            {
                _logger?.Log($"[WebSocket] Send error: {ex.Message}");
                _ws?.Abort();
                _ws?.Dispose();
                _ws = null;
            }
            finally
            {
                _sendLock.Release();
            }
        }

        // ─── payload builder ──────────────────────────────

        private static string BuildPayloadJson()
        {
            var tele = new ClientTelemetry();
            var coords = Coordinates.Me;
            var stats = _gameStats;
            var payload = new
            {
                type = "telemetry",
                character_name = CoreManager.Current.CharacterFilter.Name,
                char_tag = stats?.CharTag ?? "",
                session_id = SessionInfo.GuidString,
                timestamp = DateTime.UtcNow.ToString("o"),
                ew = coords.EW,
                ns = coords.NS,
                z = coords.Z,
                kills = stats?.TotalKills ?? 0,
                kills_per_hour = (stats?.KillsPerHour ?? 0).ToString("F0"),
                onlinetime = (DateTime.Now - (stats?.StatsStartTime ?? DateTime.Now)).ToString(@"dd\.hh\:mm\:ss"),
                deaths = (stats?.SessionDeaths ?? 0).ToString(),
                total_deaths = (stats?.TotalDeaths ?? 0).ToString(),
                prismatic_taper_count = (stats?.CachedPrismaticCount ?? 0).ToString(),
                vt_state = VtankControl.VtGetMetaState(),
                mem_mb = tele.MemoryBytes,
                cpu_pct = tele.GetCpuUsage(),
                mem_handles = tele.HandleCount
            };
            return JsonConvert.SerializeObject(payload);
        }
    }
}
