using EggLink.DanhengServer.Database.Inventory;
using EggLink.DanhengServer.Enums.Item;
using EggLink.DanhengServer.Util;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace EggLink.DanhengServer.Data.Excel;

[ResourceEntity("MappingInfo.json")]
public class MappingInfoExcel : ExcelResource
{
    public int ID { get; set; }
    public int WorldLevel { get; set; }

    [JsonConverter(typeof(StringEnumConverter))]
    public FarmTypeEnum FarmType { get; set; } = FarmTypeEnum.None; // is enum

    public List<MappingInfoItem> DisplayItemList { get; set; } = [];

    [JsonIgnore] public List<MappingInfoItem> DropItemList { get; set; } = [];

    [JsonIgnore] public List<MappingInfoItem> DropRelicItemList { get; set; } = [];

    public override int GetId()
    {
        return ID * 10 + WorldLevel;
    }

    public override void Loaded()
    {
        GameData.MappingInfoData.Add(GetId(), this);
        if (DisplayItemList.Count == 0) return;

        List<int> equipDrop = [];
        Dictionary<int, List<int>> relicDrop = [];

        foreach (var item in DisplayItemList)
        {
            if (item.ItemNum > 0)
            {
                DropItemList.Add(item);
                continue;
            }

            if (item.ItemID == 2)
            {
                DropItemList.Add(new MappingInfoItem() // random credit
                {
                    ItemID = 2,
                    MinCount = (50 + WorldLevel * 10) * (int)FarmType,
                    MaxCount = (100 + WorldLevel * 10) * (int)FarmType
                });

                continue;
            }

            GameData.ItemConfigData.TryGetValue(item.ItemID, out var excel);
            if (excel == null) continue;

            if (excel.ItemSubType == ItemSubTypeEnum.RelicSetShowOnly)
            {
                var baseRelicId = item.ItemID / 10 % 1000;
                var baseRarity = item.ItemID % 10;

                // Add relics from the set
                var relicStart = 20001 + baseRarity * 10000 + baseRelicId * 10;
                var relicEnd = relicStart + 3;
                for (; relicStart <= relicEnd; relicStart++)
                {
                    GameData.ItemConfigData.TryGetValue(relicStart, out var relicExcel);
                    if (relicExcel == null) break;


                    if (!relicDrop.TryGetValue(baseRarity, out _))
                    {
                        var value = new List<int>();
                        relicDrop[baseRarity] = value;
                    }

                    relicDrop[baseRarity].Add(relicStart);
                }
            }
            else if (excel.ItemMainType == ItemMainTypeEnum.Material)
            {
                // Calculate amount to drop by purpose level
                MappingInfoItem? drop;
                switch (excel.PurposeType)
                {
                    // Avatar exp. Drop rate is guessed (with data)
                    case 1:
                        // Calc amount
                        var amount = excel.Rarity switch
                        {
                            ItemRarityEnum.NotNormal => WorldLevel < 3 ? WorldLevel + 3 : 2.5,
                            ItemRarityEnum.Rare => WorldLevel < 3 ? WorldLevel + 3 : WorldLevel * 2 - 3,
                            _ => 1
                        };

                        drop = new MappingInfoItem(excel.ID, (int)amount);
                        break;
                    // Boss materials
                    case 2:
                        drop = new MappingInfoItem(excel.ID, WorldLevel);
                        break;
                    // Trace materials. Drop rate is guessed (with data)
                    case 3:
                        drop = new MappingInfoItem(excel.ID, 5);
                        break;
                    // Boss Trace materials. Drop rate is guessed (with data)
                    case 4:
                        drop = new MappingInfoItem(excel.ID, (int)(WorldLevel * 0.5 + 0.5));
                        break;
                    // Lightcone exp. Drop rate is guessed (with data)
                    case 5:
                        // Calc amount
                        var count = excel.Rarity switch
                        {
                            ItemRarityEnum.NotNormal => Math.Max(5 - WorldLevel, 2.5),
                            ItemRarityEnum.Rare => WorldLevel % 3 + 1,
                            _ => 1
                        };

                        drop = new MappingInfoItem(excel.ID, (int)count);
                        break;
                    // Lucent afterglow
                    case 11:
                        drop = new MappingInfoItem(excel.ID, 4 + WorldLevel);
                        break;
                    // Unknown
                    default:
                        drop = null;
                        break;
                }

                ;

                // Add to drop list
                if (drop != null) DropItemList.Add(drop);
            }
            else if (excel.ItemMainType == ItemMainTypeEnum.Equipment)
            {
                // Add lightcones
                equipDrop.Add(excel.ID);
            }


            // Add equipment drops
            if (equipDrop.Count > 0)
                foreach (var dropId in equipDrop)
                {
                    MappingInfoItem drop = new(dropId, 1)
                    {
                        Chance = WorldLevel * 10 + 40
                    };
                    DropItemList.Add(drop);
                }

            // Add relic drops
            if (relicDrop.Count > 0)
                foreach (var entry in relicDrop)
                    // Add items to drop param
                foreach (var value in entry.Value)
                {
                    MappingInfoItem drop = new(value, 1);

                    // Set count by rarity
                    var amount = entry.Key switch
                    {
                        4 =>
                            WorldLevel * 0.5 - 0.5,
                        3 =>
                            WorldLevel * 0.5 + (WorldLevel == 2 ? 1.0 : 0),
                        2 =>
                            6 - WorldLevel + 0.5 - (WorldLevel == 1 ? 3.75 : 0),
                        _ =>
                            WorldLevel == 1 ? 6 : 2
                    };

                    // Set amount
                    if (amount > 0)
                    {
                        drop.ItemNum = (int)amount;
                        DropRelicItemList.Add(drop);
                    }
                }
        }
    }

