#include "adapters/eft/localization/EftLocalizationService.h"

#include <chrono>
#include <cstring>
#include <mutex>
#include <optional>
#include <unordered_map>

#include "adapters/il2cpp/Il2CppResolver.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/ManagedRuntime.h"
#include "adapters/il2cpp/Il2CppString.h"
#include "foundation/diagnostics/OnceLogger.h"

namespace
{
    using ta::adapters::eft::localization::EftStringCase;
    using LocalizedMethod = Il2CppString*(*)(Il2CppString*, int, const MethodInfo*);
    using Clock = std::chrono::steady_clock;

    struct CachedText
    {
        std::string value;
        Clock::time_point expires_at{};
    };

    constexpr auto kValueCacheLifetime = std::chrono::seconds(30);
    constexpr auto kMissingCacheLifetime = std::chrono::seconds(5);

    std::mutex g_mutex;
    Il2CppDomain* g_domain = nullptr;
    const MethodInfo* g_localized_method = nullptr;
    Clock::time_point g_next_method_resolve{};
    std::unordered_map<std::string, CachedText> g_localized_cache;

    std::string cache_key(const std::string& key, const EftStringCase string_case)
    {
        std::string result;
        result.reserve(key.size() + 2);
        result.push_back(static_cast<char>('0' + static_cast<int>(string_case)));
        result.push_back(':');
        result.append(key);
        return result;
    }

    const Il2CppImage* find_image_quiet(Il2CppDomain* domain, const char* image_name)
    {
        if (!domain)
            return nullptr;

        size_t assembly_count = 0;
        const Il2CppAssembly** assemblies =
            ta::adapters::il2cpp::il2cpp_domain_get_assemblies(domain, &assembly_count);
        if (!assemblies)
            return nullptr;

        for (size_t index = 0; index < assembly_count; ++index)
        {
            const Il2CppImage* image = assemblies[index] ? assemblies[index]->image : nullptr;
            if (image && image->name && std::strcmp(image->name, image_name) == 0)
                return image;
        }
        return nullptr;
    }

    void synchronize_domain_locked(Il2CppDomain* domain)
    {
        if (domain == g_domain)
            return;
        g_domain = domain;
        g_localized_method = nullptr;
        g_next_method_resolve = {};
        g_localized_cache.clear();
    }

    const MethodInfo* resolve_localized_method_locked(Il2CppDomain* domain)
    {
        synchronize_domain_locked(domain);
        if (g_localized_method && g_localized_method->methodPointer)
            return g_localized_method;

        const auto now = Clock::now();
        if (now < g_next_method_resolve)
            return nullptr;
        g_next_method_resolve = now + std::chrono::seconds(5);

        const Il2CppImage* assembly_csharp = find_image_quiet(domain, "Assembly-CSharp.dll");
        if (!assembly_csharp)
        {
            ta::foundation::diagnostics::OnceLogger::warn(
                "RESOURCE",
                "eft-localization-assembly-missing",
                "localization.resolve | stage=assembly path=Assembly-CSharp.dll status=missing");
            return nullptr;
        }

        ta::foundation::diagnostics::OnceLogger::info(
            "RESOURCE",
            "eft-localization-assembly-available",
            "localization.resolve | stage=assembly path=Assembly-CSharp.dll status=available");

        const Il2CppClass* localization_extensions =
            ta::adapters::il2cpp::il2cpp_class_from_name(
                assembly_csharp, "EFT", "LocalizationExtensions");
        if (!localization_extensions)
        {
            ta::foundation::diagnostics::OnceLogger::warn(
                "RESOURCE",
                "eft-localization-class-missing",
                "localization.resolve | stage=class path=EFT.LocalizationExtensions status=missing");
            return nullptr;
        }

        ta::foundation::diagnostics::OnceLogger::info(
            "RESOURCE",
            "eft-localization-class-available",
            "localization.resolve | stage=class path=EFT.LocalizationExtensions status=available");

        const MethodInfo* method = ta::adapters::il2cpp::ManagedRuntime::method_or_null(
            localization_extensions,
            "Localized",
            2,
            ta::adapters::il2cpp::LookupPolicy::ExactClass);
        if (!method || !method->methodPointer)
        {
            ta::foundation::diagnostics::OnceLogger::warn(
                "RESOURCE",
                "eft-localization-method-missing",
                "localization.resolve | stage=method path=EFT.LocalizationExtensions.Localized argc=2 status=missing");
            return nullptr;
        }

        ta::foundation::diagnostics::OnceLogger::info(
            "RESOURCE",
            "eft-localization-method-selected",
            "localization.resolve | stage=method path=EFT.LocalizationExtensions.Localized argc=2 status=selected");

        g_localized_method = method;
        return g_localized_method;
    }

