#include "memory/vitals_fingerprint.hpp"

#include "memory/pointer_chain_resolver.hpp"

#include <algorithm>
#include <cstring>
#include <sstream>

namespace vitals_fingerprint {
namespace {

constexpr int kWindowLo = -64;
constexpr int kWindowHi = 2048;
constexpr uint32_t kEcsType = 2;
constexpr uint32_t kEcsLife = 4;

bool HeapPtr(uintptr_t p) {
    return PointerChainResolver::IsValidAddress(p);
}

bool ReadU32(IMemoryReader& reader, uintptr_t addr, uint32_t& out) {
    return reader.ReadValue<uint32_t>(addr, out);
}

uintptr_t AtOff(uintptr_t life, int off) {
    if (off >= 0) {
        return life + static_cast<uintptr_t>(off);
    }
    const uintptr_t n = static_cast<uintptr_t>(-off);
    if (life < n) {
        return 0;
    }
    return life - n;
}

bool Skipped(int off, const int* skip, size_t skipN) {
    if (off == 0 || off == 4 || off == 8) {
        return true;
    }
    for (size_t s = 0; s < skipN; ++s) {
        if (off == skip[s]) {
            return true;
        }
    }
    return false;
}

bool TryCanonicalPair(IMemoryReader& reader, uintptr_t lifeAddr, int off,
                      uint32_t expectMax, PoolHit& hit) {
    if (expectMax == 0) {
        return false;
    }
    uint32_t cur = 0;
    uint32_t mx = 0;
    const uintptr_t a = AtOff(lifeAddr, off);
    if (a == 0 || !ReadU32(reader, a, cur) || !ReadU32(reader, a + 4, mx)) {
        return false;
    }
    if (mx != expectMax || cur > mx * 2) {
        return false;
    }
    hit.offset = off;
    hit.cur = cur;
    hit.max = mx;
    return true;
}

bool FindPoolLive(IMemoryReader& reader, uintptr_t lifeAddr, uint32_t expectMax,
                  PoolHit& hit, const int* skip, size_t skipN) {
    if (expectMax == 0) {
        return false;
    }
    for (int off = kWindowLo; off + 8 <= kWindowHi; off += 4) {
        if (Skipped(off, skip, skipN)) {
            continue;
        }
        const uintptr_t a = AtOff(lifeAddr, off);
        if (a == 0) {
            continue;
        }
        uint32_t cur = 0;
        uint32_t mx = 0;
        if (!ReadU32(reader, a, cur) || !ReadU32(reader, a + 4, mx)) {
            continue;
        }
        if (mx != expectMax || cur > mx * 2) {
            continue;
        }
        hit.offset = off;
        hit.cur = cur;
        hit.max = mx;
        return true;
    }
    return false;
}

} // namespace

bool MatchAt(IMemoryReader& reader, uintptr_t lifeAddr, const Expected& exp, LifeMatch& out) {
    out = LifeMatch{};
    if (!HeapPtr(lifeAddr) || !exp.Enabled()) {
        return false;
    }

    uint32_t hp = 0;
    uint32_t maxHP = 0;
    if (!ReadU32(reader, lifeAddr, hp) || !ReadU32(reader, lifeAddr + 4, maxHP)) {
        return false;
    }
    if (exp.maxHP == 1) {
        // CI đang sống trên HUD là 1/1 — loại 0/1 (chết) và 1/N giả.
        if (maxHP != 1 || hp != 1) {
            return false;
        }
    } else if (exp.maxHP != 0 && maxHP != exp.maxHP) {
        return false;
    } else if (maxHP < 1 || maxHP > 25000) {
        return false;
    }

    PoolHit es{};
    PoolHit ward{};
    PoolHit mana{};
    PoolHit spirit{};

    bool okEs = (exp.maxES == 0) ||
        TryCanonicalPair(reader, lifeAddr, kEsRel, exp.maxES, es);
    bool okWard = (exp.maxWard == 0) ||
        TryCanonicalPair(reader, lifeAddr, kWardRel, exp.maxWard, ward);
    bool okMana = (exp.maxMana == 0) ||
        TryCanonicalPair(reader, lifeAddr, kManaRel, exp.maxMana, mana);
    if (!okMana && exp.maxMana != 0) {
        okMana = TryCanonicalPair(reader, lifeAddr, kLegacyManaRel, exp.maxMana, mana);
    }
    bool okSpirit = (exp.maxSpirit == 0) ||
        TryCanonicalPair(reader, lifeAddr, kSpiritRel, exp.maxSpirit, spirit);

    const int skipEs[] = {kEsRel, kWardRel, kLegacyManaRel, kManaRel, kSpiritRel, 0, 4, 8};
    if (!okEs && exp.maxES != 0) {
        okEs = FindPoolLive(reader, lifeAddr, exp.maxES, es, skipEs, 8);
    }
    if (!okWard && exp.maxWard != 0) {
        okWard = FindPoolLive(reader, lifeAddr, exp.maxWard, ward, skipEs, 8);
    }
    if (!okMana && exp.maxMana != 0) {
        okMana = FindPoolLive(reader, lifeAddr, exp.maxMana, mana, skipEs, 8);
    }
    const int skipSpirit[] = {kEsRel, kWardRel, kLegacyManaRel, kManaRel, kSpiritRel, 0, 4, 8,
                              es.offset, ward.offset, mana.offset};
    if (!okSpirit && exp.maxSpirit != 0) {
        okSpirit = FindPoolLive(reader, lifeAddr, exp.maxSpirit, spirit, skipSpirit, 11);
    }
    if (okSpirit && exp.curSpirit != 0 && spirit.cur != exp.curSpirit) {
        spirit = PoolHit{};
        okSpirit = FindPoolLive(reader, lifeAddr, exp.maxSpirit, spirit, skipSpirit, 11);
        if (okSpirit && spirit.cur != exp.curSpirit) {
            spirit = PoolHit{};
            okSpirit = false;
        }
    }

    // Bắt buộc CI/HP + ES. Ward bắt buộc nếu HUD có Ward.
    // Mana/Spirit: dump PID 36104 — nằm tại +0x420 / +0x44C; thiếu không loại Life.
    if (exp.maxES != 0 && !okEs) {
        return false;
    }
    if (exp.maxWard != 0 && !okWard) {
        return false;
    }

    if (exp.maxES && exp.maxWard && es.offset == ward.offset) {
        return false;
    }
    if (okMana && exp.maxMana && exp.maxES && mana.offset == es.offset) {
        return false;
    }

    out.lifeAddr = lifeAddr;
    out.hp = hp;
    out.maxHP = maxHP;
    out.es = es;
    out.ward = ward;
    out.mana = mana;
    out.spirit = spirit;

    uint32_t prev8 = 0;
    uint32_t prev4 = 0;
    const uintptr_t ecsAddr = AtOff(lifeAddr, -8);
    if (ecsAddr != 0 &&
        ReadU32(reader, ecsAddr, prev8) &&
        ReadU32(reader, ecsAddr + 4, prev4) &&
        prev8 == kEcsType && prev4 == kEcsLife) {
        out.ecsSignature = true;
    }

    int score = 100;
    if (out.ecsSignature) {
        score += 400;
    }
    if (exp.maxES && es.offset == kEsRel) {
        score += 80;
    }
    if (exp.maxWard && ward.offset == kWardRel) {
        score += 80;
    }
    if (okMana && exp.maxMana && mana.offset == kManaRel) {
        score += 80;
    } else if (okMana && exp.maxMana && mana.offset == kLegacyManaRel) {
        score += 40;
    }
    if (okSpirit && exp.maxSpirit && spirit.offset == kSpiritRel) {
        score += 80;
    }
    if (exp.curSpirit != 0 && spirit.cur == exp.curSpirit) {
        score += 50;
    }
    out.score = score;
    return true;
}

namespace {

void ConsiderEsDword(IMemoryReader& reader, const Expected& exp, uintptr_t esAddr,
                     std::vector<LifeMatch>& hits, size_t maxHits, ScanStats* stats) {
    const uintptr_t candidates[3] = {
        (esAddr >= 16) ? (esAddr - 16) : 0,
        (esAddr >= 12) ? (esAddr - 12) : 0,
        esAddr
    };
    for (uintptr_t life : candidates) {
        if (life == 0) {
            continue;
        }
        if (stats) {
            ++stats->matchAttempts;
        }
        LifeMatch hit;
        if (!MatchAt(reader, life, exp, hit)) {
            continue;
        }
        bool dup = false;
        for (const auto& prev : hits) {
            if (prev.lifeAddr == hit.lifeAddr) {
                dup = true;
                break;
            }
        }
        if (!dup) {
            hits.push_back(hit);
            if (hits.size() >= maxHits) {
                return;
            }
        }
    }
}

void ScanBuffer(IMemoryReader& reader, const Expected& exp, const uint8_t* buf,
                size_t n, uintptr_t baseAddr, std::vector<LifeMatch>& hits,
                size_t maxHits, ScanStats* stats) {
    for (size_t i = 0; i + 4 <= n; i += 4) {
        if (hits.size() >= maxHits) {
            return;
        }
        uint32_t v = 0;
        std::memcpy(&v, buf + i, 4);
        if (v != exp.maxES) {
            continue;
        }
        if (stats) {
            ++stats->esDwords;
        }
        ConsiderEsDword(reader, exp, baseAddr + i, hits, maxHits, stats);
    }
}

} // namespace

std::vector<LifeMatch> ScanHeap(IMemoryReader& reader, const Expected& exp,
                                size_t maxHits, ScanStats* stats) {
    std::vector<LifeMatch> hits;
    if (!exp.Enabled() || exp.maxES == 0) {
        return hits;
    }

    std::vector<uint8_t> buf(1024 * 1024);
    std::vector<uint8_t> small(4096);
    constexpr size_t kOverlap = 16;
    reader.ForEachReadableRegion([&](uintptr_t base, size_t size) -> bool {
        if (hits.size() >= maxHits) {
            return false;
        }
        if (stats) {
            ++stats->regionsVisited;
        }
        const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
        for (size_t off = 0; off + 16 <= size; off += step) {
            const size_t n = (std::min)(buf.size(), size - off);
            if (reader.Read(base + off, buf.data(), n)) {
                if (stats) {
                    ++stats->chunkReadsOk;
                }
                ScanBuffer(reader, exp, buf.data(), n, base + off, hits, maxHits, stats);
            } else {
                if (stats) {
                    ++stats->chunkReadsFail;
                }
                for (size_t s = 0; s < n; s += small.size()) {
                    const size_t sn = (std::min)(small.size(), n - s);
                    if (!reader.Read(base + off + s, small.data(), sn)) {
                        continue;
                    }
                    if (stats) {
                        ++stats->chunkReadsOk;
                    }
                    ScanBuffer(reader, exp, small.data(), sn, base + off + s, hits, maxHits, stats);
                    if (hits.size() >= maxHits) {
                        return false;
                    }
                }
            }
            if (hits.size() >= maxHits) {
                return false;
            }
        }
        return true;
    });

    std::sort(hits.begin(), hits.end(), [](const LifeMatch& a, const LifeMatch& b) {
        return a.score > b.score;
    });
    return hits;
}

bool FindLifeFromInGame(IMemoryReader& reader, uintptr_t inGame, const Expected& exp, LifeMatch& out) {
    out = LifeMatch{};
    if (!HeapPtr(inGame) || !exp.Enabled()) {
        return false;
    }

    uintptr_t player = 0;
    if (!reader.ReadValue<uintptr_t>(inGame + 0x10, player) || !HeapPtr(player)) {
        return false;
    }

    if (MatchAt(reader, player, exp, out)) {
        return true;
    }

    for (int off = -512; off <= 4096; off += 4) {
        const uintptr_t cand = AtOff(player, off);
        if (cand == 0 || !HeapPtr(cand)) {
            continue;
        }
        if (MatchAt(reader, cand, exp, out)) {
            return true;
        }
    }

    for (uintptr_t off = 0; off + 8 <= 0x2000; off += 8) {
        uintptr_t ptr = 0;
        if (!reader.ReadValue<uintptr_t>(player + off, ptr) || !HeapPtr(ptr)) {
            continue;
        }
        if (MatchAt(reader, ptr, exp, out)) {
            return true;
        }
    }
    return false;
}

std::string Describe(const LifeMatch& hit) {
    std::ostringstream oss;
    oss << "life HP=" << hit.hp << "/" << hit.maxHP
        << " ES=" << hit.es.cur << "/" << hit.es.max << "@+" << hit.es.offset
        << " Ward=" << hit.ward.cur << "/" << hit.ward.max << "@+" << hit.ward.offset
        << " Mana=" << hit.mana.cur << "/" << hit.mana.max << "@+" << hit.mana.offset
        << " Spirit=" << hit.spirit.cur << "/" << hit.spirit.max << "@+" << hit.spirit.offset
        << (hit.ecsSignature ? " ecs{2,4}" : " no-ecs");
    return oss.str();
}

std::vector<PairHit> ScanCurMaxPair(IMemoryReader& reader, uint32_t expectCur,
                                    uint32_t expectMax, size_t maxHits, ScanStats* stats) {
    std::vector<PairHit> hits;
    if (expectMax == 0) {
        return hits;
    }

    std::vector<uint8_t> buf(1024 * 1024);
    std::vector<uint8_t> small(4096);
    constexpr size_t kOverlap = 8;
    const bool anyCur = (expectCur == 0xFFFFFFFFu);

    auto consider = [&](uintptr_t addr, uint32_t cur, uint32_t mx) {
        if (mx != expectMax) {
            return;
        }
        if (!anyCur && cur != expectCur) {
            return;
        }
        if (anyCur && cur > mx * 2) {
            return;
        }
        for (const auto& prev : hits) {
            if (prev.addr == addr) {
                return;
            }
        }
        hits.push_back(PairHit{addr, cur, mx});
    };

    auto scanBuf = [&](const uint8_t* p, size_t n, uintptr_t baseAddr) {
        for (size_t i = 0; i + 8 <= n; i += 4) {
            if (hits.size() >= maxHits) {
                return;
            }
            uint32_t cur = 0;
            uint32_t mx = 0;
            std::memcpy(&cur, p + i, 4);
            std::memcpy(&mx, p + i + 4, 4);
            if (mx != expectMax) {
                continue;
            }
            if (stats) {
                ++stats->esDwords;
            }
            consider(baseAddr + i, cur, mx);
        }
    };

    reader.ForEachReadableRegion([&](uintptr_t base, size_t size) -> bool {
        if (hits.size() >= maxHits) {
            return false;
        }
        if (stats) {
            ++stats->regionsVisited;
        }
        const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
        for (size_t off = 0; off + 8 <= size; off += step) {
            const size_t n = (std::min)(buf.size(), size - off);
            if (reader.Read(base + off, buf.data(), n)) {
                if (stats) {
                    ++stats->chunkReadsOk;
                }
                scanBuf(buf.data(), n, base + off);
            } else {
                if (stats) {
                    ++stats->chunkReadsFail;
                }
                for (size_t s = 0; s < n; s += small.size()) {
                    const size_t sn = (std::min)(small.size(), n - s);
                    if (!reader.Read(base + off + s, small.data(), sn)) {
                        continue;
                    }
                    if (stats) {
                        ++stats->chunkReadsOk;
                    }
                    scanBuf(small.data(), sn, base + off + s);
                    if (hits.size() >= maxHits) {
                        return false;
                    }
                }
            }
            if (hits.size() >= maxHits) {
                return false;
            }
        }
        return true;
    });
    return hits;
}

} // namespace vitals_fingerprint
