using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using PhoenixLib.Logging;
using WingsAPI.Data.Drops;
using WingsAPI.Data.GameData;
using WingsEmu.DTOs.BCards;
using WingsEmu.DTOs.NpcMonster;
using WingsEmu.DTOs.Skills;
using WingsEmu.Game._enum;
using WingsEmu.Game.Algorithm;
using WingsEmu.Packets.Enums.Battle;

namespace Plugin.ResourceLoader.Loaders
{
    public class NpcMonsterFileLoader : IResourceLoader<NpcMonsterDto>
    {
        private readonly IBattleEntityAlgorithmService _algorithm;
        private readonly ResourceLoadingConfiguration _config;
        private readonly ILogger<NpcMonsterFileLoader> _logger;
        private readonly List<NpcMonsterDto> _npcMonsters = new();

        public NpcMonsterFileLoader(ResourceLoadingConfiguration config, ILogger<NpcMonsterFileLoader> logger, IBattleEntityAlgorithmService algorithm)
        {
            _config = config;
            _logger = logger;
            _algorithm = algorithm;
        }

        public async Task<IReadOnlyList<NpcMonsterDto>> LoadAsync()
        {
            if (_npcMonsters.Any())
            {
                return _npcMonsters;
            }

            string filePath = Path.Combine(_config.GameDataPath, "monster.dat");

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException($"{filePath} should be present");
            }

            var npc = new NpcMonsterDto();

            bool itemAreaBegin = false;
            int counter = 0;

