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

using Mag.Shared.Constants;

namespace MosswartMassacre
{
    public struct AllegianceInfoRecord
    {
        public string name;
        public int rank;
        public int race;
        public int gender;

        public AllegianceInfoRecord(string _name, int _rank, int _race, int _gender)
        {
            name = _name;
            rank = _rank;
            race = _race;
            gender = _gender;
        }
    }

    public static class CharacterStats
    {
        private static IPluginLogger _logger;

        // Cached allegiance data (populated from network messages)
        private static string allegianceName;
        private static int allegianceSize;
        private static int followers;
        private static AllegianceInfoRecord monarch;
        private static AllegianceInfoRecord patron;
        private static int allegianceRank;

        // Cached luminance data (populated from network messages)
        private static long luminanceEarned = -1;
        private static long luminanceTotal = -1;

        // Cached title data (populated from network messages)
        private static int currentTitle = -1;
        private static List<string> titlesList = new List<string>();

        // Cached DWORD properties (populated from 0x0013 message)
        private static Dictionary<int, int> characterProperties = new Dictionary<int, int>();

        // DWORD blacklist — exclude cosmetic/internal properties (same as TreeStats)
        private static readonly HashSet<int> dwordBlacklist = new HashSet<int> {
            2, 5, 7, 10, 17, 19, 20, 24, 25, 26, 28, 30, 33, 35, 36, 38, 43, 45,
            86, 87, 88, 89, 90, 91, 92, 98,
            105, 106, 107, 108, 109, 110, 111, 113, 114, 115, 117, 125, 129, 131, 134,
            158, 159, 160, 166, 170, 171, 172, 174, 175, 176, 177, 178, 179, 188, 193,
            270, 271, 272, 293
        };

        /// <summary>
        /// Reset all cached data. Call on plugin init.
        /// </summary>
        internal static void Init(IPluginLogger logger = null)
        {
            _logger = logger;
            allegianceName = null;
            allegianceSize = 0;
            followers = 0;
            monarch = new AllegianceInfoRecord();
            patron = new AllegianceInfoRecord();
            allegianceRank = 0;
            luminanceEarned = -1;
            luminanceTotal = -1;
            currentTitle = -1;
            titlesList.Clear();
            characterProperties.Clear();
        }

        /// <summary>
        /// Process game event 0x0020 - Allegiance info.
        /// Extracts monarch, patron, rank, followers from the allegiance tree.
        /// Reference: TreeStats Character.cs:642-745
        /// </summary>
        internal static void ProcessAllegianceInfoMessage(NetworkMessageEventArgs e)
        {
            try
            {
                allegianceName = e.Message.Value<string>("allegianceName");
                allegianceSize = e.Message.Value<Int32>("allegianceSize");
                followers = e.Message.Value<Int32>("followers");

                monarch = new AllegianceInfoRecord();
                patron = new AllegianceInfoRecord();

                MessageStruct records = e.Message.Struct("records");
                int currentId = CoreManager.Current.CharacterFilter.Id;
                var parentMap = new Dictionary<int, int>();
                var recordMap = new Dictionary<int, AllegianceInfoRecord>();

                for (int i = 0; i < records.Count; i++)
                {
                    var record = records.Struct(i);
                    int charId = record.Value<int>("character");
                    int treeParent = record.Value<int>("treeParent");

                    parentMap[charId] = treeParent;
                    recordMap[charId] = new AllegianceInfoRecord(
                        record.Value<string>("name"),
                        record.Value<int>("rank"),
                        record.Value<int>("race"),
                        record.Value<int>("gender"));

                    // Monarch: treeParent <= 1
                    if (treeParent <= 1)
                    {
                        monarch = recordMap[charId];
                    }
                }

                // Patron: parent of current character
                if (parentMap.ContainsKey(currentId) && recordMap.ContainsKey(parentMap[currentId]))
                {
                    patron = recordMap[parentMap[currentId]];
                }

                // Our rank from the record
                if (recordMap.ContainsKey(currentId))
                {
                    allegianceRank = recordMap[currentId].rank;
                }
            }
            catch (Exception ex)
            {
                _logger?.Log($"[CharStats] Allegiance processing error: {ex.Message}");
            }
        }

