#include "adapters/eft/quests/QuestRuntimeReader.h"

#include "adapters/eft/bindings/QuestAccess.h"
#include "adapters/eft/localization/EftLocalizationService.h"
#include "adapters/il2cpp/Il2CppCollections.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "adapters/il2cpp/ManagedRuntime.h"
#include "foundation/diagnostics/OnceLogger.h"

namespace
{
    void report_quest_warning(const char* key, const char* message)
    {
        ta::foundation::diagnostics::OnceLogger::warn("QUEST", key, message);
    }

    void report_quest_path(const char*, const char*)
    {
        ta::foundation::diagnostics::OnceLogger::trace(
            "QUEST",
            "compat-runtime",
            "compat.quest | name=Quest.get_Name category=QuestTemplate.get_IsMainQuest");
    }

    [[nodiscard]] Il2CppObject* object_getter(Il2CppObject* instance, const char* method_name)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<Il2CppObject*>(
            instance,
            method_name,
            ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        return result ? result.value() : nullptr;
    }

    [[nodiscard]] std::string string_getter(Il2CppObject* instance, const char* method_name)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<Il2CppString*>(
            instance,
            method_name,
            ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        return result && result.value()
            ? ta::adapters::il2cpp::conv_string(result.value())
            : std::string{};
    }
}

namespace ta::adapters::eft::quests
{
    bool QuestRuntimeReader::visit_quests(
        ta::adapters::il2cpp::Il2CppObjectInstance main_player,
        const Visitor& visitor)
    {
        constexpr int kMaxQuestCount = 4096;

        Il2CppObject* quest_controller_ptr = object_getter(
            main_player.get_instance(), "get_QuestController");
        if (!quest_controller_ptr)
        {
            report_quest_warning(
                "get-quest-controller-failed",
                "Quest read failed: get_QuestController unavailable or returned null");
            return false;
        }
        ta::adapters::il2cpp::Il2CppObjectInstance quest_controller(quest_controller_ptr);

        Il2CppObject* quest_book_ptr = object_getter(quest_controller_ptr, "get_Quests");
        if (!quest_book_ptr)
        {
            report_quest_warning(
                "get-quests-failed",
                "Quest read failed: get_Quests unavailable or returned null");
            return false;
        }

        const auto quest_list = ta::adapters::il2cpp::ManagedRuntime::read_field<ta::adapters::unity::list*>(
            quest_book_ptr,
            "_questsData",
            ta::adapters::il2cpp::LookupPolicy::ExactClass);
        if (!quest_list || !quest_list.value() || !quest_list.value()->m_p_list_array)
        {
            report_quest_warning(
                "quest-book-data-list-null",
                "Quest read failed: QuestBook._questsData is unavailable or null");
            return false;
        }

        const int capacity = static_cast<int>(quest_list.value()->m_p_list_array->max_length);
        const int count = quest_list.value()->m_i_size;
        if (count < 0 || count > capacity || count > kMaxQuestCount)
        {
            report_quest_warning(
                "quest-book-data-list-invalid",
                "Quest read failed: QuestBook._questsData size is invalid");
            return false;
        }

        auto** items = reinterpret_cast<Il2CppObject**>(&quest_list.value()->m_p_list_array->data);
        for (int index = 0; index < count; ++index)
        {
            Il2CppObject* quest_data = items[index];
            const auto quest_value = ta::adapters::il2cpp::ManagedRuntime::read_field<Il2CppObject*>(
                quest_data, "Quest", ta::adapters::il2cpp::LookupPolicy::ExactClass);
            if (!quest_value || !quest_value.value() || !quest_value.value()->klass)
                continue;

            Il2CppObject* quest_ptr = quest_value.value();
            const auto status_data = ta::adapters::il2cpp::ManagedRuntime::read_field<Il2CppObject*>(
                quest_ptr, "_statusData", ta::adapters::il2cpp::LookupPolicy::ExactClass);
            const auto template_value = ta::adapters::il2cpp::ManagedRuntime::read_field<Il2CppObject*>(
                quest_ptr, "_template", ta::adapters::il2cpp::LookupPolicy::ExactClass);
            if (!status_data || !status_data.value() || !template_value ||
                !template_value.value() || !template_value.value()->klass)
            {
                continue;
            }

            const auto status = ta::adapters::il2cpp::ManagedRuntime::read_field<int>(
                status_data.value(), "Status", ta::adapters::il2cpp::LookupPolicy::ExactClass);
            if (!status)
                continue;

            ta::adapters::il2cpp::Il2CppObjectInstance quest(quest_ptr);
            ta::adapters::il2cpp::Il2CppObjectInstance quest_template(template_value.value());
            const std::string id = bindings::QuestAccess::quest_id_string(quest_template);
            if (id.empty())
                continue;

            QuestRuntimeQuest runtime_quest{
                quest_controller,
                quest,
                quest_template,
                id,
                status.value()
            };
            visitor(runtime_quest);
        }
        return true;
    }

    ta::features::quests::contracts::QuestEntry QuestRuntimeReader::make_quest_entry(
        QuestRuntimeQuest& runtime_quest)
    {
        using ta::features::quests::contracts::QuestCategory;

        const std::string runtime_name = string_getter(
            runtime_quest.quest.get_instance(), "get_Name");

        const bool has_resolved_name = !runtime_name.empty() && runtime_name != runtime_quest.id;
        std::string display_name;
        if (has_resolved_name)
        {
            display_name = runtime_name;
            report_quest_path("quest-name-path-runtime", "Quest name path active: Quest.get_Name");
        }
        else
        {
            display_name = runtime_quest.id;
            ta::foundation::diagnostics::OnceLogger::warn(
                "QUEST", "quest-name-path-id",
                "quest.name unresolved | fallback=id category=unknown");
        }

        const std::string trader_id = bindings::QuestAccess::trader_id(runtime_quest.quest_template);
        QuestCategory category = QuestCategory::Unclassified;
        if (has_resolved_name)
        {
            if (bindings::QuestAccess::is_operational_quest(runtime_quest.quest, runtime_quest.quest_template))
            {
                category = QuestCategory::Operational;
            }
            else
            {
                bool is_main = false;
                if (bindings::QuestAccess::is_main_quest(runtime_quest.quest_template, is_main))
                    category = is_main ? QuestCategory::Main : QuestCategory::Side;
            }
        }

        return {
            runtime_quest.id,
            display_name,
            trader_id,
            trader_display_name(trader_id),
            category,
            {}
        };
    }

    std::string QuestRuntimeReader::trader_display_name(const std::string& id)
    {
        const std::string name =
            ta::adapters::eft::localization::EftLocalizationService::trader_name(id);
        if (!name.empty())
            return name;
        report_quest_warning(
            "quest-trader-name-unavailable",
            "quest.trader_name unresolved | fallback=Unknown Trader");
        return {};
    }
}
