#include "Resolvers/NetworkMetadataReader.h"

#include "Offsets.h"
#include "Memory/MemoryValidation.h"

#include <cstring>
#include <spdlog/spdlog.h>

bool NetworkMetadataReader::TryRead(VmmAccessor& mem,
                                    const RuntimeSession& session,
                                    NetworkMetadata& meta)
{
    const uint32_t pid = session.processId;

    // -----------------------------------------------------------------
    // 1. NetworkClient pointer
    // -----------------------------------------------------------------
    uint64_t probeAddr = session.networkManagerAddress + Offsets::Network::ManagerNetworkClient;
    spdlog::trace("NetworkMetadataReader: reading NetworkClient ptr at 0x{:X} "
                  "(networkManager=0x{:X} + 0x{:X})",
                  probeAddr,
                  session.networkManagerAddress,
                  Offsets::Network::ManagerNetworkClient);

    uint64_t networkClientAddr = 0;
    if (!mem.TryReadPointer(pid, probeAddr, networkClientAddr))
    {
        // Read the raw 8 bytes to see what value was there (may be 0 or an
        // out-of-range pointer that failed IsValidUserAddress).
        std::vector<uint8_t> raw;
        if (mem.ReadBytes(pid, probeAddr, sizeof(uint64_t), raw) && raw.size() == 8) {
            uint64_t rawVal = 0;
            std::memcpy(&rawVal, raw.data(), 8);
            spdlog::debug("NetworkMetadataReader: NetworkClient ptr invalid "
                          "(raw=0x{:X}) at 0x{:X}", rawVal, probeAddr);
        } else {
            spdlog::debug("NetworkMetadataReader: NetworkClient ptr unreadable at 0x{:X}",
                          probeAddr);
        }
        m_lastFailureReason = "Failed to read NetworkClient pointer";
        return false;
    }
    spdlog::debug("NetworkMetadataReader: NetworkClient=0x{:X}", networkClientAddr);

    // -----------------------------------------------------------------
    // 2. Server name  (ptr at networkClient + offset, then ArmaString)
    // -----------------------------------------------------------------
    uint64_t serverNamePtr = 0;
    if (!mem.TryReadPointer(pid,
                            networkClientAddr + Offsets::Network::ServerName,
                            serverNamePtr))
    {
        m_lastFailureReason = "Failed to read ServerName pointer";
        return false;
    }

    std::string serverName = mem.ReadArmaString(pid, serverNamePtr);
    if (!MemoryValidation::IsUsableString(serverName)) {
        m_lastFailureReason = "ServerName is not a usable string";
        return false;
    }

    // -----------------------------------------------------------------
    // 3. Game version  (ptr at networkClient + offset, then ArmaString)
    // -----------------------------------------------------------------
    uint64_t gameVersionPtr = 0;
    if (!mem.TryReadPointer(pid,
                            networkClientAddr + Offsets::Network::GameVersion,
                            gameVersionPtr))
    {
        m_lastFailureReason = "Failed to read GameVersion pointer";
        return false;
    }

    std::string gameVersion = mem.ReadArmaString(pid, gameVersionPtr);
    if (!MemoryValidation::IsUsableString(gameVersion)) {
        m_lastFailureReason = "GameVersion is not a usable string";
        return false;
    }

    // -----------------------------------------------------------------
    // 4. Map name  (optional — failure is non-fatal)
    // -----------------------------------------------------------------
    std::optional<std::string> mapName;
    {
        uint64_t mapNamePtr = 0;
        if (mem.TryReadPointer(pid,
                               networkClientAddr + Offsets::Network::MapName,
                               mapNamePtr))
        {
            std::string raw = mem.ReadArmaString(pid, mapNamePtr);
            if (MemoryValidation::IsUsableString(raw)) {
                mapName = std::move(raw);
            }
        }
    }

    // -----------------------------------------------------------------
    // 5. Commit
    // -----------------------------------------------------------------
    meta = NetworkMetadata{
        .serverName    = std::move(serverName),
        .gameVersion   = std::move(gameVersion),
        .serverMapName = std::move(mapName),
    };

    m_lastFailureReason.clear();
    return true;
}
