#include "adapters/eft/quests/conditions/QuestConditionInterop.h"

#include <cmath>
#include <cstring>

#include "adapters/eft/common/ManagedObjectAccess.h"
#include "adapters/il2cpp/Il2CppCollections.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "adapters/il2cpp/ManagedRuntime.h"
#include "adapters/unity/bindings/UnityTypes.h"
#include "foundation/diagnostics/OnceLogger.h"

namespace
{
    [[nodiscard]] std::string class_name(Il2CppObject* object)
    {
        return object && object->klass && object->klass->name
            ? object->klass->name
            : "<unknown>";
    }

    template <typename TValue>
    [[nodiscard]] std::optional<TValue> read_quiet(
        Il2CppObject* object,
        const char* name,
        const ta::adapters::il2cpp::LookupPolicy policy =
            ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::read_field<TValue>(object, name, policy);
        return result ? std::optional<TValue>(result.value()) : std::nullopt;
    }

    template <typename TValue>
    [[nodiscard]] std::optional<TValue> read_required(Il2CppObject* object, const char* name)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::read_field<TValue>(
            object, name, ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        if (result)
            return result.value();

        ta::foundation::diagnostics::OnceLogger::warn(
            "QUEST",
            "quest-field:" + class_name(object) + ":" + name,
            "Quest field missing: " + class_name(object) + "." + name);
        return std::nullopt;
    }

    [[nodiscard]] const MethodInfo* required_method(
        Il2CppObject* object,
        const char* name,
        const int parameter_count)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::method(
            object ? object->klass : nullptr,
            name,
            parameter_count,
            ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        if (result)
            return result.value();

        ta::foundation::diagnostics::OnceLogger::warn(
            "QUEST",
            "quest-method:" + class_name(object) + ":" + name,
            "Quest method missing: " + class_name(object) + "." + name);
        return nullptr;
    }

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

    void report_path(const char*, const char*)
    {
        ta::foundation::diagnostics::OnceLogger::trace(
            "QUEST",
            "compat-conditions",
            "compat.quest.conditions | manager=Quest.get_ConditionCountersManager counters=ConditionCountersManager.Counters|Profile.TaskConditionCounters");
    }
}

namespace ta::adapters::eft::quests
{
    std::vector<QuestTaskConditionCounter> QuestConditionInterop::collect_counters(
        ta::adapters::il2cpp::Il2CppObjectInstance& quest,
        ta::adapters::il2cpp::Il2CppObjectInstance main_player)
    {
        std::vector<QuestTaskConditionCounter> result;
        Il2CppObject* quest_object = quest.get_instance();
        if (!quest_object)
            return result;

        const auto append_counter = [&](Il2CppObject* counter)
        {
            const auto conditional = read_quiet<Il2CppObject*>(counter, "Conditional");
            const auto condition = read_quiet<Il2CppObject*>(counter, "Template");
            if (!conditional || !condition || !conditional.value() ||
                conditional.value() != quest_object || !condition.value())
            {
                return;
            }

            ta::adapters::eft::common::EftMongoId id{};
            if (condition_id(condition.value(), id))
                result.push_back({counter, condition.value(), id});
        };

        if (Il2CppObject* manager = invoke_object_quiet(quest_object, "get_ConditionCountersManager"))
        {
            report_path(
                "condition-manager-path-method",
                "Quest condition manager path active: Quest.get_ConditionCountersManager");

            if (const auto counters = read_quiet<ta::adapters::unity::list*>(manager, "Counters"))
            {
                ta::adapters::eft::common::for_each_list_object(
                    counters.value(),
                    [&](Il2CppObject* counter, int) { append_counter(counter); });
                if (!result.empty())
                {
                    report_path(
                        "condition-counter-path-manager",
                        "Quest counter source active: ConditionCountersManager.Counters");
                    return result;
                }
            }
        }

        Il2CppObject* profile = invoke_object_quiet(main_player.get_instance(), "get_Profile");
        if (!profile)
        {
            ta::foundation::diagnostics::OnceLogger::warn(
                "QUEST", "condition-counter-path-none",
                "Quest counters unresolved: manager source empty and Player.get_Profile unavailable");
            return result;
        }

        using CounterDictionary = ta::adapters::unity::dict<
            ta::adapters::eft::common::EftMongoId,
            Il2CppObject*>;
        const auto counters = read_quiet<CounterDictionary*>(profile, "TaskConditionCounters");
        if (!counters || !counters.value() || !counters.value()->m_p_entries)
        {
            ta::foundation::diagnostics::OnceLogger::warn(
                "QUEST", "condition-profile-counters-missing",
                "Quest counters unresolved: Profile.TaskConditionCounters unavailable");
            return result;
        }

        const auto* entries = counters.value()->get_entry();
        for (int index = 0; entries && index < counters.value()->m_i_count; ++index)
        {
            if (entries[index].m_i_hash_code >= 0)
                append_counter(entries[index].m_t_value);
        }
        if (!result.empty())
        {
            report_path(
                "condition-counter-path-profile",
                "Quest counter source active: Profile.TaskConditionCounters");
        }
        return result;
    }

    std::string QuestConditionInterop::mongo_id_string(
        const ta::adapters::eft::common::EftMongoId& id)
    {
        return ta::adapters::eft::common::mongo_id_string(id);
    }

