#include "adapters/eft/player/PlayerProgressionService.h"


#include "adapters/eft/bindings/PlayerAccess.h"
#include "adapters/il2cpp/ManagedRuntime.h"
#include "foundation/diagnostics/BuildOptions.h"
#include "foundation/diagnostics/Logger.h"

namespace ta::adapters::eft::player::runtime
{
    void PlayerProgressionService::run_queued_actions(const Il2CppClass* game_world_class,
                                                      ta::adapters::il2cpp::Il2CppObjectInstance& main_player,
                                                      const PlayerQueuedActions& actions)
    {
        Il2CppObject* profile_ptr = nullptr;

        if (actions.add_exp || actions.add_trader_rep)
            profile_ptr = PlayerAccess::get_profile(main_player);

        if (actions.add_exp)
            add_exp(profile_ptr, actions.exp_type, actions.exp_amount);

        if (actions.add_fence_rep)
            add_fence_rep(main_player, actions.fence_rep_amount, actions.fence_rep_source);

        if (actions.add_trader_rep)
            add_trader_rep(
                game_world_class,
                profile_ptr,
                actions.trader_index,
                actions.trader_name,
                actions.trader_rep_amount);

        if (actions.set_skill)
            set_skill(main_player, actions.skill_name, actions.skill_level);
    }

    void PlayerProgressionService::enable_instant_search(ta::adapters::il2cpp::Il2CppObjectInstance& main_player)
    {
        Il2CppObject* skills_ptr = PlayerAccess::get_skills(main_player);
        if (!skills_ptr) return;

        ta::adapters::il2cpp::Il2CppObjectInstance skill_manager(skills_ptr);
        Il2CppObject* attention_ptr = nullptr;
        if (!skill_manager.try_get_field("AttentionEliteLuckySearch", attention_ptr) || !attention_ptr)
            return;

        ta::adapters::il2cpp::Il2CppObjectInstance attention_elite_lucky_search(attention_ptr);
        attention_elite_lucky_search.set_field("Value", 100000.0f);
    }