            using var npcIdStream = new StreamReader(filePath, Encoding.GetEncoding(1252));
            string line;
            while ((line = await npcIdStream.ReadLineAsync()) != null)
            {
                string[] currentLine = line.Split('\t');

                switch (currentLine.Length)
                {
                    case > 2 when currentLine[1] == "VNUM":
                        npc = new NpcMonsterDto
                        {
                            Id = Convert.ToInt16(currentLine[2])
                        };
                        itemAreaBegin = true;
                        break;
                    case > 2 when currentLine[1] == "NAME":
                        npc.Name = currentLine[2];
                        break;
                    case > 2 when currentLine[1] == "LEVEL":
                    {
                        if (!itemAreaBegin)
                        {
                            continue;
                        }

                        npc.Level = Convert.ToByte(currentLine[2]);
                        break;
                    }
                    case > 3 when currentLine[1] == "RACE":
                        npc.Race = Convert.ToByte(currentLine[2]);
                        npc.RaceType = Convert.ToByte(currentLine[3]);
                        break;
                    case > 7 when currentLine[1] == "ATTRIB":
                        npc.Element = Convert.ToByte(currentLine[2]);
                        npc.ElementRate = Convert.ToInt16(currentLine[3]);
                        npc.FireResistance = Convert.ToInt16(currentLine[4]);
                        npc.WaterResistance = Convert.ToInt16(currentLine[5]);
                        npc.LightResistance = Convert.ToInt16(currentLine[6]);
                        npc.DarkResistance = Convert.ToInt16(currentLine[7]);
                        break;
                    case > 2 when currentLine[1] == "EXP":
                    {
                        npc.BaseXp = Convert.ToInt32(currentLine[2]);
                        npc.BaseJobXp = Convert.ToInt32(currentLine[3]);

                        npc.Xp = npc.Level < 20 ? 60 * npc.Level + Convert.ToInt32(currentLine[2]) : 70 * npc.Level + Convert.ToInt32(currentLine[2]);
                        npc.JobXp = npc.Level > 60 ? 105 + Convert.ToInt32(currentLine[3]) : 120 + Convert.ToInt32(currentLine[3]);

                        if (npc.Xp < 0)
                        {
                            npc.Xp = 0;
                        }

                        if (npc.JobXp < 0)
                        {
                            npc.JobXp = 0;
                        }

                        break;
                    }
                    case > 6 when currentLine[1] == "PREATT":
                        npc.HostilityType = Convert.ToInt32(currentLine[2]);
                        npc.GroupAttack = Convert.ToInt32(currentLine[3]);
                        npc.NoticeRange = Convert.ToByte(currentLine[4]);
                        npc.Speed = Convert.ToByte(currentLine[5]);
                        npc.RespawnTime = Convert.ToInt32(currentLine[6]);
                        break;
                    case > 4 when currentLine[1] == "WINFO":
                        npc.WinfoValue = Convert.ToByte(currentLine[3]);
                        npc.AttackUpgrade = Convert.ToByte(currentLine[4]);
                        break;
                    case > 3 when currentLine[1] == "AINFO":
                        npc.DefenceUpgrade = Convert.ToByte(currentLine[3]);
                        break;
                    case > 4 when currentLine[1] == "PETINFO":
                    {
                        if (npc.Race == 8)
                        {
                            switch (npc.RaceType)
                            {
                                case 7: //collectable NPC
                                    npc.MaxTries = Convert.ToByte(currentLine[2]);
                                    npc.CollectionCooldown = Convert.ToInt16(currentLine[3]);
                                    npc.AmountRequired = Convert.ToInt16(currentLine[4]);
                                    npc.CollectionDanceTime = Convert.ToByte(currentLine[5]);
                                    break;
                                case 5: //teleporters
                                    npc.VNumRequired = Convert.ToInt16(currentLine[2]);
                                    npc.AmountRequired = Convert.ToInt16(currentLine[3]);
                                    npc.TeleportRemoveFromInventory = currentLine[4] != "0";
                                    break;
                            }
                        }
                        else
                        {
                            npc.MeleeHpFactor = Convert.ToInt16(currentLine[2]);
                            npc.RangeDodgeFactor = Convert.ToInt16(currentLine[3]);
                            npc.MagicMpFactor = Convert.ToInt16(currentLine[4]);
                        }

                        break;
                    }
                    case > 3 when currentLine[1] == "HP/MP":
                        npc.CleanHp = Convert.ToInt32(currentLine[2]);
                        npc.CleanMp = Convert.ToInt32(currentLine[3]);

                        npc.MaxHp = _algorithm.GetBasicHp(npc.Race, npc.Level, npc.MeleeHpFactor, Convert.ToInt32(currentLine[2]));
                        npc.MaxMp = _algorithm.GetBasicMp(npc.Race, npc.Level, npc.MagicMpFactor, Convert.ToInt32(currentLine[3]));
                        break;
                    case > 6 when currentLine[1] == "WEAPON":
                        npc.WeaponLevel = Convert.ToByte(currentLine[2]);
                        npc.CleanDamageMin = Convert.ToInt32(currentLine[4]);
                        npc.CleanDamageMax = Convert.ToInt32(currentLine[5]);
                        npc.CleanHitRate = Convert.ToInt32(currentLine[6]);

                        npc.DamageMinimum = _algorithm.GetAttack(true, npc.Race, npc.AttackType, npc.WeaponLevel, npc.WinfoValue, npc.Level, GetModifier(npc),
                            Convert.ToInt16(currentLine[4]));
                        npc.DamageMaximum = _algorithm.GetAttack(false, npc.Race, npc.AttackType, npc.WeaponLevel, npc.WinfoValue, npc.Level, GetModifier(npc),
                            Convert.ToInt16(currentLine[5]));
                        npc.Concentrate = (short)_algorithm.GetHitrate(npc.Race, npc.AttackType, npc.WeaponLevel, npc.Level, GetModifier(npc),
                            Convert.ToInt16(currentLine[6]));
                        npc.CriticalChance = (short)(Convert.ToInt16(currentLine[7]) + 4);
                        npc.CriticalRate = (short)(Convert.ToInt16(currentLine[8]) + 70);
                        break;
                    case > 6 when currentLine[1] == "ARMOR":
                        npc.ArmorLevel = Convert.ToByte(currentLine[2]);
                        npc.CleanMeleeDefence = Convert.ToInt32(currentLine[3]);
                        npc.CleanRangeDefence = Convert.ToInt32(currentLine[4]);
                        npc.CleanMagicDefence = Convert.ToInt32(currentLine[5]);
                        npc.CleanDodge = Convert.ToInt32(currentLine[6]);

                        npc.CloseDefence = (short)_algorithm.GetDefense(npc.Race, AttackType.Melee, npc.ArmorLevel, npc.Level, GetModifier(npc), Convert.ToInt16(currentLine[3]));
                        npc.DistanceDefence = (short)_algorithm.GetDefense(npc.Race, AttackType.Ranged, npc.ArmorLevel, npc.Level, GetModifier(npc), Convert.ToInt16(currentLine[4]));
                        npc.MagicDefence = (short)_algorithm.GetDefense(npc.Race, AttackType.Magical, npc.ArmorLevel, npc.Level, GetModifier(npc), Convert.ToInt16(currentLine[5]));
                        npc.DefenceDodge = (short)_algorithm.GetDodge(npc.Race, npc.ArmorLevel, npc.Level, GetModifier(npc), Convert.ToInt16(currentLine[6]));
                        npc.DistanceDefenceDodge = (short)_algorithm.GetDodge(npc.Race, npc.ArmorLevel, npc.Level, GetModifier(npc), Convert.ToInt16(currentLine[6]));
                        break;
                    case > 7 when currentLine[1] == "ETC":
                    {
                        long bitFlag = Convert.ToInt64(currentLine[2]);

                        npc.CanWalk = Convert.ToBoolean(bitFlag & (long)MobFlag.CANT_WALK) == false;
                        npc.CanBeCollected = Convert.ToBoolean(bitFlag & (long)MobFlag.CAN_BE_COLLECTED);
                        npc.CanBeDebuffed = Convert.ToBoolean(bitFlag & (long)MobFlag.CANT_BE_DEBUFFED) == false;
                        npc.CanBeCaught = Convert.ToBoolean(bitFlag & (long)MobFlag.CAN_BE_CAUGHT);
                        npc.DisappearAfterSeconds = Convert.ToBoolean(bitFlag & (long)MobFlag.DISSAPPEAR_AFTER_SECONDS);
                        npc.DisappearAfterHitting = Convert.ToBoolean(bitFlag & (long)MobFlag.DISSAPPEAR_AFTER_HITTING);
                        npc.HasMode = Convert.ToBoolean(bitFlag & (long)MobFlag.HAS_MODE);
                        npc.DisappearAfterSecondsMana = Convert.ToBoolean(bitFlag & (long)MobFlag.DISSAPPEAR_AFTER_SECONDS_MANA);
                        npc.OnDefenseOnlyOnce = Convert.ToBoolean(bitFlag & (long)MobFlag.ON_DEFENSE_ONLY_ONCE);
                        npc.HasDash = Convert.ToBoolean(bitFlag & (long)MobFlag.HAS_DASH);
                        npc.CanRegenMp = Convert.ToBoolean(bitFlag & (long)MobFlag.CAN_REGEN_MP);
                        npc.CanBePushed = Convert.ToBoolean(bitFlag & (long)MobFlag.CAN_BE_PUSHED) == false;

                        npc.IsPercent = currentLine[4] == "1";
                        npc.DamagedOnlyLastJajamaruSkill = currentLine[5] == "1";
                        npc.DropToInventory = currentLine[7] == "1";
                        break;
                    }
                    case > 6 when currentLine[1] == "SETTING":
                    {
                        npc.IconId = Convert.ToInt32(currentLine[2]);
                        npc.SpawnMobOrColor = Convert.ToInt32(currentLine[3]);
                        npc.SpriteSize = Convert.ToInt32(currentLine[5]);
                        npc.CellSize = Convert.ToInt32(currentLine[6]);

                        if (npc.Race == 8 && (npc.RaceType == 7 || npc.RaceType == 6))
                        {
                            npc.VNumRequired = Convert.ToInt16(currentLine[4]);
                        }

                        break;
                    }
                    case > 2 when currentLine[1] == "EFF":
                        npc.AttackEffect = Convert.ToInt16(currentLine[2]);
                        npc.PermanentEffect = Convert.ToInt16(currentLine[3]);
                        npc.DeathEffect = Convert.ToInt16(currentLine[4]);
                        break;
                    case > 8 when currentLine[1] == "ZSKILL":
                        npc.AttackType = (AttackType)Convert.ToByte(currentLine[2]);
                        npc.BasicRange = Convert.ToByte(currentLine[3]);
                        npc.BasicHitChance = Convert.ToByte(currentLine[4]);
                        npc.BasicCastTime = Convert.ToByte(currentLine[5]);
                        npc.BasicCooldown = Convert.ToInt16(currentLine[6]);
                        npc.BasicDashSpeed = Convert.ToInt16(currentLine[7]);
                        break;
                    case > 1 when currentLine[1] == "SKILL":
                    {
                        for (int i = 2; i < currentLine.Length - 3; i += 3)
                        {
                            short vnum = short.Parse(currentLine[i]);
                            if (vnum is -1 or 0)
                            {
                                continue;
                            }

                            npc.Skills.Add(new NpcMonsterSkillDTO
                            {
                                SkillVNum = vnum,
                                Rate = Convert.ToInt16(currentLine[i + 1]),
                                NpcMonsterVNum = npc.Id,
                                IsBasicAttack = currentLine[i + 2] == "2",
                                IsIgnoringHitChance = currentLine[i + 2] == "1"
                            });
                        }

                        break;
                    }
                    case > 1 when currentLine[1] == "MODE":
                    {
                        for (int i = 0; i < 5; i++)
                        {
                            byte type = (byte)int.Parse(currentLine[5 * i + 2]);
                            if (type == 0)
                            {
                                continue;
                            }

                            int first = int.Parse(currentLine[3 + 5 * i]);
                            int second = int.Parse(currentLine[4 + 5 * i]);

                            int firstModulo = first % 4;
                            firstModulo = firstModulo switch
                            {
                                -1 => 1,
                                -2 => 2,
                                -3 => 1,
                                _ => firstModulo
                            };

                            int secondModulo = second % 4;
                            secondModulo = secondModulo switch
                            {
                                -1 => 1,
                                -2 => 2,
                                -3 => 1,
                                _ => secondModulo
                            };

                            var modeBCard = new BCardDTO
                            {
                                NpcMonsterVNum = npc.Id,
                                Type = type,
                                SubType = (byte)((int.Parse(currentLine[5 + 5 * i]) + 1) * 10 + 1 + (first >= 0 ? 0 : 1)),
                                FirstDataScalingType = (BCardScalingType)firstModulo,
                                SecondDataScalingType = (BCardScalingType)secondModulo,
                                FirstData = (int)Math.Abs(Math.Floor(first / 4.0)),
                                SecondData = (int)Math.Abs(Math.Floor(second / 4.0)),
                                CastType = byte.Parse(currentLine[6 + 5 * i]),
                                IsMonsterMode = true
                            };

                            npc.ModeBCards.Add(modeBCard);
                        }

                        npc.ModeIsHpTriggered = currentLine[27] == "0";
                        npc.ModeLimiterType = Convert.ToByte(currentLine[28]);
                        npc.ModeHpTresholdOrItemVnum = Convert.ToInt16(currentLine[29]);
                        npc.ModeRangeTreshold = Convert.ToInt16(currentLine[30]);
                        npc.ModeCModeVnum = Convert.ToInt16(currentLine[31]);

                        npc.MinimumAttackRange = sbyte.Parse(currentLine[32]);
                        npc.MidgardDamage = Convert.ToInt16(currentLine[33]);
                        break;
                    }
                    case > 1 when currentLine[1] == "CARD":
                    {
                        for (int i = 0; i < 4; i++)
                        {
                            byte type = (byte)int.Parse(currentLine[2 + 5 * i]);
                            if (type is 0 or 255)
                            {
                                continue;
                            }

                            int first = int.Parse(currentLine[3 + 5 * i]);
                            int second = int.Parse(currentLine[4 + 5 * i]);

                            int firstModulo = first % 4;
                            firstModulo = firstModulo switch
                            {
                                -1 => 1,
                                -2 => 2,
                                -3 => 1,
                                _ => firstModulo
                            };

                            int secondModulo = second % 4;
                            secondModulo = secondModulo switch
                            {
                                -1 => 1,
                                -2 => 2,
                                -3 => 1,
                                _ => secondModulo
                            };

                            var itemCard = new BCardDTO
                            {
                                NpcMonsterVNum = npc.Id,
                                Type = type,
                                SubType = (byte)((int.Parse(currentLine[5 + 5 * i]) + 1) * 10 + 1 + (first >= 0 ? 0 : 1)),
                                FirstDataScalingType = (BCardScalingType)firstModulo,
                                SecondDataScalingType = (BCardScalingType)secondModulo,
                                FirstData = (int)Math.Abs(Math.Floor(first / 4.0)),
                                SecondData = (int)Math.Abs(Math.Floor(second / 4.0)),
                                CastType = byte.Parse(currentLine[6 + 5 * i]),
                                TriggerType = i switch
                                {
                                    0 => BCardNpcMonsterTriggerType.ON_FIRST_ATTACK,
                                    1 => BCardNpcMonsterTriggerType.ON_DEATH,
                                    _ => BCardNpcMonsterTriggerType.ON_DEATH // Custom
                                }
                            };

                            npc.BCards.Add(itemCard);
                        }

                        break;
                    }
                    case > 1 when currentLine[1] == "BASIC":
                    {
                        for (int i = 0; i < 10; i++)
                        {
                            byte type = (byte)int.Parse(currentLine[5 * i + 2]);
                            if (type == 0)
                            {
                                continue;
                            }

                            int first = int.Parse(currentLine[3 + 5 * i]);
                            int second = int.Parse(currentLine[4 + 5 * i]);

                            int firstModulo = first % 4;
                            firstModulo = firstModulo switch
                            {
                                -1 => 1,
                                -2 => 2,
                                -3 => 1,
                                _ => firstModulo
                            };

                            int secondModulo = second % 4;
                            secondModulo = secondModulo switch
                            {
                                -1 => 1,
                                -2 => 2,
                                -3 => 1,
                                _ => secondModulo
                            };

                            var itemCard = new BCardDTO
                            {
                                NpcMonsterVNum = npc.Id,
                                Type = type,
                                SubType = (byte)((int.Parse(currentLine[5 + 5 * i]) + 1) * 10 + 1 + (first >= 0 ? 0 : 1)),
                                FirstDataScalingType = (BCardScalingType)firstModulo,
                                SecondDataScalingType = (BCardScalingType)secondModulo,
                                FirstData = (int)Math.Abs(Math.Floor(first / 4.0)),
                                SecondData = (int)Math.Abs(Math.Floor(second / 4.0)),
                                CastType = byte.Parse(currentLine[6 + 5 * i]),
                                NpcTriggerType = (i % 2) switch
                                {
                                    0 => BCardNpcTriggerType.ON_ATTACK,
                                    1 => BCardNpcTriggerType.ON_DEFENSE,
                                    _ => null
                                }
                            };

                            npc.BCards.Add(itemCard);
                        }

                        break;
                    }
                    case > 3 when currentLine[1] == "ITEM":
                    {
                        _npcMonsters.Add(npc);
                        counter++;

                        for (int i = 2; i < currentLine.Length - 3; i += 3)
                        {
                            short vnum = Convert.ToInt16(currentLine[i]);
                            if (vnum is -1 or 0)
                            {
                                continue;
                            }

                            npc.Drops ??= new List<DropDTO>();

                            // add to monster vnum
                            npc.Drops.Add(new DropDTO
                            {
                                ItemVNum = vnum,
                                Amount = Convert.ToInt32(currentLine[i + 2]),
                                MonsterVNum = npc.Id,
                                DropChance = Convert.ToInt32(currentLine[i + 1])
                            });
                        }

                        itemAreaBegin = false;
                        break;
                    }
                }
            }

            Log.Info($"[RESOURCE_LOADER] {counter.ToString()} Monster Data loaded");
            return _npcMonsters;
        }

        private static int GetModifier(NpcMonsterDto npc)
        {
            return npc.AttackType switch
            {
                AttackType.Melee => npc.MeleeHpFactor,
                AttackType.Ranged => npc.RangeDodgeFactor,
                AttackType.Magical => npc.MagicMpFactor
            };
        }

        private enum MobFlag : long
        {
            CANT_WALK = 1,
            CAN_BE_COLLECTED = 2,
            CANT_BE_DEBUFFED = 4,
            CAN_BE_CAUGHT = 8,
            DISSAPPEAR_AFTER_SECONDS = 16,
            DISSAPPEAR_AFTER_HITTING = 32,
            HAS_MODE = 64,
            DISSAPPEAR_AFTER_SECONDS_MANA = 128,
            ON_DEFENSE_ONLY_ONCE = 256,
            HAS_DASH = 512,
            CAN_REGEN_MP = 1024,
            CAN_BE_PUSHED = 2048
        }
    }
}