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

#include <algorithm>
#include <string>
#include <Windows.h>

#include "adapters/eft/bindings/PlayerAccess.h"
#include "adapters/il2cpp/ManagedRuntime.h"
#include "adapters/unity/bindings/UnityTypes.h"
#include "adapters/unity/bindings/UnityApi.h"
#include "foundation/diagnostics/Logger.h"

namespace ta::adapters::eft::player::movement
{
    namespace
    {
        bool speed_enabled = false;
        int speed_iterations = 2;
        bool fly_enabled = false;
        float fly_speed_units_per_second = 1.0f;
        int fly_up_hotkey = 0x20;
        int fly_down_hotkey = 0x11;
        ULONGLONG last_fly_update_ms = 0;
        bool force_survive = false;
        bool fly_logged_missing_freefall = false;

        float fly_delta_time_seconds()
        {
            const ULONGLONG now = GetTickCount64();
            if (!last_fly_update_ms)
            {
                last_fly_update_ms = now;
                return 0.0f;
            }

            const ULONGLONG elapsed_ms = now - last_fly_update_ms;
            last_fly_update_ms = now;

            // Clamp to avoid a large jump after a stall or load hitch.
            return (std::min)(0.1f, static_cast<float>(elapsed_ms) / 1000.0f);
        }

        bool try_set_instance_field_float(Il2CppObject* instance,
                                          const char* field_name,
                                          const float value,
                                          bool& logged_missing,
                                          const char* log_name)
        {
            if (!instance || !instance->klass)
                return false;

            if (!ta::adapters::il2cpp::ManagedRuntime::write_field(instance, field_name, value))
            {
                if (!logged_missing)
                {
                    logged_missing = true;
                    ta::foundation::logger::warn(std::string("Fly Mode disabled for this frame: field not found: ") + log_name);
                }
                return false;
            }
            return true;
        }
    }

    void configure_speed(const bool enabled, const int iterations)
    {
        speed_enabled = enabled;
        speed_iterations = (std::max)(1, iterations);
    }

    void configure_fly(const bool enabled, const int speed, const int up_hotkey, const int down_hotkey)
    {
        fly_enabled = enabled;
        fly_speed_units_per_second = static_cast<float>((std::max)(1, speed));
        fly_up_hotkey = up_hotkey;
        fly_down_hotkey = down_hotkey;
        if (!enabled)
        {
            fly_speed_units_per_second = 1.0f;
            last_fly_update_ms = 0;
        }
    }

    void configure_survival(const bool enabled)
    {
        force_survive = enabled;
    }

    void apply_speed_hack(Il2CppObject* instance)
    {
        if (!speed_enabled || !instance) return;

        ta::adapters::il2cpp::Il2CppObjectInstance controller_instance(instance);

        const auto get_player =
            controller_instance.get_method<Il2CppObject*(*)(Il2CppObject*)>("get_Player", 0);
        Il2CppObject* player_ptr = get_player ? get_player(instance) : nullptr;
        if (!player_ptr) return;

        ta::adapters::il2cpp::Il2CppObjectInstance main_player(player_ptr);
        Il2CppObject* character_controller_ptr = nullptr;
        if (!main_player.try_get_field("_characterController", character_controller_ptr) ||
            !character_controller_ptr)
        {
            return;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance character_controller(character_controller_ptr);
        const auto get_velocity =
            character_controller.get_method<ta::adapters::unity::vector3(*)(Il2CppObject*)>("get_velocity", 0);
        const auto move =
            character_controller.get_method<int(*)(Il2CppObject*, ta::adapters::unity::vector3, float)>("Move", 2);
        if (!get_velocity || !move) return;

        ta::adapters::unity::vector3 velocity = get_velocity(character_controller.get_instance());
        velocity.x /= 100.0f;
        velocity.y = 0.0f;
        velocity.z /= 100.0f;

        for (int i = 0; i < speed_iterations; ++i)
            move(character_controller.get_instance(), velocity, 100.0f);
    }

    void apply_fly_mode(ta::adapters::il2cpp::Il2CppObjectInstance& main_player)
    {
        if (!fly_enabled)
            return;

        const float dt = fly_delta_time_seconds();
        if (dt <= 0.0f)
            return;

        Il2CppObject* movement_context_ptr = ta::adapters::eft::player::runtime::PlayerAccess::get_movement_context(main_player);
        if (!movement_context_ptr)
            return;

        Il2CppObject* player_game_object = ta::adapters::unity::component_get_game_object(main_player.get_instance());
        Il2CppObject* player_transform = ta::adapters::unity::gameobject_get_transform(player_game_object);
        if (!player_transform)
            return;

        if (!try_set_instance_field_float(movement_context_ptr, "FreefallTime", 0.0f,
            fly_logged_missing_freefall, "MovementContext.FreefallTime"))
            return;

        const bool up_pressed = fly_up_hotkey > 0 && (GetAsyncKeyState(fly_up_hotkey) & 0x8000) != 0;
        const bool down_pressed = fly_down_hotkey > 0 && (GetAsyncKeyState(fly_down_hotkey) & 0x8000) != 0;
        if (up_pressed == down_pressed)
            return;

        if (up_pressed && !try_set_instance_field_float(movement_context_ptr, "FreefallTime", -0.4f,
            fly_logged_missing_freefall, "MovementContext.FreefallTime"))
            return;

        ta::adapters::il2cpp::Il2CppObjectInstance transform_instance(player_transform);
        const auto get_up = transform_instance.get_method<ta::adapters::unity::vector3(*)(Il2CppObject*)>("get_up", 0);
        if (!get_up)
            return;
        const ta::adapters::unity::vector3 up = get_up(player_transform);

        constexpr float vertical_speed_multiplier = 2.0f;
        const float direction = up_pressed ? vertical_speed_multiplier : -vertical_speed_multiplier;
        const float distance = fly_speed_units_per_second * dt * direction;

        ta::adapters::unity::vector3 pos = ta::adapters::unity::transform_get_pos(player_transform);
        pos.x += up.x * distance;
        pos.y += up.y * distance;
        pos.z += up.z * distance;
        ta::adapters::unity::transform_set_pos(player_transform, pos);
    }

    int apply_survival(const int exit_status)
    {
        if (force_survive) return 0;
        return exit_status;
    }
}
