#include "memory/registry_live_label.hpp"

#include "memory/game_layout.hpp"
#include "memory/pointer_chain_resolver.hpp"
#include "memory/vitals_fingerprint.hpp"

#include <cstdio>
#include <ctime>
#include <fstream>
#include <sstream>

namespace registry_live_label {
namespace {

std::string NowStamp() {
    std::time_t now = std::time(nullptr);
    std::tm local{};
    localtime_s(&local, &now);
    char buf[32] = {};
    std::strftime(buf, sizeof(buf), "%d/%m/%Y %H:%M:%S", &local);
    return buf;
}

void FillLifeHit(ProbeHit& hit, const vitals_fingerprint::LifeMatch& life) {
    hit.lifeMatched = true;
    hit.playerMaxHp = life.maxHP;
    hit.esOffset = life.es.offset;
    hit.wardOffset = life.ward.offset;
    hit.manaOffset = life.mana.offset;
    hit.spiritOffset = life.spirit.offset;
    hit.esMax = life.es.max;
    hit.wardMax = life.ward.max;
    hit.manaMax = life.mana.max;
    hit.spiritCur = life.spirit.cur;
    hit.spiritMax = life.spirit.max;
}

void ProbeHeapCandidate(PointerChainResolver& resolver,
                        IMemoryReader& reader,
                        ProbeHit& hit,
                        bool simulated,
                        const vitals_fingerprint::Expected* expected) {
    if (hit.heapPtr == 0 || !PointerChainResolver::IsValidAddress(hit.heapPtr)) {
        return;
    }

    const bool needFingerprint = expected && expected->Enabled() && !simulated;
    if (needFingerprint) {
        vitals_fingerprint::LifeMatch life;
        if (vitals_fingerprint::FindLifeFromInGame(reader, hit.heapPtr, *expected, life)) {
            FillLifeHit(hit, life);
            hit.lifeDescribe = vitals_fingerprint::Describe(life);
            hit.validInGame = true;
            hit.playerResolved = true;
        }
        uintptr_t entityMap = 0;
        uint32_t cap = 0;
        uint32_t count = 0;
        if (resolver.ResolveEntityMap(hit.heapPtr, entityMap, cap, count)) {
            hit.entityMapResolved = true;
            hit.entityMapCap = cap;
            hit.entityMapCount = count;
        }
        return;
    }

    if (!resolver.ValidateInGameState(hit.heapPtr)) {
        return;
    }
    hit.validInGame = true;

    ResolvedPlayerState player{};
    if (resolver.ResolvePlayer(hit.heapPtr, player)) {
        hit.playerResolved = true;
        hit.playerMaxHp = player.maxHP;
    }

    uintptr_t entityMap = 0;
    uint32_t cap = 0;
    uint32_t count = 0;
    if (resolver.ResolveEntityMap(hit.heapPtr, entityMap, cap, count)) {
        hit.entityMapResolved = true;
        hit.entityMapCap = cap;
        hit.entityMapCount = count;
    }
}

void PushRelative(Report& out, const char* target, int offset, int index,
                  const std::string& evidence) {
    if (offset < 0) {
        return;
    }
    LabelAssignment label;
    label.target = target;
    label.kind = "relative_offset";
    label.index = index;
    label.rva = static_cast<uintptr_t>(offset);
    label.evidence = evidence;
    out.labels.push_back(std::move(label));
}

void MaybeAssignInGame(Report& out, const ProbeHit& hit, bool simulated,
                       const vitals_fingerprint::Expected* expected) {
    const bool needFingerprint = expected && expected->Enabled() && !simulated;
    if (needFingerprint) {
        if (!hit.lifeMatched || hit.rva == 0) {
            return;
        }
        LabelAssignment label;
        label.target = "InGameState";
        label.kind = hit.kind;
        label.index = hit.index;
        label.rva = hit.rva;
        label.evidence = "LifeFingerprint CI+ES+Ward+Mana+Spirit";
        PushRelative(out, "LocalPlayer", 0x10, hit.index,
                     "InGameState+0x10 + LifeFingerprint");
        PushRelative(out, "EnergyShield", hit.esOffset, hit.index,
                     "LifeComponent ES pair");
        PushRelative(out, "Ward", hit.wardOffset, hit.index,
                     "LifeComponent Ward pair");
        PushRelative(out, "Mana", hit.manaOffset, hit.index,
                     "LifeComponent Mana pair");
        PushRelative(out, "Spirit", hit.spiritOffset, hit.index,
                     "LifeComponent Spirit pair");
        if (hit.entityMapResolved) {
            label.evidence += "+ResolveEntityMap";
            if (out.entityMapOffset == 0) {
                out.entityMapOffset = game_layout::kEntityMapRootOffset;
            }
            PushRelative(out, "EntityMap",
                         static_cast<int>(game_layout::kEntityMapRootOffset),
                         hit.index, "InGameState+0x3E8 ResolveEntityMap");
        }
        out.labels.push_back(std::move(label));
        return;
    }

    // Game thật không có fingerprint HUD → không gán (tránh overlay PlayerStateMemory).
    if (!simulated) {
        return;
    }
    if (!hit.validInGame || hit.rva == 0) {
        return;
    }
    LabelAssignment label;
    label.target = "InGameState";
    label.kind = hit.kind;
    label.index = hit.index;
    label.rva = hit.rva;
    label.evidence = "ResolveInGameState";
    if (hit.playerResolved) {
        label.evidence += "+ResolvePlayer";
        PushRelative(out, "LocalPlayer",
                     static_cast<int>(game_layout::kPlayerStatePtrOffset),
                     hit.index, "InGameState+0x10 ResolvePlayer");
    }
    if (hit.entityMapResolved) {
        label.evidence += "+ResolveEntityMap";
        if (out.entityMapOffset == 0) {
            out.entityMapOffset = game_layout::kEntityMapRootOffset;
        }
        PushRelative(out, "EntityMap",
                     static_cast<int>(game_layout::kEntityMapRootOffset),
                     hit.index, "InGameState+0x3E8 ResolveEntityMap");
    }
    out.labels.push_back(std::move(label));
}

} // namespace

std::string FormatHex(uintptr_t value) {
    char buf[32] = {};
    std::snprintf(buf, sizeof(buf), "0x%llX", static_cast<unsigned long long>(value));
    return buf;
}

bool Probe(IMemoryReader& reader,
           const OffsetRegistry& registry,
           const ModuleInfo& mod,
           bool simulated,
           Report& out,
           const vitals_fingerprint::Expected* expected) {
    out = Report{};
    out.generatedAt = NowStamp();
    out.pid = reader.AttachedPid();
    out.moduleBase = mod.base;
    out.moduleSize = mod.size;
    if (expected) {
        out.expected = *expected;
    }

    PointerChainConfig cfg{};
    cfg.mainBase = mod.base;
    cfg.moduleSize = mod.size;
    cfg.isSimulated = simulated;
    PointerChainResolver resolver(reader, cfg);
    resolver.SetMainBase(mod.base, mod.size);
    resolver.SetSimulated(simulated);
    if (registry.EntityMapOffset() != 0) {
        resolver.SetEntityMapOffset(registry.EntityMapOffset());
    }

    const auto roots = registry.StaticRootCandidates();
    for (size_t i = 0; i < roots.size(); ++i) {
        ProbeHit hit;
        hit.kind = "static_root";
        hit.index = static_cast<int>(i + 1);
        hit.rva = roots[i];

        uintptr_t stored = 0;
        if (reader.ReadValue<uintptr_t>(mod.base + hit.rva, stored)) {
            hit.readable = PointerChainResolver::IsValidAddress(stored);
            hit.heapPtr = stored;
        }
        ProbeHeapCandidate(resolver, reader, hit, simulated, expected);
        if (hit.validInGame || hit.lifeMatched) {
            MaybeAssignInGame(out, hit, simulated, expected);
        }
        out.hits.push_back(std::move(hit));
    }

    for (const auto& entry : registry.AobEntries()) {
        ProbeHit hit;
        hit.kind = "aob_pattern";
        hit.index = entry.index;
        hit.pattern = entry.pattern;
        hit.ripOffset = entry.ripOffset;

        const AobPattern parsed = AobPattern::Parse(entry.pattern);
        if (!parsed.Valid()) {
            out.hits.push_back(std::move(hit));
            continue;
        }
        const uintptr_t match = AobScanner::ScanModule(reader, mod, parsed);
        hit.matchVa = match;
        if (match != 0) {
            const uintptr_t ripTarget = AobScanner::ResolveRipTarget(
                reader, match, static_cast<size_t>(entry.ripOffset));
            if (ripTarget >= mod.base) {
                hit.rva = ripTarget - mod.base;
            }
            uintptr_t stored = 0;
            if (reader.ReadValue<uintptr_t>(ripTarget, stored)) {
                hit.readable = PointerChainResolver::IsValidAddress(stored);
                hit.heapPtr = stored;
            }
            ProbeHeapCandidate(resolver, reader, hit, simulated, expected);
        }
        if (hit.validInGame || hit.lifeMatched) {
            MaybeAssignInGame(out, hit, simulated, expected);
        }
        out.hits.push_back(std::move(hit));
    }

    // Compiled InGameState AOB (Tier 3) — đối chứng với registry, không thay validator.
    {
        ProbeHit hit;
        hit.kind = "compiled_ingame_aob";
        hit.pattern = PointerChainResolver::kInGameStatePattern;
        hit.ripOffset = 3;
        const AobPattern parsed = AobPattern::Parse(hit.pattern);
        if (parsed.Valid()) {
            const uintptr_t match = AobScanner::ScanModule(reader, mod, parsed);
            hit.matchVa = match;
            if (match != 0) {
                const uintptr_t ripTarget = AobScanner::ResolveRipTarget(reader, match, 3);
                if (ripTarget >= mod.base) {
                    hit.rva = ripTarget - mod.base;
                }
                uintptr_t stored = 0;
                if (reader.ReadValue<uintptr_t>(ripTarget, stored)) {
                    hit.readable = PointerChainResolver::IsValidAddress(stored);
                    hit.heapPtr = stored;
                }
                ProbeHeapCandidate(resolver, reader, hit, simulated, expected);
            }
        }
        if (hit.validInGame || hit.lifeMatched) {
            MaybeAssignInGame(out, hit, simulated, expected);
        }
        out.hits.push_back(std::move(hit));
    }

    // Fallback: quét heap theo fingerprint HUD nếu không root nào tới Life.
    if (!simulated && expected && expected->Enabled()) {
        bool anyLife = false;
        for (const auto& hit : out.hits) {
            if (hit.lifeMatched) {
                anyLife = true;
                break;
            }
        }
        if (!anyLife) {
            ProbeHit hit;
            hit.kind = "heap_fingerprint";
            hit.index = 0;
            vitals_fingerprint::ScanStats stats;
            const auto lives = vitals_fingerprint::ScanHeap(reader, *expected, 8, &stats);
            hit.esDwordHits = stats.esDwords;
            hit.chunkReadsFail = stats.chunkReadsFail;
            if (!lives.empty()) {
                FillLifeHit(hit, lives.front());
                hit.readable = true;
                hit.heapPtr = lives.front().lifeAddr;
                hit.playerMaxHp = lives.front().maxHP;
                hit.lifeDescribe = vitals_fingerprint::Describe(lives.front());
                // Chỉ persist offset tương đối pool — không gán InGameState khi chưa có static RVA.
                PushRelative(out, "EnergyShield", hit.esOffset, 0,
                             "HeapFingerprint ES (no static root)");
                PushRelative(out, "Ward", hit.wardOffset, 0,
                             "HeapFingerprint Ward (no static root)");
                PushRelative(out, "Mana", hit.manaOffset, 0,
                             "HeapFingerprint Mana (no static root)");
                PushRelative(out, "Spirit", hit.spiritOffset, 0,
                             "HeapFingerprint Spirit (no static root)");
            }
            out.hits.push_back(std::move(hit));
        }
    }

    return true;
}

bool WriteJson(const Report& report, const std::string& path) {
    std::ofstream file(path);
    if (!file) {
        return false;
    }

    auto hex = [](uintptr_t v) { return FormatHex(v); };
    file << "{\n";
    file << "  \"schema\": \"autopoe2.live_label/v1\",\n";
    file << "  \"generated_at\": \"" << report.generatedAt << "\",\n";
    file << "  \"pid\": " << report.pid << ",\n";
    file << "  \"build_id\": \"" << report.buildId << "\",\n";
    file << "  \"build_match\": \"" << report.buildMatch << "\",\n";
    file << "  \"module_base\": \"" << hex(report.moduleBase) << "\",\n";
    file << "  \"module_size\": \"" << hex(report.moduleSize) << "\",\n";
    file << "  \"entity_map_offset\": \"" << hex(report.entityMapOffset) << "\",\n";
    file << "  \"expected\": {\n";
    file << "    \"max_hp\": " << report.expected.maxHP << ",\n";
    file << "    \"max_es\": " << report.expected.maxES << ",\n";
    file << "    \"max_ward\": " << report.expected.maxWard << ",\n";
    file << "    \"max_mana\": " << report.expected.maxMana << ",\n";
    file << "    \"cur_spirit\": " << report.expected.curSpirit << ",\n";
    file << "    \"max_spirit\": " << report.expected.maxSpirit << "\n";
    file << "  },\n";
    file << "  \"hits\": [\n";
    for (size_t i = 0; i < report.hits.size(); ++i) {
        const auto& hit = report.hits[i];
        file << "    {\n";
        file << "      \"kind\": \"" << hit.kind << "\",\n";
        file << "      \"index\": " << hit.index << ",\n";
        file << "      \"rva\": \"" << hex(hit.rva) << "\",\n";
        file << "      \"heap_ptr\": \"" << hex(hit.heapPtr) << "\",\n";
        file << "      \"readable\": " << (hit.readable ? "true" : "false") << ",\n";
        file << "      \"valid_ingame\": " << (hit.validInGame ? "true" : "false") << ",\n";
        file << "      \"player_resolved\": " << (hit.playerResolved ? "true" : "false") << ",\n";
        file << "      \"entity_map_resolved\": " << (hit.entityMapResolved ? "true" : "false") << ",\n";
        file << "      \"entity_map_cap\": " << hit.entityMapCap << ",\n";
        file << "      \"entity_map_count\": " << hit.entityMapCount << ",\n";
        file << "      \"player_max_hp\": " << hit.playerMaxHp << ",\n";
        file << "      \"life_matched\": " << (hit.lifeMatched ? "true" : "false") << ",\n";
        file << "      \"es_offset\": " << hit.esOffset << ",\n";
        file << "      \"ward_offset\": " << hit.wardOffset << ",\n";
        file << "      \"mana_offset\": " << hit.manaOffset << ",\n";
        file << "      \"spirit_offset\": " << hit.spiritOffset << ",\n";
        file << "      \"es_max\": " << hit.esMax << ",\n";
        file << "      \"ward_max\": " << hit.wardMax << ",\n";
        file << "      \"mana_max\": " << hit.manaMax << ",\n";
        file << "      \"spirit_cur\": " << hit.spiritCur << ",\n";
        file << "      \"spirit_max\": " << hit.spiritMax << ",\n";
        file << "      \"pattern\": \"" << hit.pattern << "\",\n";
        file << "      \"rip_offset\": " << hit.ripOffset << ",\n";
        file << "      \"match_va\": \"" << hex(hit.matchVa) << "\",\n";
        file << "      \"es_dword_hits\": " << hit.esDwordHits << ",\n";
        file << "      \"chunk_reads_fail\": " << hit.chunkReadsFail << ",\n";
        file << "      \"life_describe\": \"" << hit.lifeDescribe << "\"\n";
        file << "    }" << (i + 1 < report.hits.size() ? "," : "") << "\n";
    }
    file << "  ],\n";
    file << "  \"labels\": [\n";
    for (size_t i = 0; i < report.labels.size(); ++i) {
        const auto& label = report.labels[i];
        file << "    {\n";
        file << "      \"target\": \"" << label.target << "\",\n";
        file << "      \"kind\": \"" << label.kind << "\",\n";
        file << "      \"index\": " << label.index << ",\n";
        file << "      \"rva\": \"" << hex(label.rva) << "\",\n";
        file << "      \"evidence\": \"" << label.evidence << "\"\n";
        file << "    }" << (i + 1 < report.labels.size() ? "," : "") << "\n";
    }
    file << "  ]\n";
    file << "}\n";
    return true;
}

} // namespace registry_live_label
