using AssortGenerator.Common.Helpers;
using QuestValidator.Common;
using QuestValidator.Common.Helpers;
using QuestValidator.Helpers;
using QuestValidator.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace QuestValidator
{
    class Program
    {
        static void Main(string[] args)
        {
            var inputPath = DiskHelpers.CreateWorkingFolders();
            InputFileHelper.SetInputFiles(inputPath);

            //read in quest file
            var questData = QuestHelper.GetQuestData();
            var liveQuestData = QuestHelper.GetLiveQuestData();

            if (questData == null || liveQuestData == null)
            {
                LoggingHelpers.LogError("Unable to read quest data. Are you sure the both quest files are in 'QuestValidator//bin//Debug//netcoreapp3.1//input'");
                return;
            }

            ListMissingQuestsInLive(questData, liveQuestData);

            CheckForMissingQuestsInAkiFile(liveQuestData, questData);

            foreach (var item in questData)
            {
                var quest = item.Value;
                LogQuestDetails(quest);

                // Get live quest
                var relatedLiveQuest = liveQuestData.data.FirstOrDefault(x => x._id == quest._id);
                if (!ItemExists(relatedLiveQuest, "live quest. Live dump too old ?"))
                {
                    LoggingHelpers.LogInfo("");
                    continue;
                }

                CheckRootItemValues(quest, relatedLiveQuest);

                CheckSuccessRewardItems(quest, relatedLiveQuest);
                CheckStartedRewardItems(quest, relatedLiveQuest);

                CheckAvailableForFinishConditionItems(quest, relatedLiveQuest);
                CheckAvailableForStartConditionItems(quest, relatedLiveQuest);
                CheckFailConditionItems(quest, relatedLiveQuest);

                LoggingHelpers.LogInfo("");
                LoggingHelpers.LogInfo("-----");
                LoggingHelpers.LogInfo("");
            }
        }

        private static void ListMissingQuestsInLive(Dictionary<string, Quest> questData, QuestRoot liveQuestData)
        {
            var missingQuests = new List<Quest>();
            foreach (var quest in questData.Values)
            {
                var liveQuest = liveQuestData.data.FirstOrDefault(x=>x._id == quest._id);

                if (liveQuest == null)
                {
                    missingQuests.Add(quest);
                    LoggingHelpers.LogError($"ERROR Quest {quest._id} {QuestHelper.GetQuestNameById(quest._id)}  missing in live");
                }
            }
        }

        private static void CheckFailConditionItems(Quest quest, Quest relatedLiveQuest)
        {
            var liveFailConditions = relatedLiveQuest.conditions.Fail;

            // Check count
            CheckValuesMatch(quest.conditions.Fail.Count, liveFailConditions.Count, "FailCondition mismatch");

            foreach (var failItem in quest.conditions.Fail)
            {
                var liveFailItem = liveFailConditions.FirstOrDefault(x => x._props.id == failItem._props.id);
                if (!ItemExists(liveFailItem, "condition fail item", failItem._props.index.Value))
                {
                    continue;
                }

                // Check parentId
                CheckValuesMatch(failItem._props.parentId, liveFailItem._props.parentId, "AvailableForFinish parentId mismatch", failItem._props.id);
            }
        }

        private static void CheckAvailableForStartConditionItems(Quest quest, Quest relatedLiveQuest)
        {
            var liveStartConditions = relatedLiveQuest.conditions.AvailableForStart;

            // Check count
            CheckValuesMatch(quest.conditions.AvailableForStart.Count, liveStartConditions.Count, "AvailableForStartCondition mismtch");

            foreach (var availableForStartItem in quest.conditions.AvailableForStart)
            {
                var liveStartItem = liveStartConditions.FirstOrDefault(x=>x._props.id == availableForStartItem._props.id);
                if (!ItemExists(liveStartItem, "AvailableForStart item", availableForStartItem._props.index.Value))
                {
                    continue;
                }

                // Check parentId
                CheckValuesMatch(availableForStartItem._props.parentId, liveStartItem._props.parentId, "AvailableForFinish parentId mismatch", availableForStartItem._props.id);
            }
        }

        private static void LogQuestDetails(Quest quest)
        {
            var questName = QuestHelper.GetQuestNameById(quest._id);
            var trader = TraderHelper.GetTraderTypeById(quest.traderId);
            LoggingHelpers.LogInfo($"### Quest name: {questName} ({quest._id}) ({trader})");
            LoggingHelpers.LogInfo($"Wiki: https://escapefromtarkov.fandom.com/wiki/{questName.Replace(' ', '_')}");
        }

        private static void CheckRootItemValues(Quest quest, Quest relatedLiveQuest)
        {
            // Check image id matches
            CheckValuesMatch(quest.image.Substring(0, quest.image.Length - 4), relatedLiveQuest?.image.Substring(0, relatedLiveQuest.image.Length - 4), "item path mismatch");

            //Check image id contains quest id
            if (!quest.image.Contains(quest._id))
            {
                LoggingHelpers.LogInfo($"INFO Quest image path does not contain quest id");
            }

            // Check started reward count matches
            CheckValuesMatch(quest.rewards.Started.Count, relatedLiveQuest.rewards.Started.Count, "Started item count mismatch");

            // Check success reward count matches
            CheckValuesMatch(quest.rewards.Success.Count, relatedLiveQuest.rewards.Success.Count, "success item count mismatch");

            // Check Fail reward count matches
            CheckValuesMatch(quest.rewards.Fail.Count, relatedLiveQuest.rewards.Fail.Count, "fail item count mismatch");

            // Check min level matches
            CheckValuesMatch(quest.min_level, relatedLiveQuest.min_level, "min level value mismatch");

            // Check location matches
            CheckValuesMatch(quest.location, relatedLiveQuest.location, "location value mismatch");

            // Check traderid matches
            CheckValuesMatch(quest.traderId, relatedLiveQuest.traderId, "traderid value mismatch");

            // Check type matches
            CheckValuesMatch(quest.type, relatedLiveQuest.type, "quest type value mismatch");
        }

        private static void CheckSuccessRewardItems(Quest quest, Quest relatedLiveQuest)
        {
            var liveQuestSuccessRewardItems = relatedLiveQuest.rewards.Success;

            foreach (RewardStatus questSuccessRewardItem in quest.rewards.Success.Where(x => x.type == "Item"))
            {
                // Get live reward item by index and type
                var relatedLiveRewardItem = GetLiveRewardItem(questSuccessRewardItem, liveQuestSuccessRewardItems);

                if (relatedLiveRewardItem == null)
                {
                    LogUnableToFindSuccessItemInLiveData(questSuccessRewardItem, relatedLiveRewardItem);
                    continue;
                }

                // Ensure target matches the objects items[0].id value
                if (questSuccessRewardItem.items[0]?._id != questSuccessRewardItem.target)
                {
                    LoggingHelpers.LogWarning($"WARNING target does not match first item: {questSuccessRewardItem.target}, expected {questSuccessRewardItem.items[0]?._id}");
                }

                // Check template ids match
                CheckValuesMatch(questSuccessRewardItem.items[0]._tpl, relatedLiveRewardItem.items[0]._tpl, "mismatch for template id", questSuccessRewardItem.items[0]._id, true);

                // Check value values match
                CheckValuesMatch(questSuccessRewardItem.value, relatedLiveRewardItem.value, "mismatch for success item reward value", questSuccessRewardItem.id);

                // Check item stack count
                if (questSuccessRewardItem.items[0]?.upd != null && relatedLiveRewardItem.items[0]?.upd != null)
                {
                    CheckValuesMatch(questSuccessRewardItem.items[0].upd.StackObjectsCount.Value, relatedLiveRewardItem.items[0].upd.StackObjectsCount.Value, "mismatch for success item StackObjectsCount", questSuccessRewardItem.items[0]._id);
                }

                // check sub items match
                CheckSubItemsMatch(questSuccessRewardItem, relatedLiveRewardItem);
            }

            foreach (var questSuccessRewardItem in quest.rewards.Success.Where(x => x.type == "Experience"))
            {
                var relatedLiveRewardItem = liveQuestSuccessRewardItems.FirstOrDefault(x => x.type == "Experience");
                if (!ItemExists(relatedLiveRewardItem, "experience success reward item", questSuccessRewardItem.index))
                {
                    continue;
                }

                // check experience value matches
                CheckValuesMatch(questSuccessRewardItem.value, relatedLiveRewardItem.value, "experience value mismatch");
            }

            foreach (var questSuccessRewardItem in quest.rewards.Success.Where(x => x.type == "TraderStanding"))
            {
                var relatedLiveRewardItem = liveQuestSuccessRewardItems.FirstOrDefault(x => x.target == questSuccessRewardItem.target && x.type == "TraderStanding");
                if (!ItemExists(relatedLiveRewardItem, "TraderStanding success reward item", questSuccessRewardItem.index))
                {
                    continue;
                }

                // check standing value matches
                CheckValuesMatch(questSuccessRewardItem.value, relatedLiveRewardItem.value, "trader standing value mismatch");

                // check target value matches
                CheckValuesMatch(questSuccessRewardItem.target, relatedLiveRewardItem.target, "trader target value mismatch");
            }

            foreach (var questSuccessRewardItem in quest.rewards.Success.Where(x => x.type == "AssortmentUnlock"))
            {
                // Find the assort unlock item in the list of success rewards
                var possibleLiveRewardItems = liveQuestSuccessRewardItems.Where(x => x.id == questSuccessRewardItem.id && x.type == "AssortmentUnlock");
                RewardStatus relatedLiveRewardItem = null;

                // we found the one we want
                if (possibleLiveRewardItems?.Count() == 1)
                {
                    relatedLiveRewardItem = possibleLiveRewardItems.First();
                }

                // multiple found
                if (possibleLiveRewardItems?.Count() > 1)
                {
                    // be more specific, get my index
                    relatedLiveRewardItem = possibleLiveRewardItems.FirstOrDefault(x=>x.index == questSuccessRewardItem.index);

                    // nothing found by index, try by 
                    if (relatedLiveRewardItem == null)
                    {
                        relatedLiveRewardItem = possibleLiveRewardItems.FirstOrDefault(x => x.traderId == questSuccessRewardItem.traderId);
                    }
                }

                if (relatedLiveRewardItem == null)
                {
                    relatedLiveRewardItem = liveQuestSuccessRewardItems.Find(x => x.traderId == questSuccessRewardItem.traderId
                    && x.index == questSuccessRewardItem.index 
                    && x.type == "AssortmentUnlock"
                    && x.items[0]._tpl == questSuccessRewardItem.items[0]._tpl);
                }

                if (!ItemExists(relatedLiveRewardItem, "AssortmentUnlock success reward item", questSuccessRewardItem.index))
                {
                    continue;
                }

                // Check loyalty level
                CheckValuesMatch(questSuccessRewardItem.loyaltyLevel.Value, relatedLiveRewardItem.loyaltyLevel.Value, "loyalty level value mismatch", questSuccessRewardItem.id);

                // Check traderId
                CheckValuesMatch(questSuccessRewardItem.traderId, relatedLiveRewardItem.traderId, "traderId value mismatch", questSuccessRewardItem.id);

                // check target equals items[0].id
                CheckValuesMatch(questSuccessRewardItem.target, questSuccessRewardItem.items[0]._id, "target value does not match items[0].id mismatch", questSuccessRewardItem.id);
            }
        }

        private static void CheckSubItemsMatch(RewardStatus questSuccessRewardItem, RewardStatus relatedLiveRewardItem)
        {
            foreach (var subItem in questSuccessRewardItem.items.Where(x => !string.IsNullOrEmpty(x.slotId)))
            {
                // find live item by slotid
                var liveCounterpart = relatedLiveRewardItem.items.Where(x => x.slotId == subItem.slotId);
                if (liveCounterpart == null || liveCounterpart.Count() == 0)
                {
                    // Look for live item by template id
                    liveCounterpart = relatedLiveRewardItem.items.Where(x => x._tpl == subItem._tpl);
                    if (liveCounterpart == null || liveCounterpart.Count() == 0)
                    {
                        LoggingHelpers.LogWarning($"a live counterpart for the subItem {subItem.slotId} could not be found by slotid or tpId, skipping subItem check");
                        continue;
                    }
                }
                if (liveCounterpart.Count() > 1)
                {
                    LoggingHelpers.LogWarning($"Multiple live counterparts for the subItem {subItem.slotId} found, skipping subItem check");
                    continue;
                }

                var firstLiveItem = liveCounterpart.FirstOrDefault();
                CheckValuesMatch(subItem._tpl, firstLiveItem._tpl, $"mismatch for success subItem({subItem.slotId}) reward templateId", subItem._id);
            }
        }

        private static void LogUnableToFindSuccessItemInLiveData(RewardStatus questSuccessRewardItem, RewardStatus relatedLiveRewardItem)
        {
            if (relatedLiveRewardItem == null)
            {
                LoggingHelpers.LogError($"ERROR unable to find success reward item in live quest data by index: ({questSuccessRewardItem.index}) OR template id: {questSuccessRewardItem.items[0]._tpl} ({ItemTemplateHelper.GetTemplateById(questSuccessRewardItem.items[0]._tpl)._name})");

                LoggingHelpers.LogError("Existing items:");
                LogSuccessItems(questSuccessRewardItem);

                LoggingHelpers.LogError($"ERROR Skipping quest success item. id: {questSuccessRewardItem.id}");
            }
        }

        private static void LogSuccessItems(RewardStatus rewardItem)
        {
            foreach (var item in rewardItem.items)
            {
                LoggingHelpers.LogInfo($"{item._tpl} ({ItemTemplateHelper.GetTemplateById(item._tpl)._name})");
            }
        }

        /// <summary>
        /// Find live success item reward by index
        /// If item at index does not match templateId to desired item
        /// get live success item reward by template id
        /// </summary>
        /// <param name="questSuccessRewardItem"></param>
        /// <param name="liveQuestSuccessRewardItems"></param>
        /// <returns></returns>
        private static RewardStatus GetLiveRewardItem(RewardStatus questSuccessRewardItem, List<RewardStatus> liveQuestSuccessRewardItems)
        {
            var LiveItemRewards = liveQuestSuccessRewardItems.Where(x => x.type == "Item");
            var liveRewardItemByIndex = LiveItemRewards.FirstOrDefault(x => x.index == questSuccessRewardItem.index);

            // no item found by index, find by template id
            if (liveRewardItemByIndex == null)
            {
                foreach (var liveItem in LiveItemRewards
                    .SelectMany(liveItem => liveItem.items
                    .Where(subItem => subItem._tpl == questSuccessRewardItem.items[0]._tpl)
                    .Select(subItem => liveItem)))
                {
                    return liveItem;
                }
            }

            // item found by index but template id didnt match
            if (liveRewardItemByIndex != null && liveRewardItemByIndex.items[0]._tpl != questSuccessRewardItem.items[0]._tpl)
            {
                return LiveItemRewards.FirstOrDefault(x => x.items[0]._tpl == questSuccessRewardItem.items[0]._tpl);
            }

            return liveRewardItemByIndex;
        }

        private static void CheckStartedRewardItems(Quest quest, Quest relatedLiveQuest)
        {
            var liveQuestStartedRewardItems = relatedLiveQuest.rewards.Started;

            foreach (var questStartedRewardItem in quest.rewards.Started.Where(x => x.type == "Item"))
            {
                var errorMessage = string.Empty;
                // Get live reward item by index and type
                var relatedLiveRewardItem = liveQuestStartedRewardItems.Find(x => x.index == questStartedRewardItem.index && x.type == "Item");
                if (relatedLiveRewardItem == null)
                {
                    // Get live reward item by templateId and type as we cant find it by index
                    relatedLiveRewardItem = liveQuestStartedRewardItems.Find(x => x.items != null && x.items[0]?._tpl == questStartedRewardItem.items[0]?._tpl && x.type == "Item");
                    if (relatedLiveRewardItem == null)
                    {
                        LoggingHelpers.LogError($"ERROR unable to find started reward item in live quest data by index: ({questStartedRewardItem.index}) OR template id: {questStartedRewardItem.items[0]._tpl}");
                        LoggingHelpers.LogError($"ERROR Skipping quest started item: {questStartedRewardItem.id}");
                        continue;
                    }
                }

                // Ensure target matches the objects items[0].id value
                if (questStartedRewardItem.items[0]?._id != questStartedRewardItem.target)
                {
                    LoggingHelpers.LogWarning($"WARNING target does not match first item: {questStartedRewardItem.target}, expected {questStartedRewardItem.items[0]?._id}");
                }

                // Check template ids match
                CheckValuesMatch(questStartedRewardItem.items[0]._tpl, relatedLiveRewardItem.items[0]._tpl, "mismatch for template id", questStartedRewardItem.items[0]._id, true);

                // Check 'value' values match
                CheckValuesMatch(questStartedRewardItem.value, relatedLiveRewardItem.value, "mismatch for success item reward value", questStartedRewardItem.id);

                // Check item stack count
                if (questStartedRewardItem.items[0] != null && questStartedRewardItem.items[0].upd != null)
                {
                    CheckValuesMatch(questStartedRewardItem.items[0].upd.StackObjectsCount.Value, relatedLiveRewardItem.items[0].upd.StackObjectsCount.Value, "mismatch for started item StackObjectsCount", questStartedRewardItem.items[0]._id);
                }
            }
                
            foreach (var questStartedRewardItem in quest.rewards.Started.Where(x => x.type == "AssortmentUnlock"))
            {
                // Get live reward item by id
                var relatedLiveRewardItem = liveQuestStartedRewardItems.FirstOrDefault(x => x.id == questStartedRewardItem.id && x.type == "AssortmentUnlock");
                if (relatedLiveRewardItem == null)
                {
                    // Cant find live reward item by id, get my template id inside items[0]
                    relatedLiveRewardItem = liveQuestStartedRewardItems.Find(x => x.traderId == questStartedRewardItem.traderId
                    && x.index == questStartedRewardItem.index
                    && x.type == "AssortmentUnlock"
                    && x.items[0]._tpl == questStartedRewardItem.items[0]._tpl);
                }

                if (!ItemExists(relatedLiveRewardItem, "AssortmentUnlock started reward item", questStartedRewardItem.index))
                {
                    continue;
                }

                // Check loyalty level
                CheckValuesMatch(questStartedRewardItem.loyaltyLevel.Value, relatedLiveRewardItem.loyaltyLevel.Value, "loyalty level value mismatch", questStartedRewardItem.id);

                // Check traderId
                CheckValuesMatch(questStartedRewardItem.traderId, relatedLiveRewardItem.traderId, "traderId value mismatch", questStartedRewardItem.id);

                // check target equals items[0].id
                CheckValuesMatch(questStartedRewardItem.target, questStartedRewardItem.items[0]._id, "target value does not match items[0].id mismatch", questStartedRewardItem.id);
            }
        }

        private static void CheckAvailableForFinishConditionItems(Quest quest, Quest relatedLiveQuest)
        {
            // Check count
            CheckValuesMatch(quest.conditions.AvailableForFinish.Count, relatedLiveQuest.conditions.AvailableForFinish.Count, "AvailableForFinish mismtch");

            foreach (var availableForFinishItem in quest.conditions.AvailableForFinish)
            {
                AvailableFor liveFinishItem = relatedLiveQuest.conditions.AvailableForFinish.Find(x => x._props.id == availableForFinishItem._props.id);
                if (!ItemExists(liveFinishItem, "AvailableForFinish item", availableForFinishItem._props.index.Value))
                {
                    continue;
                }

                // Check parentId
                CheckValuesMatch(availableForFinishItem._props.parentId, liveFinishItem._props.parentId, "AvailableForFinish parentId mismatch", availableForFinishItem._props.id);

                // check AvailableForFinish resetOnSessionEnd
                CheckValuesMatch(availableForFinishItem._props.resetOnSessionEnd.Value, liveFinishItem._props.resetOnSessionEnd.Value, "AvailableForFinish resetOnSessionEnd value mismatch", availableForFinishItem._props.id);

                // check AvailableForFinish target
                CheckValuesMatch(Convert.ToString(availableForFinishItem._props.target), Convert.ToString(liveFinishItem._props.target), "AvailableForFinish target value mismatch", availableForFinishItem._props.id);

                // check weapons allowed match
                //CheckValuesMatch(availableForFinishItem._props.weapon.Count, liveFinishItem._props.counter.conditions), "AvailableForFinish target value mismatch", availableForFinishItem._props.id);

            }
        }

        private static bool ItemExists(object itemToCheck, string message, int index = -1)
        {
            if (itemToCheck == null)
            {
                if (index == -1)
                {
                    LoggingHelpers.LogError($"ERROR no match found for {message}");
                }
                else
                {
                    LoggingHelpers.LogError($"ERROR no match found for {message} at index: {index}");
                }

                return false;
            }

            return true;
        }

        private static void CheckValuesMatch(int firstValue, int secondValue, string message, string associatedId = "")
        {
            if (firstValue != secondValue)
            {
                if (associatedId == string.Empty)
                {
                    LoggingHelpers.LogWarning($"WARNING {message}: '{firstValue}', expected '{secondValue}'");
                }
                else
                {
                    LoggingHelpers.LogWarning($"WARNING {associatedId} {message}: '{firstValue}', expected '{secondValue}'");
                }
            }
        }

        private static void CheckValuesMatch<T>(T firstValue, T secondValue, string message, string associatedId = "") where T : struct
        {
            if (firstValue.Equals(secondValue))
            {
                if (associatedId == string.Empty)
                {
                    LoggingHelpers.LogWarning($"WARNING {message}: '{firstValue}', expected '{secondValue}'");
                }
                else
                {
                    LoggingHelpers.LogWarning($"WARNING {associatedId} {message}: '{firstValue}', expected '{secondValue}'");
                }
            }
        }

            private static void CheckValuesMatch(string firstValue, string secondValue, string message, string associatedId = "", bool performTemplateIdLookup = false)
        {
            if (firstValue != secondValue)
            {
                if (performTemplateIdLookup)
                {
                    firstValue = $"{firstValue} ({ItemTemplateHelper.GetTemplateById(firstValue)._name})";
                    secondValue = $"{secondValue} ({ItemTemplateHelper.GetTemplateById(secondValue)._name})";
                }
                if (associatedId == string.Empty)
                {
                    LoggingHelpers.LogWarning($"WARNING {message}: '{firstValue}', expected '{secondValue}'");
                }
                else
                {
                    LoggingHelpers.LogWarning($"WARNING {associatedId} {message}: '{firstValue}', expected '{secondValue}'");
                }
            }
        }

        private static void CheckValuesMatch(bool firstValue, bool secondValue, string message, string associatedId = "")
        {
            if (firstValue != secondValue)
            {
                if (associatedId == string.Empty)
                {
                    LoggingHelpers.LogWarning($"WARNING {message}: '{firstValue}', expected '{secondValue}'");
                }
                else
                {
                    LoggingHelpers.LogWarning($"WARNING {associatedId} {message}: '{firstValue}', expected '{secondValue}'");
                }
            }
        }

        private static void CheckForMissingQuestsInAkiFile(QuestRoot liveQuestData, Dictionary<string, Quest> akiQuestData)
        {
            // iterate over live quests and look for quests that exist in live but not in aki
            var missingQuests = new List<string>();
            foreach (var liveQuest in liveQuestData.data)
            {
                if (!akiQuestData.ContainsKey(liveQuest._id))
                {
                    missingQuests.Add($"{liveQuest._id} {QuestHelper.GetQuestNameById(liveQuest._id)}");
                }
            }
            // Quests in live but not in aki were found, log it
            if (missingQuests.Count > 0)
            {
                LoggingHelpers.LogWarning($"WARNING aki quest list is missing quests:");
                foreach (var item in missingQuests)
                {
                    LoggingHelpers.LogWarning(item);
                }
            }
        }
    }
}