    std::optional<std::string> query_game_localization(
        const std::string& key,
        const EftStringCase string_case)
    {
        Il2CppDomain* domain = ta::adapters::il2cpp::il2cpp_domain_get();
        const MethodInfo* method = nullptr;
        {
            std::lock_guard lock(g_mutex);
            method = resolve_localized_method_locked(domain);
        }
        if (!method)
            return std::nullopt;

        Il2CppString* managed_key = ta::adapters::il2cpp::il2cpp_string_new(key.c_str());
        if (!managed_key)
            return std::nullopt;

        const auto localized = reinterpret_cast<LocalizedMethod>(method->methodPointer);
        Il2CppString* managed_value = localized(
            managed_key, static_cast<int>(string_case), method);
        if (!managed_value)
            return std::string{};

        std::string value = ta::adapters::il2cpp::conv_string(managed_value);
        if (value.empty() || value == key)
            return std::string{};
        return value;
    }

    std::string exact_text(const std::string& key, const EftStringCase string_case)
    {
        if (key.empty())
            return {};

        const std::string lookup_key = cache_key(key, string_case);
        const auto now = Clock::now();
        {
            std::lock_guard lock(g_mutex);
            synchronize_domain_locked(ta::adapters::il2cpp::il2cpp_domain_get());
            const auto found = g_localized_cache.find(lookup_key);
            if (found != g_localized_cache.end() && found->second.expires_at > now)
                return found->second.value;
            if (found != g_localized_cache.end())
                g_localized_cache.erase(found);
        }

        std::optional<std::string> value = query_game_localization(key, string_case);
        if (!value)
            return {};

        {
            std::lock_guard lock(g_mutex);
            g_localized_cache.insert_or_assign(
                lookup_key,
                CachedText{*value, now + (value->empty() ? kMissingCacheLifetime : kValueCacheLifetime)});
        }
        return *value;
    }
}

namespace ta::adapters::eft::localization
{
    std::string EftLocalizationService::trader_name(const std::string& trader_id)
    {
        if (trader_id.empty())
            return {};
        return exact_text(trader_id + " Nickname", EftStringCase::None);
    }


    std::string EftLocalizationService::condition_text(
        const std::string& condition_id,
        const std::string& quest_id)
    {
        if (condition_id.empty() || quest_id.empty())
            return {};
        return exact_text(quest_id + " " + condition_id, EftStringCase::None);
    }

    std::string EftLocalizationService::item_name(const std::string& template_id)
    {
        return exact_text(template_id + " Name", EftStringCase::None);
    }

    std::string EftLocalizationService::item_short_name(const std::string& template_id)
    {
        return exact_text(template_id + " ShortName", EftStringCase::None);
    }


    std::string EftLocalizationService::localized_text(
        const std::string& key,
        const EftStringCase string_case)
    {
        return exact_text(key, string_case);
    }

    void EftLocalizationService::invalidate_language_cache() noexcept
    {
        std::lock_guard lock(g_mutex);
        g_localized_cache.clear();
    }

    void EftLocalizationService::reset_session() noexcept
    {
        std::lock_guard lock(g_mutex);
        g_domain = nullptr;
        g_localized_method = nullptr;
        g_next_method_resolve = {};
        g_localized_cache.clear();
    }
}
