#include "adapters/hooks/combat/AimbotHooks.h"
#include "adapters/hooks/Il2CppMethodHook.h"
#include "application/lifecycle/CallbackBarrier.h"

#include "adapters/eft/aimbot/AimbotDiagnostics.h"
#include "adapters/eft/aimbot/AimbotShotPolicy.h"
#include "adapters/eft/aimbot/AimbotTargetScanner.h"
#include "adapters/eft/aimbot/AimbotTargetState.h"
#include "adapters/il2cpp/Il2CppObjectInstance.h"
#include "adapters/il2cpp/Il2CppResolver.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "adapters/unity/bindings/UnityTypes.h"
#include "adapters/il2cpp/Il2CppCollections.h"
#include "adapters/unity/bindings/UnityApi.h"
#include "application/lifecycle/RuntimeState.h"

namespace ta::adapters::eft::aimbot::hooks
{
    using initiate_shot_sig = void(*)(Il2CppObject*, Il2CppObject*, Il2CppObject*, ta::adapters::unity::vector3, ta::adapters::unity::vector3,
                                      ta::adapters::unity::vector3, int, float);

    static initiate_shot_sig o_initiate_shot = nullptr;

    static bool is_usable_object(const Il2CppObject* object)
    {
        return object && object->klass;
    }

    static bool is_local_player_shot(Il2CppObject* hands_controller, const std::string& local_nickname)
    {
        if (!is_usable_object(hands_controller))
        {
            diag::warn_once("shot_null_controller", "InitiateShot called with null hands controller");
            return false;
        }

        if (local_nickname.empty())
        {
            diag::warn_once("shot_empty_local_name", "Skipping projectile relocation: local nickname is empty");
            return false;
        }

        ta::adapters::il2cpp::Il2CppObjectInstance hands_controller_instance(hands_controller);
        Il2CppObject* player_object = nullptr;
        if (!hands_controller_instance.try_get_field("_player", player_object) ||
            !is_usable_object(player_object))
        {
            diag::warn_once("shot_missing_player", "Skipping projectile relocation: FirearmController._player was not found");
            return false;
        }

        const std::string nickname = TargetScanner::read_nickname(ta::adapters::il2cpp::Il2CppObjectInstance(player_object));
        if (nickname.empty())
        {
            diag::warn_once("shot_empty_controller_name", "Skipping projectile relocation: shooter nickname is empty");
            return false;
        }

        return nickname == local_nickname;
    }

    static void hk_initiate_shot(Il2CppObject* instance, Il2CppObject* weapon, Il2CppObject* ammo,
                                 ta::adapters::unity::vector3 shot_position, ta::adapters::unity::vector3 shot_direction,
                                 ta::adapters::unity::vector3 fireport_position, int chamber_index, float overheat)
    {
        ta::application::runtime::CallbackGuard callback_guard;
        diag::info_every("shot_hook_called", 25, "InitiateShot hook called");

        if (!o_initiate_shot)
        {
            diag::error_once("shot_missing_original", "InitiateShot original pointer is null");
            return;
        }

        if (!ta::application::runtime::should_accept_callbacks())
        {
            o_initiate_shot(instance, weapon, ammo, shot_position, shot_direction, fireport_position, chamber_index,
                            overheat);
            return;
        }

        const TargetState target_state = TargetStateStore::instance().snapshot();
        if (!target_state.has_target)
        {
            diag::info_every("shot_no_target", 25, "InitiateShot hook has no target");
        }
        else if (is_local_player_shot(instance, target_state.local_nickname))
        {
            if (shot_policy::relocate_projectile(
                    target_state.has_target, target_state.target,
                    shot_position, shot_direction, fireport_position))
            {
                diag::info_every("shot_relocated", 10,
                                 "Relocating local projectile to current target");
            }
            else
            {
                diag::warn_once("shot_invalid_target",
                                "Skipping projectile relocation: target position is invalid");
            }
        }

        o_initiate_shot(instance, weapon, ammo, shot_position, shot_direction, fireport_position, chamber_index,
                        overheat);
    }

    bool install()
    {
        diag::info_once("install_begin", "Installing InitiateShot hook");

        Il2CppImage* image = ta::adapters::il2cpp::resolve_image("Assembly-CSharp.dll");
        if (!image)
        {
            diag::error_once("install_no_assembly", "Assembly-CSharp.dll was not found");
            return false;
        }

        const Il2CppClass* player_class = ta::adapters::il2cpp::resolve_class(image, "EFT", "Player");
        if (!player_class)
        {
            diag::error_once("install_no_player_class", "EFT.Player class was not found");
            return false;
        }

        const Il2CppClass* firearm_controller_class = ta::adapters::il2cpp::resolve_class_nested(player_class, "FirearmController");
        if (!firearm_controller_class)
        {
            diag::error_once("install_no_firearm_controller", "EFT.Player.FirearmController nested class was not found");
            return false;
        }

        if (!ta::adapters::il2cpp::hook_method(firearm_controller_class, "InitiateShot", 7, hk_initiate_shot, &o_initiate_shot) ||
            !o_initiate_shot)
        {
            diag::error_once("install_failed", "InitiateShot hook failed; original pointer is null");
            return false;
        }

        diag::info_once("install_ok", "InitiateShot hook installed");
        return true;
    }

    void uninstall()
    {
        o_initiate_shot = nullptr;
    }
}