        /// <summary>
        /// Process game event 0x0013 - Character property data.
        /// Extracts luminance from QWORD keys 6 and 7.
        /// Reference: TreeStats Character.cs:582-640
        /// </summary>
        internal static void ProcessCharacterPropertyData(NetworkMessageEventArgs e)
        {
            try
            {
                MessageStruct props = e.Message.Struct("properties");
                MessageStruct qwords = props.Struct("qwords");

                for (int i = 0; i < qwords.Count; i++)
                {
                    var tmpStruct = qwords.Struct(i);
                    long key = tmpStruct.Value<Int64>("key");
                    long value = tmpStruct.Value<Int64>("value");

                    if (key == Constants.AvailableLuminanceKey)
                        luminanceEarned = value;
                    else if (key == Constants.MaximumLuminanceKey)
                        luminanceTotal = value;
                }

                // Parse DWORD properties (augmentations, ratings, masteries, society, etc.)
                MessageStruct dwords = props.Struct("dwords");
                characterProperties.Clear();
                for (int i = 0; i < dwords.Count; i++)
                {
                    var tmpStruct = dwords.Struct(i);
                    int key = tmpStruct.Value<Int32>("key");
                    int value = tmpStruct.Value<Int32>("value");
                    if (!dwordBlacklist.Contains(key))
                        characterProperties[key] = value;
                }
            }
            catch (Exception ex)
            {
                _logger?.Log($"[CharStats] Property processing error: {ex.Message}");
            }
        }

        /// <summary>
        /// Process message 0x02CF - PrivateUpdatePropertyInt64.
        /// Sent during gameplay when an Int64 property changes (e.g., luminance earned/spent).
        /// Wire format after 4-byte type header: sequence(1) + key(4) + value(8).
        /// Uses RawData since Decal's messages.xml may not define this message type.
        /// </summary>
        internal static void ProcessPropertyInt64Update(NetworkMessageEventArgs e)
        {
            try
            {
                byte[] raw = e.Message.RawData;
                if (raw.Length < 17) return; // 4 type + 1 seq + 4 key + 8 value

                int key = BitConverter.ToInt32(raw, 5);
                long value = BitConverter.ToInt64(raw, 9);

                if (key == Constants.AvailableLuminanceKey)
                    luminanceEarned = value;
                else if (key == Constants.MaximumLuminanceKey)
                    luminanceTotal = value;
            }
            catch (Exception ex)
            {
                _logger?.Log($"[CharStats] Int64 property update error: {ex.Message}");
            }
        }

        /// <summary>
        /// Process game event 0x0029 - Titles list.
        /// Extracts current title ID.
        /// Reference: TreeStats Character.cs:551-580
        /// </summary>
        internal static void ProcessTitlesMessage(NetworkMessageEventArgs e)
        {
            try
            {
                // Capture full titles list
                MessageStruct titles = e.Message.Struct("titles");
                titlesList.Clear();
                for (int i = 0; i < titles.Count; i++)
                {
                    titlesList.Add(titles.Struct(i).Value<string>("value"));
                }

                currentTitle = e.Message.Value<Int32>("current");
            }
            catch (Exception ex)
            {
                _logger?.Log($"[CharStats] Title processing error: {ex.Message}");
            }
        }

        /// <summary>
        /// Process game event 0x002b - Set title (when player changes title).
        /// </summary>
        internal static void ProcessSetTitleMessage(NetworkMessageEventArgs e)
        {
            try
            {
                currentTitle = e.Message.Value<Int32>("title");
            }
            catch (Exception ex)
            {
                _logger?.Log($"[CharStats] Set title error: {ex.Message}");
            }
        }

