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

#include <algorithm>
#include <string>

#include "adapters/eft/player/PlayerMovementService.h"
#include "adapters/eft/player/PlayerProgressionService.h"
#include "adapters/hooks/player/PlayerHookCallbacks.h"
#include "foundation/diagnostics/OnceLogger.h"

namespace ta::adapters::eft::player::runtime
{
    namespace
    {
        void apply_weapon_effects(ta::adapters::il2cpp::Il2CppObjectInstance main_player,
                                  const bool no_malfunction,
                                  const bool infinite_ammo)
        {
            if (!no_malfunction && !infinite_ammo)
                return;

            Il2CppObject* player_object = main_player.get_instance();
            if (!player_object)
                return;

            const auto get_hands_controller = main_player.get_method<Il2CppObject*(*)(Il2CppObject*)>(
                "get_HandsController", 0);
            if (!get_hands_controller)
                return;

            Il2CppObject* hands_controller_object = get_hands_controller(player_object);
            if (!hands_controller_object || !hands_controller_object->klass || !hands_controller_object->klass->name)
                return;

            if (std::string(hands_controller_object->klass->name) != "FirearmController")
                return;

            ta::adapters::il2cpp::Il2CppObjectInstance firearm_controller(hands_controller_object);
            const auto get_item = firearm_controller.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Item", 0);
            if (!get_item)
                return;

            Il2CppObject* weapon_object = get_item(hands_controller_object);
            if (!weapon_object)
                return;

            ta::adapters::il2cpp::Il2CppObjectInstance weapon(weapon_object);
            const auto get_template = weapon.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Template", 0);
            if (!get_template)
                return;

            Il2CppObject* template_object = get_template(weapon_object);
            if (!template_object)
                return;
            ta::adapters::il2cpp::Il2CppObjectInstance templt(template_object);

            if (no_malfunction)
            {
                templt.set_field("AllowOverheat", false);
                templt.set_field("AllowJam", false);
                templt.set_field("AllowMisfire", false);
                templt.set_field("AllowSlide", false);
                templt.set_field("AllowFeed", false);
                templt.set_field("DurabilityBurnRatio", 0.0f);
            }

            if (!infinite_ammo)
                return;

            const auto get_current_magazine = weapon.get_method<Il2CppObject*(*)(Il2CppObject*)>(
                "GetCurrentMagazine", 0);
            const auto get_current_count = weapon.get_method<int(*)(Il2CppObject*)>(
                "GetCurrentMagazineCount", 0);
            const auto get_max_count = weapon.get_method<int(*)(Il2CppObject*)>(
                "GetMaxMagazineCount", 0);
            if (!get_current_magazine || !get_current_count || !get_max_count)
                return;

            Il2CppObject* mag_object = get_current_magazine(weapon_object);
            if (!mag_object)
                return;

            const int current_count = get_current_count(weapon_object);
            const int max_count = get_max_count(weapon_object);
            if (current_count == 0 || max_count <= 0)
                return;

            ta::adapters::il2cpp::Il2CppObjectInstance magazine(mag_object);
            const auto first_real_ammo_method = magazine.get_method<Il2CppObject*(*)(Il2CppObject*)>(
                "FirstRealAmmo", 0);
            if (!first_real_ammo_method)
                return;

            Il2CppObject* first_real_ammo_object = first_real_ammo_method(mag_object);
            if (!first_real_ammo_object)
                return;

            ta::adapters::il2cpp::Il2CppObjectInstance first_real_ammo(first_real_ammo_object);
            const auto get_ammo_template =
                first_real_ammo.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Template", 0);
            if (!get_ammo_template)
            {
                ta::foundation::diagnostics::OnceLogger::error(
                    "PLAYER",
                    "infinite-ammo-template-method",
                    "Infinite ammo failed: ammo get_Template unavailable");
                return;
            }

            Il2CppObject* ammo_template_object = get_ammo_template(first_real_ammo_object);
            if (!ammo_template_object)
            {
                ta::foundation::diagnostics::OnceLogger::error(
                    "PLAYER",
                    "infinite-ammo-template-null",
                    "Infinite ammo failed: ammo template is null");
                return;
            }

            ta::adapters::il2cpp::Il2CppObjectInstance ammo_template(ammo_template_object);
            int max_stack_size = 0;
            if (!ammo_template.try_get_field("StackMaxSize", max_stack_size))
            {
                ta::foundation::diagnostics::OnceLogger::error(
                    "PLAYER",
                    "infinite-ammo-stack-field",
                    "Infinite ammo failed: ammo template StackMaxSize unavailable");
                return;
            }
            if (max_stack_size <= 0)
            {
                ta::foundation::diagnostics::OnceLogger::error(
                    "PLAYER",
                    "infinite-ammo-stack-invalid",
                    "Infinite ammo failed: ammo template StackMaxSize is invalid");
                return;
            }

            first_real_ammo.set_field(
                "StackObjectsCount",
                (std::min)(max_count, max_stack_size));
        }
    }

    void PlayerRuntimeService::update(const Il2CppClass* game_world_class,
                                      ta::adapters::il2cpp::Il2CppObjectInstance& main_player,
                                      const PlayerSettings& settings,
                                      const PlayerQueuedActions& queued_actions)
    {
        vitals_service_.apply(main_player, settings);

        if (settings.instant_search)
            PlayerProgressionService::enable_instant_search(main_player);

        PlayerProgressionService::run_queued_actions(game_world_class, main_player, queued_actions);
        apply_weapon_effects(main_player, settings.no_malfunction, settings.infinite_ammo);

        ta::adapters::eft::player::movement::configure_speed(settings.speed_hack, settings.speed);
        const int fly_speed = settings.speed_hack ? settings.speed : 1;
        ta::adapters::eft::player::movement::configure_fly(
            settings.fly_mode, fly_speed, settings.fly_up_hotkey, settings.fly_down_hotkey);
        ta::adapters::eft::player::movement::apply_fly_mode(main_player);
        ta::adapters::eft::player::movement::configure_survival(settings.always_survive);
        ta::adapters::hooks::player::set_fixed_update_callback(&ta::adapters::eft::player::movement::apply_speed_hack);
        ta::adapters::hooks::player::set_local_game_stop_callback(&ta::adapters::eft::player::movement::apply_survival);
    }
}