    bool QuestConditionInterop::same_mongo_id(
        const ta::adapters::eft::common::EftMongoId& left,
        const ta::adapters::eft::common::EftMongoId& right)
    {
        return ta::adapters::eft::common::same_mongo_id(left, right);
    }

    bool QuestConditionInterop::condition_id(
        Il2CppObject* condition,
        ta::adapters::eft::common::EftMongoId& id)
    {
        const auto value = read_required<ta::adapters::eft::common::EftMongoId>(
            condition, "<id>k__BackingField");
        if (!value)
            return false;
        id = value.value();
        return true;
    }

    Il2CppObject* QuestConditionInterop::completed_conditions(
        ta::adapters::il2cpp::Il2CppObjectInstance& quest)
    {
        const auto status = read_required<Il2CppObject*>(quest.get_instance(), "_statusData");
        if (!status || !status.value())
        {
            ta::foundation::diagnostics::OnceLogger::warn(
                "QUEST", "quest-status-data-null",
                "Quest condition reader: _statusData is null.");
            return nullptr;
        }

        const auto completed = read_required<Il2CppObject*>(status.value(), "CompletedConditions");
        if (!completed || !completed.value())
        {
            ta::foundation::diagnostics::OnceLogger::warn(
                "QUEST", "quest-completed-null",
                "Quest condition reader: CompletedConditions is null.");
            return nullptr;
        }
        return completed.value();
    }

    std::optional<bool> QuestConditionInterop::contains_completed(
        Il2CppObject* completed_set,
        const ta::adapters::eft::common::EftMongoId& condition_id)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<
            bool,
            ta::adapters::eft::common::EftMongoId>(
                completed_set,
                "Contains",
                ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses,
                condition_id);
        return result ? std::optional<bool>(result.value()) : std::nullopt;
    }

    std::optional<bool> QuestConditionInterop::add_completed(
        Il2CppObject* completed_set,
        const ta::adapters::eft::common::EftMongoId& condition_id)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke_with_method_info<
            bool,
            ta::adapters::eft::common::EftMongoId>(
                completed_set,
                "Add",
                ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses,
                condition_id);
        return result ? std::optional<bool>(result.value()) : std::nullopt;
    }

    std::optional<int> QuestConditionInterop::condition_target(Il2CppObject* condition)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke<float>(
            condition,
            "get_value",
            ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        if (!result)
            return std::nullopt;
        return result.value() <= 0.0f ? 0 : static_cast<int>(std::ceil(result.value()));
    }

    std::optional<int> QuestConditionInterop::counter_value(Il2CppObject* counter)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke<int>(
            counter,
            "get_Value",
            ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        if (!result)
            return std::nullopt;
        return result.value() > 0 ? result.value() : 0;
    }

    bool QuestConditionInterop::set_counter_value(Il2CppObject* counter, const int value)
    {
        const MethodInfo* setter = required_method(counter, "set_Value", 1);
        if (!setter)
            return false;
        reinterpret_cast<void(*)(Il2CppObject*, int)>(setter->methodPointer)(counter, value);
        return true;
    }

    std::string QuestConditionInterop::formatted_description(Il2CppObject* condition)
    {
        const auto result = ta::adapters::il2cpp::ManagedRuntime::invoke<Il2CppString*>(
            condition,
            "get_FormattedDescription",
            ta::adapters::il2cpp::LookupPolicy::IncludeBaseClasses);
        return result && result.value()
            ? ta::adapters::il2cpp::conv_string(result.value())
            : std::string{};
    }

    std::string QuestConditionInterop::condition_type(Il2CppObject* condition)
    {
        if (!condition || !condition->klass || !condition->klass->name)
            return {};
        std::string name = condition->klass->name;
        constexpr const char* prefix = "Condition";
        if (name.rfind(prefix, 0) == 0)
            name.erase(0, std::strlen(prefix));
        return name;
    }

    std::vector<Il2CppObject*> QuestConditionInterop::display_conditions(Il2CppObject* condition)
    {
        if (!condition)
            return {};
        if (condition_type(condition) != "CounterCreator")
            return {condition};

        Il2CppObject* conditions = invoke_object_quiet(condition, "get_Conditions");
        if (!conditions)
        {
            ta::foundation::diagnostics::OnceLogger::warn(
                "QUEST", "counter-creator-path-parent",
                "CounterCreator.get_Conditions unavailable; using the parent condition");
            return {condition};
        }

        std::vector<Il2CppObject*> result;
        ta::adapters::eft::common::for_each_list_object(
            reinterpret_cast<ta::adapters::unity::list*>(conditions),
            [&](Il2CppObject* child, int)
            {
                if (child)
                    result.push_back(child);
            });
        if (result.empty())
            return {condition};
        return result;
    }

    std::string QuestConditionInterop::primary_target_id(Il2CppObject* condition)
    {
        for (Il2CppObject* displayed : display_conditions(condition))
        {
            const auto targets = read_quiet<Il2CppArray*>(displayed, "target");
            if (!targets || !targets.value() || targets.value()->max_length == 0)
                continue;
            Il2CppString* target = reinterpret_cast<Il2CppString**>(&targets.value()->data)[0];
            if (target)
                return ta::adapters::il2cpp::conv_string(target);
        }
        return {};
    }
}