        /// <summary>
        /// Collect all character data and send via WebSocket.
        /// Called on login (after delay) and every 10 minutes.
        /// </summary>
        internal static void CollectAndSend()
        {
            if (!PluginCore.WebSocketEnabled)
                return;

            try
            {
                var cf = CoreManager.Current.CharacterFilter;
                var culture = new CultureInfo("en-US");

                // --- Attributes ---
                var attributes = new Dictionary<string, object>();
                foreach (var attr in cf.Attributes)
                {
                    attributes[attr.Name.ToLower()] = new
                    {
                        @base = attr.Base,
                        creation = attr.Creation
                    };
                }

                // --- Vitals (base values) ---
                var vitals = new Dictionary<string, object>();
                foreach (var vital in cf.Vitals)
                {
                    vitals[vital.Name.ToLower()] = new
                    {
                        @base = vital.Base
                    };
                }

                // --- Skills ---
                var skills = new Dictionary<string, object>();
                Decal.Filters.FileService fs = CoreManager.Current.FileService as Decal.Filters.FileService;
                if (fs != null)
                {
                    for (int i = 0; i < fs.SkillTable.Length; i++)
                    {
                        Decal.Interop.Filters.SkillInfo skillinfo = null;
                        try
                        {
                            skillinfo = cf.Underlying.get_Skill(
                                (Decal.Interop.Filters.eSkillID)fs.SkillTable[i].Id);

                            string name = skillinfo.Name.ToLower().Replace(" ", "_");
                            string training = skillinfo.Training.ToString();
                            // Training enum returns "eTrainSpecialized" etc, strip "eTrain" prefix
                            if (training.Length > 6)
                                training = training.Substring(6);

                            skills[name] = new
                            {
                                @base = skillinfo.Base,
                                training = training
                            };
                        }
                        finally
                        {
                            if (skillinfo != null)
                            {
                                Marshal.ReleaseComObject(skillinfo);
                                skillinfo = null;
                            }
                        }
                    }
                }

                // --- Allegiance ---
                object allegiance = null;
                if (allegianceName != null)
                {
                    allegiance = new
                    {
                        name = allegianceName,
                        monarch = monarch.name != null ? new
                        {
                            name = monarch.name,
                            race = monarch.race,
                            rank = monarch.rank,
                            gender = monarch.gender
                        } : null,
                        patron = patron.name != null ? new
                        {
                            name = patron.name,
                            race = patron.race,
                            rank = patron.rank,
                            gender = patron.gender
                        } : null,
                        rank = allegianceRank,
                        followers = followers
                    };
                }

                // --- Build payload ---
                int effectiveStrength = cf.EffectiveAttribute[CharFilterAttributeType.Strength];
                int carryingCapacityAugs = cf.GetCharProperty((int)IntValueKey.AugmentationIncreasedCarryingCapacity);
                int encumbranceCapacity = (150 * effectiveStrength) + (carryingCapacityAugs * 30 * effectiveStrength);

                var payload = new
                {
                    type = "character_stats",
                    timestamp = DateTime.UtcNow.ToString("o"),
                    character_name = cf.Name,
                    level = cf.Level,
                    race = cf.Race,
                    gender = cf.Gender,
                    birth = cf.Birth.ToString(culture),
                    total_xp = cf.TotalXP,
                    unassigned_xp = cf.UnassignedXP,
                    skill_credits = cf.SkillPoints,
                    deaths = cf.Deaths,
                    burden = cf.Burden,
                    burden_units = cf.BurdenUnits,
                    encumbrance_capacity = encumbranceCapacity,
                    luminance_earned = luminanceEarned >= 0 ? (long?)luminanceEarned : null,
                    luminance_total = luminanceTotal >= 0 ? (long?)luminanceTotal : null,
                    current_title = currentTitle >= 0 ? (int?)currentTitle : null,
                    attributes = attributes,
                    vitals = vitals,
                    skills = skills,
                    allegiance = allegiance,
                    properties = characterProperties.Count > 0 ? new Dictionary<int, int>(characterProperties) : null,
                    titles = titlesList.Count > 0 ? new List<string>(titlesList) : null
                };

                _ = WebSocket.SendCharacterStatsAsync(payload);
            }
            catch (Exception ex)
            {
                _logger?.Log($"[CharStats] Error collecting stats: {ex.Message}");
            }
        }
    }
}
