#include "adapters/hooks/player/PlayerHooks.h"
#include "adapters/hooks/Il2CppMethodHook.h"
#include "application/lifecycle/CallbackBarrier.h"

#include "adapters/il2cpp/Il2CppObjectInstance.h"
#include "adapters/il2cpp/Il2CppResolver.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "foundation/diagnostics/Logger.h"
#include "foundation/diagnostics/CrashDiagnostics.h"
#include "adapters/hooks/player/PlayerHookCallbacks.h"
#include "application/lifecycle/RuntimeState.h"
#include "application/lifecycle/RaidContext.h"
#include "adapters/hooks/HookDiagnostics.h"

namespace ta::adapters::hooks::player
{
    namespace
    {
        using FixedUpdateFn = void(*)(Il2CppObject*);
        FixedUpdateFn original_player_camera_controller_fixed_update = nullptr;

        using LocalGameStopFn = void(*)(Il2CppObject*, Il2CppString*, int, Il2CppString*, float);
        LocalGameStopFn original_local_game_stop = nullptr;

        bool installed = false;

        void hk_player_camera_controller_fixed_update(Il2CppObject* instance)
        {
            ta::foundation::diagnostics::crash::CallbackScope diagnostic_scope(
                "hook.player.fixed-update.enter",
                reinterpret_cast<std::uintptr_t>(instance),
                reinterpret_cast<std::uintptr_t>(original_player_camera_controller_fixed_update));
            ta::application::runtime::CallbackGuard callback_guard;
            ta::adapters::hooks::diagnostics::mark_player_fixed_update();

            if (ta::application::runtime::should_accept_callbacks() &&
                !ta::application::raid_context::teardown_in_progress())
            {
                ta::foundation::diagnostics::crash::set_stage(
                    "hook.player.fixed-update.dispatch",
                    reinterpret_cast<std::uintptr_t>(instance));
                ta::adapters::hooks::player::dispatch_fixed_update(instance);
            }
            else if (ta::application::raid_context::teardown_in_progress())
            {
                ta::foundation::diagnostics::crash::set_stage(
                    "hook.player.fixed-update.skipped-teardown",
                    reinterpret_cast<std::uintptr_t>(instance));
            }

            ta::foundation::diagnostics::crash::set_stage(
                "hook.player.fixed-update.original",
                reinterpret_cast<std::uintptr_t>(instance),
                reinterpret_cast<std::uintptr_t>(original_player_camera_controller_fixed_update));
            if (original_player_camera_controller_fixed_update)
                original_player_camera_controller_fixed_update(instance);
            ta::foundation::diagnostics::crash::set_stage("hook.player.fixed-update.complete");
        }

        void hk_local_game_stop(Il2CppObject* instance,
                                Il2CppString* profile_id,
                                int exit_status,
                                Il2CppString* exit_name,
                                float delay)
        {
            ta::foundation::diagnostics::crash::CallbackScope diagnostic_scope(
                "hook.player.local-game-stop.enter",
                reinterpret_cast<std::uintptr_t>(instance),
                static_cast<std::uintptr_t>(exit_status),
                reinterpret_cast<std::uintptr_t>(original_local_game_stop));
            ta::application::raid_context::begin_teardown();
            ta::foundation::diagnostics::crash::set_in_raid(false);
            ta::foundation::diagnostics::crash::write_event(
                "local-game-stop-hook",
                reinterpret_cast<std::uintptr_t>(profile_id),
                reinterpret_cast<std::uintptr_t>(exit_name),
                static_cast<std::uintptr_t>(exit_status));
            ta::application::runtime::CallbackGuard callback_guard;
            ta::adapters::hooks::diagnostics::mark_local_game_stop();

            if (ta::application::runtime::should_accept_callbacks())
            {
                ta::foundation::diagnostics::crash::set_stage(
                    "hook.player.local-game-stop.dispatch",
                    reinterpret_cast<std::uintptr_t>(instance),
                    static_cast<std::uintptr_t>(exit_status));
                exit_status = ta::adapters::hooks::player::dispatch_local_game_stop(exit_status);
            }

            ta::foundation::diagnostics::crash::set_stage(
                "hook.player.local-game-stop.original",
                reinterpret_cast<std::uintptr_t>(instance),
                static_cast<std::uintptr_t>(exit_status),
                reinterpret_cast<std::uintptr_t>(original_local_game_stop));
            if (original_local_game_stop)
                original_local_game_stop(instance, profile_id, exit_status, exit_name, delay);
            ta::foundation::diagnostics::crash::set_stage("hook.player.local-game-stop.complete");
        }
    }

    bool install()
    {
        if (installed) return true;

        const Il2CppImage* image = ta::adapters::il2cpp::resolve_image("Assembly-CSharp.dll");
        if (!image)
        {
            ta::foundation::logger::warn("Player hooks failed: Assembly-CSharp.dll was not found");
            return false;
        }

        bool ok = true;

        const Il2CppClass* player_camera_controller_class =
            ta::adapters::il2cpp::resolve_class(image, "EFT.CameraControl", "PlayerCameraController");

        if (player_camera_controller_class)
        {
            ok = ta::adapters::il2cpp::hook_method(
                player_camera_controller_class,
                "FixedUpdate",
                0,
                hk_player_camera_controller_fixed_update,
                &original_player_camera_controller_fixed_update) && ok;
        }
        else
        {
            ta::foundation::logger::error("Player hooks failed: EFT.CameraControl.PlayerCameraController was not found");
            ok = false;
        }

        const Il2CppClass* local_game = ta::adapters::il2cpp::resolve_class(image, "EFT", "LocalGame");
        if (local_game)
        {
            ok = ta::adapters::il2cpp::hook_method(
                local_game,
                "Stop",
                4,
                hk_local_game_stop,
                &original_local_game_stop) && ok;
        }
        else
        {
            ta::foundation::logger::warn("Player hooks failed: LocalGame was not found");
            ok = false;
        }

        installed = ok;
        return ok;
    }

    void reset()
    {
        ta::adapters::hooks::player::clear_callbacks();
        original_player_camera_controller_fixed_update = nullptr;
        original_local_game_stop = nullptr;
        installed = false;
    }
}
