using SptCommon.Annotations;
using Core.Models.Eft.Common;
using Core.Models.Eft.Common.Tables;
using Core.Models.Eft.ItemEvent;
using Core.Models.Enums;
using Core.Models.Spt.Config;
using Core.Models.Utils;
using Core.Servers;
using Core.Services;
using Core.Utils;
using Core.Utils.Cloners;
using SptCommon.Extensions;


namespace Core.Helpers;

[Injectable]
public class QuestRewardHelper(
    ISptLogger<QuestRewardHelper> _logger,
    HashUtil _hashUtil,
    TimeUtil _timeUtil,
    ItemHelper _itemHelper,
    PaymentHelper _paymentHelper,
    TraderHelper _traderHelper,
    DatabaseService _databaseService,
    QuestConditionHelper _questConditionHelper,
    ProfileHelper _profileHelper,
    PresetHelper _presetHelper,
    RewardHelper _rewardHelper,
    LocalisationService _localisationService,
    ICloner _cloner,
    ConfigServer _configServer
)
{
    protected QuestConfig _questConfig = _configServer.GetConfig<QuestConfig>();

    /**
     * Give player quest rewards - Skills/exp/trader standing/items/assort unlocks - Returns reward items player earned
     * @param profileData Player profile (scav or pmc)
     * @param questId questId of quest to get rewards for
     * @param state State of the quest to get rewards for
     * @param sessionId Session id
     * @param questResponse Response to send back to client
     * @returns Array of reward objects
     */
    public IEnumerable<Item> ApplyQuestReward(PmcData profileData, string questId, QuestStatusEnum state, string sessionId,
        ItemEventRouterResponse questResponse)
    {
        // Repeatable quest base data is always in PMCProfile, `profileData` may be scav profile
        // TODO: consider moving repeatable quest data to profile-agnostic location
        var fullProfile = _profileHelper.GetFullProfile(sessionId);
        var pmcProfile = fullProfile.CharacterData.PmcData;
        if (pmcProfile is null)
        {
            _logger.Error($"Unable to get pmc profile for: {sessionId}, no rewards given");
            return Enumerable.Empty<Item>();
        }

        var questDetails = GetQuestFromDb(questId, pmcProfile);
        if (questDetails is null)
        {
            _logger.Warning(_localisationService.GetText("quest-unable_to_find_quest_in_db_no_quest_rewards", questId));
            return Enumerable.Empty<Item>();
        }

        var questMoneyRewardBonusMultiplier = GetQuestMoneyRewardBonusMultiplier(pmcProfile);
        if (questMoneyRewardBonusMultiplier > 0) // money = money + (money * intelCenterBonus / 100)
            questDetails = ApplyMoneyBoost(questDetails, questMoneyRewardBonusMultiplier, state);

        // e.g. 'Success' or 'AvailableForFinish'
        var rewards = questDetails.Rewards.GetByJsonProp<List<Reward>>(state.ToString());
        return _rewardHelper.ApplyRewards(
            rewards,
            CustomisationSource.UNLOCKED_IN_GAME,
            fullProfile,
            profileData,
            questId,
            questResponse);
    }

    /**
     * Get quest by id from database (repeatables are stored in profile, check there if questId not found)
     * @param questId Id of quest to find
     * @param pmcData Player profile
     * @returns IQuest object
     */
    protected Quest GetQuestFromDb(string questId, PmcData pmcData)
    {
        // May be a repeatable quest
        var quest = _databaseService.GetQuests()[questId];
        if (quest == null)
        {
            // Check daily/weekly objects
            foreach (var repeatableQuest in pmcData.RepeatableQuests)
            {
                quest = repeatableQuest.ActiveQuests.FirstOrDefault(r => r.Id == questId);
                if (quest != null)
                    break;
            }
        }

        return quest;
    }

    /// <summary>
    /// Get players money reward bonus from profile
    /// </summary>
    /// <param name="pmcData">player profile</param>
    /// <returns>bonus as a percent</returns>
    protected double GetQuestMoneyRewardBonusMultiplier(PmcData pmcData)
    {
        // Check player has intel center
        var moneyRewardbonuses = pmcData.Bonuses.Where(bonus => bonus.Type == BonusType.QuestMoneyReward);

        // Get a total of the quest money reward percent bonuses
        var moneyRewardBonusPercent = moneyRewardbonuses.Aggregate(0D, (accumulate, bonus) => accumulate + bonus.Value ?? 0);

        // Calculate hideout management bonus as a percentage (up to 51% bonus)
        var hideoutManagementSkill = _profileHelper.GetSkillFromProfile(pmcData, SkillTypes.HideoutManagement);

        // 5100 becomes 0.51, add 1 to it, 1.51
        // We multiply the money reward bonuses by the hideout management skill multipler, giving the new result
        var hideoutManagementBonusMultiplier = (hideoutManagementSkill != null)
            ? (1 + hideoutManagementSkill.Progress / 1000)
            : 1;

        // e.g 15% * 1.4
        return moneyRewardBonusPercent * hideoutManagementBonusMultiplier ?? 1;
    }

    /**
     * Adjust quest money rewards by passed in multiplier
     * @param quest Quest to multiple money rewards
     * @param bonusPercent Percent to adjust money rewards by
     * @param questStatus Status of quest to apply money boost to rewards of
     * @returns Updated quest
     */
    public Quest ApplyMoneyBoost(Quest quest, double bonusPercent, QuestStatusEnum questStatus)
    {
        var clonedQuest = _cloner.Clone(quest);
        var rewards = (List<Reward>)clonedQuest.Rewards.GetType()
                          .GetProperties()
                          .FirstOrDefault(p => p.Name == questStatus.ToString())
                          .GetValue(quest.Rewards) ??
                      new();
        var currencyRewards = rewards.Where(
            r =>
                r.Type.ToString() == "Item" &&
                _paymentHelper.IsMoneyTpl(r.Items.FirstOrDefault().Template)
        );
        foreach (var reward in currencyRewards)
        {
            // Add % bonus to existing StackObjectsCount
            var rewardItem = reward.Items[0];
            var newCurrencyAmount = Math.Floor((rewardItem.Upd.StackObjectsCount ?? 0) * (1 + bonusPercent / 100));
            rewardItem.Upd.StackObjectsCount = (int)newCurrencyAmount;
            reward.Value = newCurrencyAmount;
        }

        return clonedQuest;
    }
}