    public List<ItemData> GenerateRelicDrops()
    {
        var relicsMap = new Dictionary<int, List<MappingInfoItem>>();
        foreach (var relic in DropRelicItemList)
        {
            GameData.ItemConfigData.TryGetValue(relic.ItemID, out var itemData);
            if (itemData == null) continue;
            switch (itemData.Rarity)
            {
                case ItemRarityEnum.NotNormal:
                    AddRelicToMap(relic, 2, relicsMap);
                    break;
                case ItemRarityEnum.Rare:
                    AddRelicToMap(relic, 3, relicsMap);
                    break;
                case ItemRarityEnum.VeryRare:
                    AddRelicToMap(relic, 4, relicsMap);
                    break;
                case ItemRarityEnum.SuperRare:
                    AddRelicToMap(relic, 5, relicsMap);
                    break;
                default:
                    continue;
            }
        }

        List<ItemData> drops = [];
        // Add higher rarity relics first
        for (var rarity = 5; rarity >= 2; rarity--)
        {
            var count = GetRelicCountByWorldLevel(rarity) *
                        ConfigManager.Config.ServerOption.ValidFarmingDropRate();
            if (count <= 0) continue;
            if (!relicsMap.TryGetValue(rarity, out var value)) continue;
            if (value.IsNullOrEmpty()) continue;
            while (count > 0)
            {
                var relic = value.RandomElement();
                drops.Add(new ItemData
                {
                    ItemId = relic.ItemID,
                    Count = 1
                });
                count--;
            }
        }

        return drops;
    }

    private void AddRelicToMap(MappingInfoItem relic, int rarity, Dictionary<int, List<MappingInfoItem>> relicsMap)
    {
        if (relicsMap.TryGetValue(rarity, out var value))
            value.Add(relic);
        else
            relicsMap.Add(rarity, [relic]);
    }

    private int GetRelicCountByWorldLevel(int rarity)
    {
        return WorldLevel switch
        {
            1 => rarity switch
            {
                2 => 6,
                3 => 3,
                4 => 1,
                5 => 0,
                _ => 0
            },
            2 => rarity switch
            {
                2 => 2,
                3 => 4,
                4 => 2 + LuckyRelicDropped(),
                5 => 0,
                _ => 0
            },
            3 => rarity switch
            {
                2 => 0,
                3 => 4,
                4 => 2,
                5 => 1,
                _ => 0
            },
            4 => rarity switch
            {
                2 => 0,
                3 => 3,
                4 => 2 + LuckyRelicDropped(),
                5 => 1 + LuckyRelicDropped(),
                _ => 0
            },
            5 => rarity switch
            {
                2 => 0,
                3 => 1 + LuckyRelicDropped(),
                4 => 3,
                5 => 2,
                _ => 0
            },
            6 => rarity switch
            {
                2 => 0,
                3 => 0,
                4 => 5,
                5 => 2 + LuckyRelicDropped(),
                _ => 0
            },
            _ => 0
        };
    }

    private int LuckyRelicDropped()
    {
        return Random.Shared.Next(100) < 25 ? 1 : 0;
    }
}

public class MappingInfoItem
{
    public MappingInfoItem()
    {
    }

    public MappingInfoItem(int itemId, int itemNum)
    {
        ItemID = itemId;
        ItemNum = itemNum;
    }

    public int ItemID { get; set; }
    public int ItemNum { get; set; }

    public int MinCount { get; set; }
    public int MaxCount { get; set; }
    public int Chance { get; set; } = 100;
}