using Campofinale.Game.Inventory;
using Campofinale.Packets.Sc;
using Campofinale.Protocol;
using Campofinale.Resource;
using Google.Protobuf.Collections;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.IdGenerators;
using static Campofinale.Resource.ResourceManager;
using static Campofinale.Resource.ResourceManager.CharGrowthTable;
using static Campofinale.Resource.ResourceManager.WeaponUpgradeTemplateTable;

namespace Campofinale.Game.Char
{
    public class Character
    {
        [BsonId(IdGenerator = typeof(ObjectIdGenerator))]
        public ObjectId _id { get; set; }
        [BsonElement("templateId")]
        public string id;
        public ulong guid;
        public ulong weaponGuid;

        public int level;
        public int xp;
        public ulong owner;
        public double curHp;
        public float ultimateSp = 200;
        public uint potential = 0;
        public string breakNode = "charBreak20";
        public List<string> passiveSkillNodes = new();
        public List<string> attrNodes = new();
        public List<string> factoryNodes = new();
        public Dictionary<int,ulong> equipCol = new() { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 } };
        public Character()
        {
           
        }
        
        public Character(ulong owner, string id) : this(owner, id, 1)
        {

        }
        public Dictionary<AttributeType, (double baseVal, double val)> CalcAttributes()
        {
            Dictionary<AttributeType, (double baseVal, double val)> attributes = new();
            foreach (var item in GetAttributes())
            {
                attributes.Add((AttributeType)item.attrType, (item.attrValue, item.attrValue));
            }
            Item weapon = GetOwner().inventoryManager.items.Find(w => w.guid == weaponGuid);
            if(weapon != null)
            {
                WeaponBasicTable wTable = ResourceManager.weaponBasicTable[weapon.id];
                WeaponUpgradeTemplateTable template = ResourceManager.weaponUpgradeTemplateTable[wTable.levelTemplateId];
                WeaponCurve curve=template.list.Find(c => c.weaponLv == weapon.level);
                attributes[AttributeType.Atk] = (attributes[AttributeType.Atk].baseVal + curve.baseAtk, attributes[AttributeType.Atk].baseVal + curve.baseAtk);

            }
            
            //Won't be very precise but for now
            foreach (var equip in equipCol)
            {
                Item EquipItem = GetOwner().inventoryManager.items.Find(e => e.guid == equip.Value);
                if (EquipItem != null)
                {
                    foreach (var modifier in EquipItem.GetEquipAttributeModifier())
                    {
                        switch (modifier.modifierType)
                        {
                            case ModifierType.BaseAddition:
                            case ModifierType.Addition:
                                attributes=SetValueDic(attributes, modifier.attrType, GetValueDic(attributes, modifier.attrType) + modifier.attrValue);
                                break;
                            case ModifierType.Multiplier:
                            case ModifierType.BaseMultiplier:
                                attributes=SetValueDic(attributes, modifier.attrType, GetValueDic(attributes, modifier.attrType) * 1 + modifier.attrValue);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            attributes[AttributeType.MaxHp] = (attributes[AttributeType.MaxHp].baseVal, attributes[AttributeType.MaxHp].val + attributes[AttributeType.Str].val * 10);
            return attributes;
        }
        public double GetValueDic(Dictionary<AttributeType, (double baseVal, double val)> dic, AttributeType type)
        {

            if (dic.ContainsKey(type))
            {
                return dic[type].val;
            }
            return 0;
        }
        public Dictionary<AttributeType, (double baseVal, double val)> SetValueDic(Dictionary<AttributeType, (double baseVal, double val)> dic,AttributeType type,double value)
        {
            if (dic.ContainsKey(type))
            {
                dic[type] = (dic[type].baseVal,value);
            }
            else
            {
                dic.Add(type, (0, value));
            }
            return dic;
        }
        public void UnlockNode(string nodeId)
        {
            CharTalentNode nodeInfo = ResourceManager.GetTalentNode(id, nodeId);
            if (nodeInfo == null) return;
            //TODO remove cost items
            switch (nodeInfo.nodeType)
            {
                case TalentNodeType.CharBreak:
                    breakNode = nodeId;
                    break;
                case TalentNodeType.EquipBreak:
                    breakNode = nodeId;
                    break;
                case TalentNodeType.Attr:
                    attrNodes.Add(nodeId);
                    break;
                case TalentNodeType.PassiveSkill:
                    passiveSkillNodes.Add(nodeId);
                    break;
                case TalentNodeType.FactorySkill:
                    factoryNodes.Add(nodeId);
                    break;
                default:
                    Logger.PrintWarn($"Unimplemented NodeType {nodeInfo.nodeType}, not unlocked server side.");
                    break;
            }
            GetOwner().Send(new PacketScCharUnlockTalentNode(GetOwner(), this,nodeId));
        }
        public Character(ulong owner,string id, int level) : this()
        {
            this.owner = owner;
            this.id = id;
            this.level = level;
            guid = GetOwner().random.Next();
            this.weaponGuid = GetOwner().inventoryManager.AddWeapon(ResourceManager.charGrowthTable[id].defaultWeaponId, 1).guid;
            this.curHp = CalcAttributes()[AttributeType.MaxHp].val;
            if (level < 20) breakNode = "";
            if (level >= 20 && level <= 40) breakNode = "charBreak20";
            if (level > 40 && level <= 60) breakNode = "charBreak40";
            if (level > 60 && level <= 70) breakNode = "charBreak60";
            if (level > 70) breakNode = "charBreak70";
        }
        public int GetSkillMaxLevel()
        {
            if (GetBreakStage() == 0)
            {
                return 1;
            }else if (GetBreakStage() == 1)
            {
                return 3;
            }
            else if (GetBreakStage() == 2)
            {
                return 6;
            }
            else if (GetBreakStage() == 3)
            {
                return 9;
            }
            else if (GetBreakStage() == 4)
            {
                return 12;
            }
            else
            {
                return 12;
            }
        }
        public List<ResourceManager.Attribute> GetAttributes()
        {
            int lev = level - 1 + GetBreakStage();
            return ResourceManager.characterTable[id].attributes[lev].Attribute.attrs;
        }
        public Player GetOwner()
        {
            return Server.clients.Find(c=>c.roleId == this.owner); 
        }
        public SceneCharacter ToSceneProto()
        {
            SceneCharacter proto= new SceneCharacter()
            {
                Level = level,
                
                BattleInfo = new()
                {
                    
                    MsgGeneration = 1,
                    
                    SkillList =
                                {
                                    new ServerSkill()
                                    {
                                        Blackboard = new()
                                        {

                                        },
                                        InstId=GetOwner().random.Next(),
                                        Level=GetSkillMaxLevel(),
                                        Source=BattleSkillSource.Default,
                                        PotentialLv=GetSkillMaxLevel(),
                                        SkillId=id+"_NormalSkill",
                                    },
                                    new ServerSkill()
                                    {
                                        Blackboard = new()
                                        {

                                        },
                                        InstId=GetOwner().random.Next(),
                                        Level=GetSkillMaxLevel(),
                                        Source=BattleSkillSource.Default,
                                        PotentialLv=GetSkillMaxLevel(),
                                        SkillId=id+"_ComboSkill",
                                    },
                                    new ServerSkill()
                                    {
                                        Blackboard = new()
                                        {

                                        },
                                        InstId=GetOwner().random.Next(),
                                        Level=GetSkillMaxLevel(),
                                        Source=BattleSkillSource.Default,
                                        PotentialLv=GetSkillMaxLevel(),
                                        SkillId=id+"_UltimateSkill",
                                    },
                                    new ServerSkill()
                                    {
                                        Blackboard = new()
                                        {

                                        },
                                        InstId=GetOwner().random.Next(),
                                        Level=GetSkillMaxLevel(),
                                        Source=BattleSkillSource.Default,
                                        PotentialLv=GetSkillMaxLevel(),
                                        SkillId=id+"_NormalAttack",
                                    }
                                }
                },

                Name = $"{ResourceManager.characterTable[id].engName}",
                
                CommonInfo = new()
                {
                    Hp = curHp,
                    Id = guid,
                    Position = GetOwner().position.ToProto(),
                    Rotation = GetOwner().rotation.ToProto(),
                    SceneNumId = GetOwner().curSceneNumId,
                    Templateid = id,
                    Type = (int)0,
                    
                },
                Attrs =
                {
                    
                }
            };
            foreach(var attr in CalcAttributes())
            {
                proto.Attrs.Add(new AttrInfo()
                {
                    AttrType = (int)attr.Key,
                    BasicValue = attr.Value.baseVal,
                    Value = attr.Value.val
                    
                });
            }
            return proto;
        }
        public int GetBreakStage()
        {
            if (ResourceManager.charBreakNodeTable.ContainsKey(breakNode))
            {
                int breakStage = ResourceManager.charBreakNodeTable[breakNode].breakStage;
                return breakStage;
            }
            else
            {
                return 0;
            }

        }
        public bool IsEquipped(ulong equipGuid)
        {
            return equipCol.Values.Contains(equipGuid);
        }
        public Dictionary<int,ulong> GetEquipCol()
        {
            Dictionary<int, ulong> equips = new();
            foreach(var item in equipCol)
            {
                if (item.Value != 0)
                {
                    equips.Add(item.Key,item.Value);
                }
            }
            return equips;
        }
        public CharInfo ToProto()
        {
            CharInfo info = new CharInfo()
            {
                Exp = xp,
                Level = level,
                IsDead = curHp < 1,
                
                Objid = guid,
                Templateid = id,
                CharType = CharType.DefaultType,
                OwnTime = 1,
                NormalSkill = id + "_NormalSkill",
                WeaponId = weaponGuid,
                PotentialLevel = potential,
                EquipCol =
                {
                    GetEquipCol()
                },
                
                Talent = new()
                {
                    LatestBreakNode= breakNode,
                    LatestPassiveSkillNodes =
                    {
                        passiveSkillNodes
                    },
                    AttrNodes =
                    {
                        attrNodes
                    },
                    
                    LatestFactorySkillNodes =
                    {
                        factoryNodes
                    }
                },
                BattleMgrInfo = new()
                {
                    
                },
                BattleInfo = new()
                {
                    Hp = curHp,
                    Ultimatesp= ultimateSp,
                    
                },
                SkillInfo = new()
                {
                    
                    NormalSkill = id + "_NormalSkill",
                    ComboSkill = id + "_ComboSkill",
                    UltimateSkill = id + "_UltimateSkill",
                    DispNormalAttackSkill = id + "_NormalAttack",
                    
                    LevelInfo =
                    {
                        new SkillLevelInfo()
                        {
                            SkillId=id+"_NormalAttack",
                            SkillLevel=GetSkillMaxLevel(),
                            SkillMaxLevel=GetSkillMaxLevel(),
                            SkillEnhancedLevel=GetSkillMaxLevel()
                        },
                        new SkillLevelInfo()
                        {
                            SkillId=id+"_NormalSkill",
                            SkillLevel=GetSkillMaxLevel(),
                            SkillMaxLevel=GetSkillMaxLevel(),
                            SkillEnhancedLevel=GetSkillMaxLevel()
                        },
                        new SkillLevelInfo()
                        {
                            SkillId=id+"_UltimateSkill",
                            SkillLevel=GetSkillMaxLevel(),
                            SkillMaxLevel=GetSkillMaxLevel(),
                            SkillEnhancedLevel=GetSkillMaxLevel()
                        },
                        new SkillLevelInfo()
                        {
                            SkillId=id+"_ComboSkill",
                            SkillLevel=GetSkillMaxLevel(),
                            SkillMaxLevel=GetSkillMaxLevel(),
                            SkillEnhancedLevel=GetSkillMaxLevel()
                        },

                    }
                }
            };
            Item wep = GetOwner().inventoryManager.items.Find(w => w.guid == weaponGuid);
            if (wep != null)
            {
                //TODO weapon skills
            }
            foreach (ulong equipGuid in equipCol.Values)
            {
                Item item = GetOwner().inventoryManager.items.Find(i => i.guid == equipGuid);
                if (item != null)
                {
                    string equipSuitId = ResourceManager.GetEquipSuitTableKey(item.id);
                    if (equipSuitId.Length > 0)
                    {
                        if (info.EquipSuit.ContainsKey(equipSuitId))
                        {
                            info.EquipSuit[equipSuitId] += 1;
                        }
                        else
                        {
                            info.EquipSuit.Add(equipSuitId, 1);
                        }
                    }

                }
            }
            return info;
        }
        public (int,int,int) CalculateLevelAndGoldCost(int addedXp)
        {
            int gold = 0;
            int curLevel = this.level;
            while(addedXp >= ResourceManager.charLevelUpTable["" + curLevel].exp)
            {
                gold += ResourceManager.charLevelUpTable["" + curLevel].gold;
                addedXp -= ResourceManager.charLevelUpTable["" + curLevel].exp;
                curLevel++;
                if(curLevel >= 80)
                {
                    curLevel = 80;
                }
            }
            return (curLevel, gold, addedXp);
        }
        public void LevelUp(RepeatedField<ItemInfo> items)
        {
            int addedXp = 0;
            foreach (var item in items)
            {
                addedXp += ResourceManager.expItemDataMap[item.ResId].expGain * item.ResCount;
            }

            (int, int, int) CalculatedValues = CalculateLevelAndGoldCost(xp+addedXp);
            items.Add(new ItemInfo()
            {
                ResId = "item_gold",
                ResCount = CalculatedValues.Item2
            });
            if (GetOwner().inventoryManager.ConsumeItems(items))
            {
                this.level = CalculatedValues.Item1;
                this.xp= CalculatedValues.Item3;
                ScCharLevelUp levelUp = new()
                {
                    CharObjID = guid,
                };
                ScCharSyncLevelExp synclevel = new()
                {
                    Exp = xp,
                    CharObjID = guid,
                    Level = level
                };
                GetOwner().Send(ScMsgId.ScCharSyncLevelExp, synclevel);
                GetOwner().Send(ScMsgId.ScCharLevelUp, levelUp);
                GetOwner().Send(new PacketScSyncWallet(GetOwner()));
                GetOwner().adventureBookManager.TaskUpdate(ConditionType.CheckGreaterCharLevelNum);
                GetOwner().adventureBookManager.TaskUpdate(ConditionType.CharMaxLevel);
                GetOwner().adventureBookManager.data.dailyCharLevelUp++;
                GetOwner().adventureBookManager.TaskUpdate(ConditionType.CheckStatisticVal);
            }
        }
    }
}
