using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

using Decal.Adapter;
using Decal.Adapter.Wrappers;
using Decal.Filters;

namespace MosswartMassacre
{
    internal sealed class EquipmentCantripStateTracker : IDisposable
    {
        private readonly IPluginLogger _logger;
        private readonly Timer _debounceTimer;
        private readonly Dictionary<int, DateTime> _lastManaUpdateByItemId = new Dictionary<int, DateTime>();

        internal EquipmentCantripStateTracker(IPluginLogger logger)
        {
            _logger = logger;
            _debounceTimer = new Timer();
            _debounceTimer.Interval = 250;
            _debounceTimer.Tick += DebounceTimer_Tick;
        }

        internal void Initialize()
        {
            try
            {
                foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
                {
                    RememberManaSnapshotTime(wo);
                }

                RequestRefresh();
            }
            catch (Exception ex)
            {
                _logger?.Log($"[EquipCantrip] Initialize error: {ex.Message}");
            }
        }

        internal void Cleanup()
        {
            _debounceTimer.Stop();
            _lastManaUpdateByItemId.Clear();
        }

        internal void OnCreateObject(object sender, CreateObjectEventArgs e)
        {
            try
            {
                if (!IsPotentiallyRelevant(e.New))
                    return;

                RememberManaSnapshotTime(e.New);
                RequestRefresh();
            }
            catch (Exception ex)
            {
                _logger?.Log($"[EquipCantrip] Create error: {ex.Message}");
            }
        }

        internal void OnReleaseObject(object sender, ReleaseObjectEventArgs e)
        {
            try
            {
                if (_lastManaUpdateByItemId.Remove(e.Released.Id) || IsPotentiallyRelevant(e.Released))
                {
                    RequestRefresh();
                }
            }
            catch (Exception ex)
            {
                _logger?.Log($"[EquipCantrip] Release error: {ex.Message}");
            }
        }

        internal void OnChangeObject(object sender, ChangeObjectEventArgs e)
        {
            try
            {
                if (e.Change == WorldChangeType.IdentReceived || e.Change == WorldChangeType.ManaChange)
                {
                    RememberManaSnapshotTime(e.Changed);
                }

                if (IsPotentiallyRelevant(e.Changed) || _lastManaUpdateByItemId.ContainsKey(e.Changed.Id))
                {
                    RequestRefresh();
                }
            }
            catch (Exception ex)
            {
                _logger?.Log($"[EquipCantrip] Change error: {ex.Message}");
            }
        }

        internal void OnChangeEnchantments(object sender, ChangeEnchantmentsEventArgs e)
        {
            RequestRefresh();
        }

        private void DebounceTimer_Tick(object sender, EventArgs e)
        {
            _debounceTimer.Stop();
            SendSnapshot();
        }

        private void RequestRefresh()
        {
            _debounceTimer.Stop();
            _debounceTimer.Start();
        }

        private void SendSnapshot()
        {
            try
            {
                if (!PluginCore.WebSocketEnabled)
                    return;

                var items = BuildSnapshotItems();
                _ = WebSocket.SendEquipmentCantripStateAsync(items.ToArray());
            }
            catch (Exception ex)
            {
                _logger?.Log($"[EquipCantrip] Send error: {ex.Message}");
            }
        }

