using System;
using System.Collections.Generic;
using System.Globalization;
using System.Numerics;
using Decal.Adapter;
using Decal.Adapter.Wrappers;
using Newtonsoft.Json;

namespace MosswartMassacre
{
    /// <summary>
    /// On-demand tracker that polls nearby world objects and streams them
    /// to the backend via WebSocket. Activated/deactivated by start_radar
    /// and stop_radar commands from the browser.
    ///
    /// Uses System.Windows.Forms.Timer so the tick fires on the main UI
    /// thread — DECAL COM objects are apartment-threaded (STA) and must
    /// only be accessed from the game's main thread.
    /// </summary>
    public class NearbyObjectsTracker : IDisposable
    {
        private const int PollIntervalMs = 1000;

        private readonly IPluginLogger _logger;
        private readonly DungeonMapReader _dungeonMapReader;
        private System.Windows.Forms.Timer _timer;
        private bool _active;
        private bool _disposed;
        private uint _lastLandblock;
        private bool _inDungeon;

        public bool IsActive => _active;

        public NearbyObjectsTracker(IPluginLogger logger)
        {
            _logger = logger;
            _dungeonMapReader = new DungeonMapReader(logger);
        }

        public void Start()
        {
            if (_active) return;
            _active = true;
            _lastLandblock = 0;
            _inDungeon = false;

            _timer = new System.Windows.Forms.Timer();
            _timer.Interval = PollIntervalMs;
            _timer.Tick += OnTick;
            _timer.Start();

            _logger?.Log("[Radar] Started nearby objects tracker");
        }

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

            if (_timer != null)
            {
                _timer.Stop();
                _timer.Tick -= OnTick;
                _timer.Dispose();
                _timer = null;
            }

            _logger?.Log("[Radar] Stopped nearby objects tracker");
        }

        private async void OnTick(object sender, EventArgs e)
        {
            try
            {
                // Check for landblock change and send dungeon map if needed
                await CheckDungeonTransition();

                var payload = BuildNearbyObjectsPayload();
                if (payload == null) return;

                var json = JsonConvert.SerializeObject(payload);
                await WebSocket.SendNearbyObjectsAsync(json);
            }
            catch (Exception ex)
            {
                _logger?.Log($"[Radar] Tick error: {ex.Message}");
            }
        }

        private async System.Threading.Tasks.Task CheckDungeonTransition()
        {
            uint landcell = Utils.GetPlayerLandcell();
            uint landblock = landcell & 0xFFFF0000;

            if (landblock == _lastLandblock) return;
            _lastLandblock = landblock;

            bool wasDungeon = _inDungeon;
            _inDungeon = _dungeonMapReader.IsDungeon(landcell);

            if (_inDungeon)
            {
                string characterName = CoreManager.Current.CharacterFilter.Name;
                var dungeonMap = _dungeonMapReader.ReadDungeonMap(landcell, characterName);
                if (dungeonMap != null)
                {
                    try
                    {
                        var json = JsonConvert.SerializeObject(dungeonMap);
                        _logger?.Log($"[Radar] Sending dungeon_map ({json.Length} bytes) for 0x{landblock:X8}");
                        await WebSocket.SendDungeonMapAsync(json);
                        _logger?.Log($"[Radar] dungeon_map sent successfully");
                    }
                    catch (Exception ex)
                    {
                        _logger?.Log($"[Radar] dungeon_map send failed: {ex.Message}");
                    }
                }
                else
                {
                    _logger?.Log($"[Radar] ReadDungeonMap returned null for 0x{landblock:X8} (already cached or not a dungeon)");
                }
            }
        }

        private object BuildNearbyObjectsPayload()
        {
            try
            {
                // Guard against COM exceptions during state transitions
                string characterName;
                int playerId;
                double playerHeading;
                try
                {
                    characterName = CoreManager.Current.CharacterFilter.Name;
                    playerId = CoreManager.Current.CharacterFilter.Id;
                    playerHeading = CoreManager.Current.Actions.Heading;
                }
                catch { return null; }

                var playerCoords = Coordinates.Me;
                var playerRawPos = Utils.GetPlayerPosition();
                uint landcell = Utils.GetPlayerLandcell();
                uint landblock = landcell & 0xFFFF0000;

                var objects = new List<object>();

                using (var landscape = CoreManager.Current.WorldFilter.GetLandscape())
                {
                    foreach (WorldObject wo in landscape)
                    {
                        try
                        {
                            if (wo.Id == playerId) continue;
                            if (!CoreManager.Current.Actions.IsValidObject(wo.Id)) continue;

                            // Skip wielded items (equipped weapons/shields on other players)
                            int wielder = 0;
                            try { wielder = wo.Values(LongValueKey.Wielder, 0); } catch { }
                            if (wielder != 0) continue;

                            string objectClass = ClassifyObject(wo.ObjectClass);
                            if (objectClass == null) continue;

                            if (_inDungeon)
                            {
                                // In dungeons, use raw physics positions
                                var rawPos = Utils.GetWorldObjectPosition(wo.Id);
                                if (rawPos == Vector3.Zero) continue;

                                objects.Add(new
                                {
                                    id = wo.Id,
                                    name = wo.Name,
                                    object_class = objectClass,
                                    ew = 0.0,
                                    ns = 0.0,
                                    z = 0.0,
                                    raw_x = Math.Round(rawPos.X, 2),
                                    raw_y = Math.Round(rawPos.Y, 2),
                                    raw_z = Math.Round(rawPos.Z, 2)
                                });
                            }
                            else
                            {
                                // On surface, use AC coordinates
                                var coords = Utils.GetWorldObjectCoordinates(wo);
                                if (coords == null || (coords.EW == 0 && coords.NS == 0)) continue;

                                objects.Add(new
                                {
                                    id = wo.Id,
                                    name = wo.Name,
                                    object_class = objectClass,
                                    ew = Math.Round(coords.EW, 7),
                                    ns = Math.Round(coords.NS, 7),
                                    z = Math.Round(coords.Z, 2)
                                });
                            }
                        }
                        catch
                        {
                            // Skip individual objects that fail
                        }
                    }
                }

                return new
                {
                    type = "nearby_objects",
                    character_name = characterName,
                    timestamp = DateTime.UtcNow.ToString("o"),
                    player_ew = Math.Round(playerCoords.EW, 7),
                    player_ns = Math.Round(playerCoords.NS, 7),
                    player_z = Math.Round(playerCoords.Z, 2),
                    player_heading = Math.Round(playerHeading, 1),
                    is_dungeon = _inDungeon,
                    landblock = $"0x{landblock:X8}",
                    player_x = Math.Round(playerRawPos.X, 2),
                    player_y = Math.Round(playerRawPos.Y, 2),
                    player_raw_z = Math.Round(playerRawPos.Z, 2),
                    objects
                };
            }
            catch (Exception ex)
            {
                _logger?.Log($"[Radar] Build payload error: {ex.Message}");
                return null;
            }
        }

        private static string ClassifyObject(ObjectClass oc)
        {
            switch (oc)
            {
                case ObjectClass.Monster:   return "Monster";
                case ObjectClass.Player:    return "Player";
                case ObjectClass.Npc:       return "NPC";
                case ObjectClass.Vendor:    return "Vendor";
                case ObjectClass.Portal:    return "Portal";
                case ObjectClass.Corpse:    return "Corpse";
                case ObjectClass.Container: return "Container";
                case ObjectClass.Door:      return "Door";
                default:                    return null;
            }
        }

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