    void PlayerProgressionService::add_exp(Il2CppObject* profile_ptr,
                                           const std::string& exp_type,
                                           const int exp_amount)
    {
        if (!profile_ptr)
        {
            ta::foundation::logger::warn("GAME", "add exp failed | reason=profile unavailable");
            return;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance profile(profile_ptr);
        Il2CppObject* eft_stats_ptr = PlayerAccess::get_eft_stats(profile);
        if (!eft_stats_ptr)
        {
            ta::foundation::logger::warn("GAME", "add exp failed | reason=EftStats unavailable");
            return;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance eft_stats(eft_stats_ptr);
        Il2CppObject* session_counters_ptr = nullptr;
        if (!eft_stats.try_get_field("SessionCounters", session_counters_ptr) || !session_counters_ptr)
        {
            ta::foundation::logger::warn("GAME", "add exp failed | reason=SessionCounters unavailable");
            return;
        }

        const Il2CppImage* assembly_csharp =
            ta::adapters::il2cpp::resolve_image("Assembly-CSharp.dll");
        const Il2CppClass* predefined_counters =
            assembly_csharp
                ? ta::adapters::il2cpp::resolve_class(
                    assembly_csharp,
                    "EFT.Counters",
                    "PredefinedCounters")
                : nullptr;
        if (!predefined_counters)
        {
            ta::foundation::logger::warn("GAME", "add exp failed | reason=PredefinedCounters unavailable");
            return;
        }

        Il2CppObject* reason = nullptr;
        if (!ta::adapters::il2cpp::try_get_static_field(predefined_counters, exp_type.c_str(), reason) ||
            !reason)
        {
            ta::foundation::logger::warn("GAME", "add exp failed | reason=counter unavailable type=" + exp_type);
            return;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance session_counters(session_counters_ptr);
        const auto add_int =
            session_counters.get_method<void(*)(Il2CppObject*, int, Il2CppObject*)>("AddInt", 2);
        if (!add_int)
        {
            ta::foundation::logger::warn("GAME", "add exp failed | reason=AddInt unavailable");
            return;
        }

        add_int(session_counters.get_instance(), exp_amount, reason);

        if (ta::foundation::diagnostics::build_options::verbose)
            ta::foundation::logger::info("GAME", "add exp completed");
    }

    void PlayerProgressionService::add_fence_rep(ta::adapters::il2cpp::Il2CppObjectInstance& main_player,
                                                 const float amount,
                                                 const int source)
    {
        Il2CppObject* loyalty_ptr = PlayerAccess::get_loyalty(main_player);
        if (!loyalty_ptr)
        {
            ta::foundation::logger::warn("GAME", "add fence reputation failed | reason=loyalty data unavailable");
            return;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance loyalty_data(loyalty_ptr);
        const auto kill_as_savage =
            loyalty_data.get_method<void(*)(Il2CppObject*, double, int)>("KillAsSavage", 2);
        if (!kill_as_savage)
        {
            ta::foundation::logger::warn("GAME", "add fence reputation failed | reason=KillAsSavage unavailable");
            return;
        }

        kill_as_savage(loyalty_data.get_instance(), amount, source);

        if (ta::foundation::diagnostics::build_options::verbose)
            ta::foundation::logger::info("GAME", "add fence reputation completed");
    }

    void PlayerProgressionService::add_trader_rep(const Il2CppClass* game_world_class,
                                                  Il2CppObject* profile_ptr,
                                                  const int trader_index,
                                                  const std::string& trader_name,
                                                  const float amount)
    {
        if (!profile_ptr)
        {
            ta::foundation::logger::warn("GAME", "add trader reputation failed | reason=profile unavailable");
            return;
        }

        const MethodInfo* try_get_method =
            PlayerAccess::resolve_profile_try_get_trader_info(game_world_class);
        if (!try_get_method || !try_get_method->methodPointer)
        {
            ta::foundation::logger::warn("GAME", "add trader reputation failed | reason=TryGetTraderInfo unavailable");
            return;
        }

        const char* trader_id = nullptr;
        if (!PlayerAccess::get_trader_id(trader_index, trader_id))
        {
            ta::foundation::logger::warn("GAME", "add trader reputation failed | reason=invalid selection");
            return;
        }

        const auto try_get_trader_info =
            reinterpret_cast<bool(*)(Il2CppObject*, Il2CppString*, Il2CppObject**)>(
                try_get_method->methodPointer);

        Il2CppObject* trader_info = nullptr;
        Il2CppString* trader_id_string = ta::adapters::il2cpp::il2cpp_string_new(trader_id);
        if (!try_get_trader_info(profile_ptr, trader_id_string, &trader_info) || !trader_info)
        {
            ta::foundation::logger::warn("GAME", "add trader reputation failed | reason=trader absent");
            return;
        }

        const MethodInfo* get_standing_method =
            ta::adapters::il2cpp::ManagedRuntime::method_or_null(trader_info->klass, "get_Standing", 0);
        const MethodInfo* set_standing_method =
            ta::adapters::il2cpp::ManagedRuntime::method_or_null(trader_info->klass, "SetStanding", 1);
        if (!get_standing_method || !get_standing_method->methodPointer ||
            !set_standing_method || !set_standing_method->methodPointer)
        {
            ta::foundation::logger::warn("GAME", "add trader reputation failed | reason=standing methods unavailable");
            return;
        }

        const auto get_standing =
            reinterpret_cast<double(*)(Il2CppObject*)>(get_standing_method->methodPointer);
        const auto set_standing =
            reinterpret_cast<void(*)(Il2CppObject*, double)>(set_standing_method->methodPointer);

        const double standing = get_standing(trader_info);
        set_standing(trader_info, amount + standing);

        if (ta::foundation::diagnostics::build_options::verbose)
            ta::foundation::logger::info("GAME", "add trader reputation completed | trader=" + trader_name);
    }

    void PlayerProgressionService::set_skill(ta::adapters::il2cpp::Il2CppObjectInstance& main_player,
                                             const std::string& skill_name,
                                             const int level)
    {
        Il2CppObject* skills_ptr = PlayerAccess::get_skills(main_player);
        if (!skills_ptr)
        {
            ta::foundation::logger::warn("GAME", "set skill failed | reason=controller unavailable");
            return;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance skill_controller(skills_ptr);
        Il2CppObject* skill_ptr = nullptr;
        if (!skill_controller.try_get_field(skill_name.c_str(), skill_ptr) || !skill_ptr)
        {
            if (ta::foundation::diagnostics::build_options::verbose)
                ta::foundation::logger::warn("GAME", "set skill failed | reason=skill unavailable name=" + skill_name);
            return;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance skill_instance(skill_ptr);
        const auto set_level =
            skill_instance.get_method<void(*)(Il2CppObject*, int)>("SetLevel", 1);
        if (!set_level)
        {
            ta::foundation::logger::warn("GAME", "set skill failed | reason=SetLevel unavailable name=" + skill_name);
            return;
        }

        set_level(skill_instance.get_instance(), level);

        if (ta::foundation::diagnostics::build_options::verbose)
        {
            ta::foundation::logger::info(
                "Set Skill: " + skill_name +
                " Level: " + std::to_string(level));
        }
    }
}