        private List<object> BuildSnapshotItems()
        {
            var snapshotItems = new List<object>();
            FileService fileService = null;

            try
            {
                fileService = CoreManager.Current.Filter<FileService>();
                if (fileService?.SpellTable == null)
                    return snapshotItems;

                var activeCharacterSpells = GetActiveCharacterItemSpellData(fileService);

                foreach (WorldObject wo in CoreManager.Current.WorldFilter.GetInventory())
                {
                    if (!ShouldDisplayInManaPanel(wo))
                        continue;

                    var snapshot = BuildItemSnapshot(wo, fileService, activeCharacterSpells);
                    if (snapshot != null)
                    {
                        snapshotItems.Add(snapshot);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger?.Log($"[EquipCantrip] Snapshot build error: {ex.Message}");
            }

            return snapshotItems;
        }

        private List<Spell> GetActiveCharacterItemSpellData(FileService fileService)
        {
            var activeSpells = new List<Spell>();
            var cf = CoreManager.Current.CharacterFilter;

            try
            {
                int enchantmentCount = cf.Underlying.EnchantmentCount;
                for (int i = 0; i < enchantmentCount; i++)
                {
                    Decal.Interop.Filters.Enchantment enchantment = null;
                    try
                    {
                        enchantment = cf.Underlying.get_Enchantment(i);
                        if (enchantment == null || enchantment.TimeRemaining > 0)
                            continue;

                        var spell = fileService.SpellTable.GetById(enchantment.SpellID);
                        if (spell != null)
                        {
                            activeSpells.Add(spell);
                        }
                    }
                    finally
                    {
                        if (enchantment != null)
                        {
                            System.Runtime.InteropServices.Marshal.ReleaseComObject(enchantment);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger?.Log($"[EquipCantrip] Active enchantment read error: {ex.Message}");
            }

            return activeSpells;
        }

        private object BuildItemSnapshot(WorldObject wo, FileService fileService, List<Spell> activeCharacterSpells)
        {
            var itemState = RecalculateState(wo, fileService, activeCharacterSpells);
            if (itemState == EquipmentCantripItemState.NotActivatable)
                return null;

            int maximumMana = wo.Values(LongValueKey.MaximumMana, 0);
            int currentMana = itemState == EquipmentCantripItemState.Unknown ? 0 : CalculateCurrentMana(wo, itemState);
            double? manaTimeRemainingSeconds = null;

            if (itemState == EquipmentCantripItemState.Active)
            {
                var remaining = CalculateManaTimeRemaining(wo, currentMana);
                manaTimeRemainingSeconds = remaining.TotalSeconds;
            }

            return new
            {
                item_id = wo.Id,
                state = ToWireState(itemState),
                current_mana = itemState == EquipmentCantripItemState.Unknown ? (int?)null : currentMana,
                max_mana = itemState == EquipmentCantripItemState.Unknown ? (int?)null : maximumMana,
                mana_time_remaining_seconds = manaTimeRemainingSeconds
            };
        }

        private EquipmentCantripItemState RecalculateState(WorldObject wo, FileService fileService, List<Spell> activeCharacterSpells)
        {
            if (wo == null || !wo.HasIdData)
                return EquipmentCantripItemState.Unknown;

            if (wo.SpellCount == 0 || wo.Values(LongValueKey.MaximumMana, 0) == 0)
                return EquipmentCantripItemState.NotActivatable;

            if (wo.Values(LongValueKey.CurrentMana, 0) == 0)
                return EquipmentCantripItemState.NotActive;

            for (int i = 0; i < wo.SpellCount; i++)
            {
                int spellOnItemId = wo.Spell(i);

                if (wo.Exists(LongValueKey.AssociatedSpell) && wo.Values(LongValueKey.AssociatedSpell) == spellOnItemId)
                    continue;

                var spellOnItem = fileService.SpellTable.GetById(spellOnItemId);
                if (spellOnItem == null)
                    continue;

                if (spellOnItem.IsDebuff || spellOnItem.IsOffensive)
                    continue;

                bool thisSpellIsActive = false;

                for (int j = 0; j < wo.ActiveSpellCount; j++)
                {
                    var activeSpellOnItem = fileService.SpellTable.GetById(wo.ActiveSpell(j));
                    if (SpellMatchesOrSurpasses(activeSpellOnItem, spellOnItem))
                    {
                        thisSpellIsActive = true;
                        break;
                    }
                }

                if (thisSpellIsActive)
                    continue;

                foreach (var activeSpellOnChar in activeCharacterSpells)
                {
                    if (SpellMatchesOrSurpasses(activeSpellOnChar, spellOnItem))
                    {
                        thisSpellIsActive = true;
                        break;
                    }
                }

                if (!thisSpellIsActive)
                    return EquipmentCantripItemState.NotActive;
            }

            return EquipmentCantripItemState.Active;
        }

        private static bool SpellMatchesOrSurpasses(Spell activeSpell, Spell requiredSpell)
        {
            if (activeSpell == null || requiredSpell == null)
                return false;

            return activeSpell.Family == requiredSpell.Family && activeSpell.Difficulty >= requiredSpell.Difficulty;
        }

        private int CalculateCurrentMana(WorldObject wo, EquipmentCantripItemState itemState)
        {
            if (wo == null)
                return 0;

            if (itemState == EquipmentCantripItemState.Unknown || itemState == EquipmentCantripItemState.NotActivatable)
                return 0;

            if (itemState == EquipmentCantripItemState.NotActive)
                return wo.Values(LongValueKey.CurrentMana, 0);

            var secondsPerBurn = GetSecondsPerBurn(wo);
            if (secondsPerBurn <= 0)
                return wo.Values(LongValueKey.CurrentMana, 0);

            if (!_lastManaUpdateByItemId.TryGetValue(wo.Id, out var lastUpdate))
            {
                lastUpdate = DateTime.UtcNow;
                _lastManaUpdateByItemId[wo.Id] = lastUpdate;
            }

            var timeSinceLastUpdate = DateTime.UtcNow - lastUpdate;
            int burnedMana = (int)(timeSinceLastUpdate.TotalSeconds / secondsPerBurn);
            int currentMana = wo.Values(LongValueKey.CurrentMana, 0);
            if (burnedMana > currentMana)
                burnedMana = currentMana;

            return Math.Max(currentMana - burnedMana, 0);
        }

        private TimeSpan CalculateManaTimeRemaining(WorldObject wo, int calculatedCurrentMana)
        {
            var secondsPerBurn = GetSecondsPerBurn(wo);
            if (secondsPerBurn <= 0)
                return new TimeSpan(99, 99, 0);

            return TimeSpan.FromSeconds(calculatedCurrentMana * secondsPerBurn);
        }

        private double GetSecondsPerBurn(WorldObject wo)
        {
            if (wo == null || !wo.HasIdData)
                return 0;

            double manaRateOfChange = wo.Values(DoubleValueKey.ManaRateOfChange, 0);
            if (manaRateOfChange == 0)
                return 0;

            return ((int)Math.Ceiling(-0.2 / manaRateOfChange)) * 5;
        }

        private void RememberManaSnapshotTime(WorldObject wo)
        {
            if (wo == null)
                return;

            _lastManaUpdateByItemId[wo.Id] = DateTime.UtcNow;
        }

        private bool IsPotentiallyRelevant(WorldObject wo)
        {
            return wo != null && (ItemIsEquippedByMe(wo) || _lastManaUpdateByItemId.ContainsKey(wo.Id));
        }

        private bool ShouldDisplayInManaPanel(WorldObject wo)
        {
            if (wo == null || !ItemIsEquippedByMe(wo))
                return false;

            if (!string.IsNullOrEmpty(wo.Name) && wo.Name.IndexOf("Aetheria", StringComparison.OrdinalIgnoreCase) >= 0)
                return false;

            if (wo.Values(LongValueKey.EquipableSlots, 0) == 134217728)
                return false;

            if (wo.Values(LongValueKey.EquippedSlots, 0) == 8388608)
                return false;

            return true;
        }

        private bool ItemIsEquippedByMe(WorldObject wo)
        {
            int equippedSlots = wo.Values(LongValueKey.EquippedSlots, 0);
            if (equippedSlots <= 0)
                return false;

            if (wo.Values(LongValueKey.Slot, -1) == -1)
                return wo.Container == CoreManager.Current.CharacterFilter.Id;

            return true;
        }

        private static string ToWireState(EquipmentCantripItemState itemState)
        {
            switch (itemState)
            {
                case EquipmentCantripItemState.Active:
                    return "active";
                case EquipmentCantripItemState.NotActive:
                    return "not_active";
                case EquipmentCantripItemState.NotActivatable:
                    return "not_activatable";
                default:
                    return "unknown";
            }
        }

        public void Dispose()
        {
            Cleanup();
            _debounceTimer.Tick -= DebounceTimer_Tick;
            _debounceTimer.Dispose();
        }

        private enum EquipmentCantripItemState
        {
            Unknown,
            NotActivatable,
            Active,
            NotActive
        }
    }
}
