using AssortValidator.Common;
using AssortValidator.Common.Helpers;
using AssortValidator.Common.Helpers.Models;
using AssortValidator.Common.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace AssortValidator
{
    public static class AssortItemChecker
    {
        public static void CheckAssortValues(Dictionary<Trader, Assort> assortData, Dictionary<Trader, Assort> liveAssortData)
        {
            List<QuestAssortUnlock> questAssortUnlocks = QuestHelper.GetQuestAssortUnlocks(QuestHelper.GetQuestDataFromDump().Values);
            foreach (var trader in assortData)
            {
                LoggingHelpers.LogNewLine();
                LoggingHelpers.LogHeading($"Checking Trader: {trader.Key}");

                var correspondingLiveTrader = liveAssortData[trader.Key];
                if (correspondingLiveTrader == null)
                {
                    LoggingHelpers.LogError($"Unable to find live trader: {trader.Key}");
                    LoggingHelpers.LogNewLine();
                    continue;
                }

                foreach (var assort in trader.Value.items.Where(x => x.parentId == "hideout"))
                {
                    var itemName = ItemTemplateHelper.GetTemplateById(assort._tpl)._name;
                    int assortLoyaltyLevel = AssortHelper.GetAssortLoyaltyLevel(assort._id, trader.Value);

                    LoggingHelpers.LogInfo($"{trader.Key}: itemId: {assort._id} - tpId: {assort._tpl} ({itemName}) Level {assortLoyaltyLevel}");
                    LogUnlockQuest(questAssortUnlocks, assort, assortLoyaltyLevel);

                    var liveAssort = GetLiveAssort(correspondingLiveTrader, assort._tpl, assortLoyaltyLevel);
                    if (liveAssort == null)
                    {
                        continue;
                    }

                    // Check if upd objects match ( both exist or both dont exist)
                    if (!DoesLiveAssortUpdMatchOffline(assort, liveAssort, itemName))
                    {
                        continue;
                    }

                    var offlineBarterData = trader.Value.barter_scheme.FirstOrDefault(x => x.Key == assort._id).Value;
                    var liveBarterData = correspondingLiveTrader.barter_scheme.FirstOrDefault(x => x.Key == liveAssort._id).Value;
                    if (assort.upd != null)
                    {
                        CheckUpdValues(assort, liveAssort);

                        // Iterate over assort barter items
                        // Assorts live barter data
                        for (int i = 0; i < offlineBarterData.Count; i++)
                        {
                            var liveItem = liveBarterData[i];

                            // Create list of offline barter items for assort
                            var offlineBarterItems = new List<Assort.BarterDetails>();
                            foreach (var foundItem in offlineBarterData)
                            {
                                foreach (var item in foundItem)
                                {
                                    offlineBarterItems.Add(item);
                                }
                            }

                            LogAssortType(offlineBarterItems, "Existing");

                            if (offlineBarterItems.Count != liveItem.Count)
                            {
                                LoggingHelpers.LogError($"assort {assort._id} barter item count mismatch, found {offlineBarterItems.Count}, expected {liveItem.Count}");
                                LoggingHelpers.LogError("Found:");
                                foreach (var foundItem in offlineBarterItems)
                                {
                                    LoggingHelpers.LogError($"{foundItem._tpl} ({ItemTemplateHelper.GetTemplateById(foundItem._tpl)._name}) x{foundItem.count}");
                                }
                                LoggingHelpers.LogError("Expected:");
                                foreach (var missingItem in liveItem)
                                {
                                    LoggingHelpers.LogError($"{missingItem._tpl} ({ItemTemplateHelper.GetTemplateById(missingItem._tpl)._name}) x{missingItem.count}");
                                }
                                continue;
                            }

                            // Iterate over the barter requirement items
                            for (int j = 0; j < offlineBarterData[i].Count; j++)
                            {
                                // Check live barter item exists
                                if (j > liveItem.Count - 1)
                                {
                                    LoggingHelpers.LogError($"assort {assort._id} live barter item requirement missing");
                                    continue;
                                }
                                var liveSubItem = liveItem[j];
                                var subItem = liveItem[j];

                                ValueCheckerHelper.CheckValuesMatch(subItem._tpl, liveSubItem._tpl, "barter item template ids dont match");
                                ValueCheckerHelper.CheckValuesMatch(subItem.count.ToString(), liveSubItem.count.ToString(), "barter item counts dont match");
                            }
                        }
                    }
                    CheckAssortCost(offlineBarterData, liveBarterData);

                    LoggingHelpers.LogNewLine();
                }
            }
        }

        private static void CheckUpdValues(Assort.Item assort, Assort.Item correspondingLiveAssort)
        {
            if (assort.upd.StackObjectsCount.HasValue && !assort.upd.UnlimitedCount.HasValue)
            {
                var liveStackCount = GetRoundedStackCount(correspondingLiveAssort.upd.StackObjectsCount.Value);
                ValueCheckerHelper.CheckValuesMatch(assort.upd.StackObjectsCount.Value, liveStackCount, $"stackobjectCount does not match live. orig: ({correspondingLiveAssort.upd.StackObjectsCount})");
            }

            // check count is 999999 if unlimited count is true
            if (assort.upd.UnlimitedCount.HasValue)
            {
                ValueCheckerHelper.CheckValuesMatch(assort.upd.StackObjectsCount.Value, 999999, "unlimited count does not match");
            }

            // Check max buy restriction matches
            ValueCheckerHelper.CheckValuesMatch(assort.upd.BuyRestrictionMax.GetValueOrDefault(0), correspondingLiveAssort.upd.BuyRestrictionMax.GetValueOrDefault(0), "BuyRestrictionMax does not match");
        }

        private static int GetRoundedStackCount(int stackObjectsCount)
        {
            //TODO: automate this with math.round
            if (stackObjectsCount < 6)
            {
                return 5;
            }

            if (stackObjectsCount < 11)
            {
                return 10;
            }

            if (stackObjectsCount < 21)
            {
                return 20;
            }

            if (stackObjectsCount < 51)
            {
                return 50;
            }

            if (stackObjectsCount < 101)
            {
                return 100;
            }

            if (stackObjectsCount < 201)
            {
                return 200;
            }

            if (stackObjectsCount < 501)
            {
                return 500;
            }

            if (stackObjectsCount < 1001)
            {
                return 1000;
            }

            if (stackObjectsCount < 2001)
            {
                return 2000;
            }

            if (stackObjectsCount < 3001)
            {
                return 3000;
            }

            if (stackObjectsCount < 4001)
            {
                return 4000;
            }

            if (stackObjectsCount < 5001)
            {
                return 5000;
            }

            if (stackObjectsCount < 6001)
            {
                return 6000;
            }

            if (stackObjectsCount < 10001)
            {
                return 10000;
            }

            if (stackObjectsCount < 15001)
            {
                return 15000;
            }

            if (stackObjectsCount < 20001)
            {
                return 20000;
            }

            if (stackObjectsCount < 25001)
            {
                return 25000;
            }

            if (stackObjectsCount < 50001)
            {
                return 50000;
            }

            if (stackObjectsCount < 70001)
            {
                return 70000;
            }

            if (stackObjectsCount < 85001)
            {
                return 85000;
            }

            if (stackObjectsCount < 100001)
            {
                return 100000;
            }

            if (stackObjectsCount < 150001)
            {
                return 150000;
            }

            if (stackObjectsCount < 200001)
            {
                return 200000;
            }

            if (stackObjectsCount < 500001)
            {
                return 500000;
            }

            if (stackObjectsCount < 700001)
            {
                return 700000;
            }

            if (stackObjectsCount < 1000001)
            {
                return 1000000;
            }

            if (stackObjectsCount < 5000001)
            {
                return 5000000;
            }

            return 9999999;
        }

        private static bool DoesLiveAssortUpdMatchOffline(Assort.Item assort, Assort.Item correspondingLiveAssort, string itemName)
        {
            if (assort.upd != null && correspondingLiveAssort.upd == null)
            {
                LoggingHelpers.LogError($"assort {assort._id} ({itemName}) has a upd object, live does not. skipping assort");
                LoggingHelpers.LogNewLine();
                return false;
            }

            // no parent id = not a gun item
            if (assort.parentId == null && assort.upd == null && correspondingLiveAssort.upd != null)
            {
                LoggingHelpers.LogError($"assort {assort._id} ({itemName}) does not have a upd object, live does. skipping assort");
                LoggingHelpers.LogNewLine();
                return false;
            }

            return true;
        }

        private static Assort.Item GetLiveAssort(Assort trader, string templateId, int expectedLoyaltyLevel)
        {
            var liveAssorts = trader.items;
            var liveLoyaltyLevels = trader.loyal_level_items;

            var liveAssortsWithTemplateId = liveAssorts.Where(x => x._tpl == templateId).ToList();

            if (liveAssortsWithTemplateId.Count == 0)
            {
                LoggingHelpers.LogError($"Unable to find live assort tpId: {templateId} ({ItemTemplateHelper.GetTemplateById(templateId)._name})");
                LoggingHelpers.LogError($"Skipping assort");
                LoggingHelpers.LogNewLine();
                return null;
            }

            // Only one item, break out early and return
            if (liveAssortsWithTemplateId.Count == 1)
            {
                return liveAssortsWithTemplateId.First();
            }

            // More than one assort found
            // Gather assort ids and use them to get loyalty level records
            var liveAssortsIds = liveAssortsWithTemplateId.Select(x => x._id);
            var liveLoyaltyLevelsForTemplateId = liveLoyaltyLevels.Where(x => liveAssortsIds.Contains(x.Key)).ToList();

            // Same loyalty level + multiple found
            if (liveLoyaltyLevelsForTemplateId.All(x => x.Value == expectedLoyaltyLevel) && liveLoyaltyLevelsForTemplateId.Count > 1)
            {
                // Both have same loyalty level, cant proceed;
                LoggingHelpers.LogError($"Multiple ({liveLoyaltyLevelsForTemplateId.Count}) live assorts for this tpId found at same Level ({expectedLoyaltyLevel}) - Unable to distinguish: {templateId} ({ItemTemplateHelper.GetTemplateById(templateId)._name}) ");

                LoggingHelpers.LogNewLine();
                return null;
            }

            var loyaltyLevelItemThatMatches = liveLoyaltyLevelsForTemplateId.Where(x => x.Value == expectedLoyaltyLevel);
            if (loyaltyLevelItemThatMatches != null && loyaltyLevelItemThatMatches.Count() > 1)
            {
                LoggingHelpers.LogWarning($"({loyaltyLevelItemThatMatches.Count()}) live items found, choosing first one in list");
            }

            if (loyaltyLevelItemThatMatches == null)
            {
                // Both have same loyalty level, cant proceed;
                LoggingHelpers.LogError($"No live assort for this tpId found at same level  - Unable proceed: {templateId} ({ItemTemplateHelper.GetTemplateById(templateId)._name})");
                LoggingHelpers.LogNewLine();
                return null;
            }

            return liveAssorts.Find(x => x._id == loyaltyLevelItemThatMatches.FirstOrDefault().Key);
        }


        private static void CheckAssortCost(List<List<Assort.BarterDetails>> offlineBarterData, List<List<Assort.BarterDetails>> liveBarterData)
        {
            var firstBarterItem = offlineBarterData.First().First();
            var firstOfflineItemIsMoney = ItemTemplateHelper.IsMoney(firstBarterItem._tpl);

            var firstLiveBarterItem = liveBarterData.First().First();
            var firstLiveItemIsMoney = ItemTemplateHelper.IsMoney(firstLiveBarterItem._tpl);

            if (firstOfflineItemIsMoney != firstLiveItemIsMoney)
            {
                LoggingHelpers.LogError($"item cost mismatch. Offline item is money: {firstOfflineItemIsMoney}. Live item is money: {firstLiveItemIsMoney}");
            }

            if (firstOfflineItemIsMoney && firstOfflineItemIsMoney)
            {
                var liveCount = int.Parse(Math.Round(firstLiveBarterItem.count).ToString());
                ValueCheckerHelper.CheckValuesMatch(firstBarterItem.count.ToString(), liveCount.ToString(), "costs do not match");
            }
        }

        /// <summary>
        /// Log the quest type: money or barter trade
        /// </summary>
        private static void LogAssortType(List<Assort.BarterDetails> offlineBarterItems, string assortSource)
        {
            if (ItemTemplateHelper.IsMoney(offlineBarterItems.First()._tpl))
            {
                LoggingHelpers.LogInfo($"{assortSource} assort is money trade");
            }
            else
            {
                LoggingHelpers.LogInfo($"{assortSource} assort is barter trade");
            }
        }

        private static void LogUnlockQuest(List<QuestAssortUnlock> questAssortUnlocks, Assort.Item assort, int assortLoyaltyLevel)
        {
            var questAssortUnlockData = questAssortUnlocks.FirstOrDefault(x => x.AssortTemplateId == assort._tpl && x.LoyaltyLevel == assortLoyaltyLevel);
            if (questAssortUnlockData != null)
            {
                LoggingHelpers.LogInfo($"Assort likely unlocked by quest: {questAssortUnlockData.QuestName} ({questAssortUnlockData.QuestId})");
            }
        }
    }
}