#include "embedded_resources.h"
#include "../util/platform.h"
#include "../util/logger.h"
#include <fstream>
#include <filesystem>
#include <vector>
#include <cstdio>
#include <cctype>
#include <chrono>

#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#else
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif

// Include generated resource data if available
#if __has_include("embedded_data.h")
#include "embedded_data.h"
#define HAS_EMBEDDED_RESOURCES 1
#else
#define HAS_EMBEDDED_RESOURCES 0
#endif

namespace dragonx {
namespace resources {

#if HAS_EMBEDDED_RESOURCES
// Resource table - populated by embedded_data.h (INCBIN symbols: g_NAME_data / g_NAME_size)
static const EmbeddedResource s_resources[] = {
    { g_sapling_spend_params_data,  g_sapling_spend_params_size,  RESOURCE_SAPLING_SPEND },
    { g_sapling_output_params_data, g_sapling_output_params_size, RESOURCE_SAPLING_OUTPUT },
    { g_asmap_dat_data,             g_asmap_dat_size,             RESOURCE_ASMAP },
#ifdef HAS_EMBEDDED_DAEMON
    { g_dragonxd_exe_data,          g_dragonxd_exe_size,          RESOURCE_DRAGONXD },
    { g_dragonx_cli_exe_data,       g_dragonx_cli_exe_size,       RESOURCE_DRAGONX_CLI },
    { g_dragonx_tx_exe_data,        g_dragonx_tx_exe_size,        RESOURCE_DRAGONX_TX },
#endif
#ifdef HAS_EMBEDDED_WALLET_REBUILD
    { g_dragonx_wallet_rebuild_exe_data, g_dragonx_wallet_rebuild_exe_size, RESOURCE_DRAGONX_WALLET_REBUILD },
#endif
#ifdef HAS_EMBEDDED_XMRIG
    { g_xmrig_exe_data,             g_xmrig_exe_size,             RESOURCE_XMRIG },
#endif
#ifdef HAS_EMBEDDED_GRADIENT
    { g_dark_gradient_png_data,     g_dark_gradient_png_size,     RESOURCE_DARK_GRADIENT },
#endif
#ifdef HAS_EMBEDDED_LOGO
    { g_logo_ObsidianDragon_dark_png_data,  g_logo_ObsidianDragon_dark_png_size,  RESOURCE_LOGO },
#endif
    { nullptr, 0, nullptr } // Sentinel
};
// Embedded themes table is generated by s_embedded_themes[] in embedded_data.h
#else
static const EmbeddedResource s_resources[] = {
    { nullptr, 0, nullptr } // No embedded resources
};
// No embedded themes on non-Windows builds (themes live on disk)
struct EmbeddedThemeEntry { const uint8_t* data; unsigned int size; const char* filename; };
static const EmbeddedThemeEntry s_embedded_themes[] = {
    { nullptr, 0, nullptr }
};
// No embedded images on non-Windows builds (images live on disk)
struct EmbeddedImageEntry { const uint8_t* data; unsigned int size; const char* filename; };
static const EmbeddedImageEntry s_embedded_images[] = {
    { nullptr, 0, nullptr }
};
#endif

bool hasEmbeddedResources()
{
#if HAS_EMBEDDED_RESOURCES
    return true;
#else
    return false;
#endif
}

const EmbeddedResource* getEmbeddedResource(const std::string& name)
{
    // Search static resource table (params, daemon binaries, etc.)
    for (const auto* res = s_resources; res->data != nullptr; ++res) {
        if (name == res->filename) {
            return res;
        }
    }
    // Search dynamically generated image table (backgrounds + logos)
    // These are generated by build.sh from res/img/ contents.
    for (const auto* img = s_embedded_images; img->data != nullptr; ++img) {
        if (name == img->filename) {
            static thread_local EmbeddedResource imageResult;
            imageResult = { img->data, img->size, img->filename };
            return &imageResult;
        }
    }
    return nullptr;
}

const EmbeddedTheme* getEmbeddedThemes()
{
    // Map from generated table to public struct
    // s_embedded_themes is generated by build.sh (or empty fallback)
    static std::vector<EmbeddedTheme> themes;
    static bool init = false;
    if (!init) {
        for (const auto* t = s_embedded_themes; t->data != nullptr; ++t) {
            themes.push_back({ t->data, t->size, t->filename });
        }
        themes.push_back({ nullptr, 0, nullptr }); // Sentinel
        init = true;
    }
    return themes.data();
}

int extractBundledThemes(const std::string& destDir)
{
    namespace fs = std::filesystem;
    int count = 0;

    const auto* themes = getEmbeddedThemes();
    if (!themes || !themes->data) return 0;

    // Ensure destination exists
    std::error_code ec;
    fs::create_directories(destDir, ec);
    if (ec) {
        DEBUG_LOGF("[ERROR] EmbeddedResources: Failed to create theme dir: %s\n", destDir.c_str());
        return 0;
    }

    for (const auto* t = themes; t->data != nullptr; ++t) {
        fs::path dest = fs::path(destDir) / t->filename;
        // Always overwrite — bundled themes should match the binary version
        std::ofstream f(dest, std::ios::binary);
        if (f.is_open()) {
            f.write(reinterpret_cast<const char*>(t->data), t->size);
            f.close();
            DEBUG_LOGF("[INFO] EmbeddedResources: Extracted theme: %s (%zu bytes)\n",
                   t->filename, t->size);
            count++;
        } else {
            DEBUG_LOGF("[ERROR] EmbeddedResources: Failed to write theme: %s\n", dest.string().c_str());
        }
    }
    return count;
}

int updateBundledThemes(const std::string& dir)
{
    namespace fs = std::filesystem;
    int count = 0;

    const auto* themes = getEmbeddedThemes();
    if (!themes || !themes->data) return 0;
    if (!fs::exists(dir)) return 0;

    for (const auto* t = themes; t->data != nullptr; ++t) {
        fs::path dest = fs::path(dir) / t->filename;
        if (!fs::exists(dest)) {
            // New theme not yet on disk — extract it
        } else {
            std::error_code ec;
            auto diskSize = fs::file_size(dest, ec);
            if (!ec && diskSize == static_cast<std::uintmax_t>(t->size))
                continue; // up to date
        }
        std::ofstream f(dest, std::ios::binary);
        if (f.is_open()) {
            f.write(reinterpret_cast<const char*>(t->data), t->size);
            f.close();
            DEBUG_LOGF("[INFO] EmbeddedResources: Updated stale theme: %s (%zu bytes)\n",
                   t->filename, t->size);
            count++;
        }
    }
    return count;
}

std::string getParamsDirectory()
{
#ifdef _WIN32
    char appdata[MAX_PATH];
    if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, appdata))) {
        return std::string(appdata) + "\\ZcashParams";
    }
    return "";
#elif defined(__APPLE__)
    const char* home = getenv("HOME");
    if (!home) {
        struct passwd* pw = getpwuid(getuid());
        home = pw ? pw->pw_dir : "/tmp";
    }
    return std::string(home) + "/Library/Application Support/ZcashParams";
#else
    const char* home = getenv("HOME");
    if (!home) {
        struct passwd* pw = getpwuid(getuid());
        home = pw ? pw->pw_dir : "/tmp";
    }
    return std::string(home) + "/.zcash-params";
#endif
}

// Forward declaration — defined below extractResource()
static bool resourceNeedsUpdate(const EmbeddedResource* res, const std::string& destPath);

bool needsParamsExtraction()
{
    if (!hasEmbeddedResources()) {
        return false;
    }
    
    // Check daemon directory (dragonx/) — the only extraction target
    std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
    const char pathSep = '\\';
#else
    const char pathSep = '/';
#endif
    std::string spendPath = daemonDir + pathSep + RESOURCE_SAPLING_SPEND;
    std::string outputPath = daemonDir + pathSep + RESOURCE_SAPLING_OUTPUT;
    
    // Check if params are missing or stale (size mismatch → updated in newer build)
    const auto* spendRes = getEmbeddedResource(RESOURCE_SAPLING_SPEND);
    const auto* outputRes = getEmbeddedResource(RESOURCE_SAPLING_OUTPUT);
    if (spendRes && resourceNeedsUpdate(spendRes, spendPath)) return true;
    if (outputRes && resourceNeedsUpdate(outputRes, outputPath)) return true;
    
    // Daemon binaries are only auto-placed when MISSING (never auto-overwritten on a size
    // mismatch) — the user may be running a specific dragonxd. Replacing the bundled daemon is
    // an explicit action via Settings → daemon binary. So only trigger extraction if it's absent.
#ifdef HAS_EMBEDDED_DAEMON
    const auto* daemonRes = getEmbeddedResource(RESOURCE_DRAGONXD);
    std::string daemonPath = daemonDir + pathSep + RESOURCE_DRAGONXD;
    if (daemonRes && !std::filesystem::exists(daemonPath)) return true;
#endif

#ifdef HAS_EMBEDDED_XMRIG
    const auto* xmrigRes = getEmbeddedResource(RESOURCE_XMRIG);
    std::string xmrigPath = daemonDir + pathSep + RESOURCE_XMRIG;
    if (xmrigRes && resourceNeedsUpdate(xmrigRes, xmrigPath)) return true;
#endif

    return false;
}

// Check if an on-disk file is missing or differs in size from the embedded resource.
// A size mismatch means the binary was updated in a newer wallet build.
static bool resourceNeedsUpdate(const EmbeddedResource* res, const std::string& destPath)
{
    if (!res || !res->data || res->size == 0) return false;
    if (!std::filesystem::exists(destPath)) return true;
    std::error_code ec;
    auto diskSize = std::filesystem::file_size(destPath, ec);
    if (ec) return true; // can't stat → re-extract
    return diskSize != static_cast<std::uintmax_t>(res->size);
}

static bool extractResource(const EmbeddedResource* res, const std::string& destPath)
{
    if (!res || !res->data || res->size == 0) {
        return false;
    }
    
    // Create parent directories
    std::filesystem::path path(destPath);
    if (path.has_parent_path()) {
        std::error_code ec;
        std::filesystem::create_directories(path.parent_path(), ec);
        if (ec) {
            DEBUG_LOGF("[ERROR] Failed to create directory %s: %s\n", 
                   path.parent_path().string().c_str(), ec.message().c_str());
            return false;
        }
    }
    
    // Write file
    std::ofstream file(destPath, std::ios::binary);
    if (!file) {
        // The destination may be locked because the previous daemon is still using the binary:
        // Windows locks a running .exe against truncation, Linux returns ETXTBSY. Both platforms
        // DO allow renaming/moving such a file — the running process keeps the moved copy — so move
        // the stale binary aside and write a fresh one at the original path.
        std::error_code ec;
        if (std::filesystem::exists(destPath)) {
            std::string sidelined = destPath + ".old";
            std::filesystem::remove(sidelined, ec);            // clear any leftover from a prior swap
            ec.clear();
            std::filesystem::rename(destPath, sidelined, ec);
            if (!ec) {
                file.clear();
                file.open(destPath, std::ios::binary);
                if (file)
                    DEBUG_LOGF("[INFO] Replaced in-use %s (old copy moved to .old)\n", destPath.c_str());
            } else {
                DEBUG_LOGF("[WARN] Could not move stale %s aside: %s\n",
                           destPath.c_str(), ec.message().c_str());
            }
        }
        if (!file) {
            DEBUG_LOGF("[ERROR] Failed to open %s for writing\n", destPath.c_str());
            return false;
        }
    }
    
    file.write(reinterpret_cast<const char*>(res->data), res->size);
    if (!file) {
        DEBUG_LOGF("[ERROR] Failed to write %zu bytes to %s\n", res->size, destPath.c_str());
        return false;
    }
    
    DEBUG_LOGF("[INFO] Extracted %s (%zu bytes)\n", destPath.c_str(), res->size);
    return true;
}

bool extractEmbeddedResources()
{
    if (!hasEmbeddedResources()) {
        DEBUG_LOGF("[ERROR] No embedded resources available\n");
        return false;
    }
    
    bool success = true;
    
#ifdef _WIN32
    const char pathSep = '\\';
#else
    const char pathSep = '/';
#endif
    
    // All files go to <ObsidianDragonDir>/dragonx/
    std::string daemonDir = getDaemonDirectory();

    // Extract Sapling params to daemon directory alongside dragonxd
    const EmbeddedResource* spendRes = getEmbeddedResource(RESOURCE_SAPLING_SPEND);
    if (spendRes) {
        std::string dest = daemonDir + pathSep + RESOURCE_SAPLING_SPEND;
        if (resourceNeedsUpdate(spendRes, dest)) {
            DEBUG_LOGF("[INFO] Extracting sapling-spend.params (%zu MB)...\n", spendRes->size / (1024*1024));
            if (!extractResource(spendRes, dest)) {
                success = false;
            }
        }
    }
    
    const EmbeddedResource* outputRes = getEmbeddedResource(RESOURCE_SAPLING_OUTPUT);
    if (outputRes) {
        std::string dest = daemonDir + pathSep + RESOURCE_SAPLING_OUTPUT;
        if (resourceNeedsUpdate(outputRes, dest)) {
            DEBUG_LOGF("[INFO] Extracting sapling-output.params (%zu MB)...\n", outputRes->size / (1024*1024));
            if (!extractResource(outputRes, dest)) {
                success = false;
            }
        }
    }
    
    // Extract asmap.dat to daemon directory
    const EmbeddedResource* asmapRes = getEmbeddedResource(RESOURCE_ASMAP);
    if (asmapRes) {
        std::string dest = daemonDir + pathSep + RESOURCE_ASMAP;
        if (resourceNeedsUpdate(asmapRes, dest)) {
            DEBUG_LOGF("[INFO] Extracting asmap.dat...\n");
            if (!extractResource(asmapRes, dest)) {
                success = false;
            }
        }
    }

    // Extract daemon binaries — NOT the data directory.
    // Running dragonxd.exe from inside the data directory (where it writes blockchain
    // data, debug.log, lock files, etc.) causes crashes on some Windows machines.
#ifdef HAS_EMBEDDED_DAEMON
    DEBUG_LOGF("[INFO] Daemon extraction directory: %s\n", daemonDir.c_str());
    
    // Daemon binaries are placed ONLY when missing — never auto-overwritten on a size mismatch
    // (the user may run a specific dragonxd; replacing it is an explicit Settings action).
    const EmbeddedResource* daemonRes = getEmbeddedResource(RESOURCE_DRAGONXD);
    if (daemonRes) {
        std::string dest = daemonDir + pathSep + RESOURCE_DRAGONXD;
        if (!std::filesystem::exists(dest)) {
            DEBUG_LOGF("[INFO] Extracting dragonxd (%zu MB)...\n", daemonRes->size / (1024*1024));
            if (!extractResource(daemonRes, dest)) {
                success = false;
            }
#ifndef _WIN32
            else { chmod(dest.c_str(), 0755); }
#endif
        }
    }

    const EmbeddedResource* cliRes = getEmbeddedResource(RESOURCE_DRAGONX_CLI);
    if (cliRes) {
        std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_CLI;
        if (!std::filesystem::exists(dest)) {
            DEBUG_LOGF("[INFO] Extracting dragonx-cli (%zu MB)...\n", cliRes->size / (1024*1024));
            if (!extractResource(cliRes, dest)) {
                success = false;
            }
#ifndef _WIN32
            else { chmod(dest.c_str(), 0755); }
#endif
        }
    }

    const EmbeddedResource* txRes = getEmbeddedResource(RESOURCE_DRAGONX_TX);
    if (txRes) {
        std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_TX;
        if (!std::filesystem::exists(dest)) {
            DEBUG_LOGF("[INFO] Extracting dragonx-tx (%zu MB)...\n", txRes->size / (1024*1024));
            if (!extractResource(txRes, dest)) {
                success = false;
            }
#ifndef _WIN32
            else { chmod(dest.c_str(), 0755); }
#endif
        }
    }
#endif

#ifdef HAS_EMBEDDED_XMRIG
    const EmbeddedResource* xmrigRes = getEmbeddedResource(RESOURCE_XMRIG);
    if (xmrigRes) {
        std::string dest = daemonDir + pathSep + RESOURCE_XMRIG;
        if (resourceNeedsUpdate(xmrigRes, dest)) {
            if (std::filesystem::exists(dest))
                DEBUG_LOGF("[INFO] Updating stale xmrig (size mismatch)...\n");
            DEBUG_LOGF("[INFO] Extracting xmrig (%zu MB)...\n", xmrigRes->size / (1024*1024));
            if (!extractResource(xmrigRes, dest)) {
                success = false;
            }
#ifndef _WIN32
            else { chmod(dest.c_str(), 0755); }
#endif
        }
    }
#endif

#ifdef HAS_EMBEDDED_WALLET_REBUILD
    // Offline wallet-rebuild recovery helper — extracted next to the daemon so a bare, self-extracting
    // ObsidianDragon.exe still offers "Repair automatically" (findWalletRebuildHelper() checks this dir).
    const EmbeddedResource* rebuildRes = getEmbeddedResource(RESOURCE_DRAGONX_WALLET_REBUILD);
    if (rebuildRes) {
        std::string dest = daemonDir + pathSep + RESOURCE_DRAGONX_WALLET_REBUILD;
        if (!std::filesystem::exists(dest)) {
            DEBUG_LOGF("[INFO] Extracting dragonx-wallet-rebuild (%zu MB)...\n", rebuildRes->size / (1024*1024));
            if (!extractResource(rebuildRes, dest)) {
                success = false;
            }
#ifndef _WIN32
            else { chmod(dest.c_str(), 0755); }
#endif
        }
    }
#endif

    // Best-effort cleanup of any ".old" binaries left behind by a previous in-use replacement.
    // Once the old daemon/xmrig process has exited, the file is no longer locked and removes cleanly;
    // if it's still running, the remove fails harmlessly and we retry on the next startup.
    {
        std::error_code ec;
        for (const char* name : { RESOURCE_DRAGONXD, RESOURCE_XMRIG }) {
            std::filesystem::remove(daemonDir + pathSep + name + std::string(".old"), ec);
            ec.clear();
        }
    }

    return success;
}

std::string ensureWalletRebuildHelperExtracted()
{
#ifdef HAS_EMBEDDED_WALLET_REBUILD
    const EmbeddedResource* res = getEmbeddedResource(RESOURCE_DRAGONX_WALLET_REBUILD);
    if (!res || res->size == 0) return {};
#ifdef _WIN32
    const char sep = '\\';
#else
    const char sep = '/';
#endif
    const std::string dir  = getDaemonDirectory();
    const std::string dest = dir + sep + RESOURCE_DRAGONX_WALLET_REBUILD;
    std::error_code ec;
    if (std::filesystem::exists(dest, ec)) return dest;   // already extracted
    std::filesystem::create_directories(dir, ec);
    if (!extractResource(res, dest)) return {};
#ifndef _WIN32
    chmod(dest.c_str(), 0755);
#endif
    DEBUG_LOGF("[INFO] Extracted wallet-rebuild helper on demand: %s\n", dest.c_str());
    return dest;
#else
    return {};
#endif
}

std::string getDaemonDirectory()
{
    // Daemon binaries live in %APPDATA%/ObsidianDragon/dragonx/ (Windows) or
    // ~/.config/ObsidianDragon/dragonx/ (Linux) — separate from the blockchain
    // data directory to avoid lock-file conflicts.
    std::string obsidianDir = util::Platform::getObsidianDragonDir();
#ifdef _WIN32
    return obsidianDir + "\\dragonx";
#else
    return obsidianDir + "/dragonx";
#endif
}

bool needsDaemonExtraction()
{
#ifdef HAS_EMBEDDED_DAEMON
    std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
    std::string daemonPath = daemonDir + "\\dragonxd.exe";
#else
    std::string daemonPath = daemonDir + "/dragonxd";
#endif
    const auto* res = getEmbeddedResource(RESOURCE_DRAGONXD);
    return resourceNeedsUpdate(res, daemonPath);
#else
    return false;
#endif
}

bool hasDaemonAvailable()
{
#ifdef HAS_EMBEDDED_DAEMON
    return true;
#else
    // Check if daemon exists alongside the executable
#ifdef _WIN32
    return std::filesystem::exists("dragonxd.exe");
#else
    return std::filesystem::exists("dragonxd");
#endif
#endif
}

std::string getDaemonPath()
{
    std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
    const char pathSep = '\\';
    const char* daemonName = "dragonxd.exe";
#else
    const char pathSep = '/';
    const char* daemonName = "dragonxd";
#endif

    DEBUG_LOGF("[DEBUG] getDaemonPath: daemonDir=%s\n", daemonDir.c_str());

#ifdef HAS_EMBEDDED_DAEMON
    // Extract if needed
    std::string embeddedPath = daemonDir + pathSep + daemonName;
    DEBUG_LOGF("[DEBUG] getDaemonPath: checking embedded path %s\n", embeddedPath.c_str());
    if (!std::filesystem::exists(embeddedPath)) {
        DEBUG_LOGF("[INFO] getDaemonPath: daemon not found, extracting embedded resources...\n");
        extractEmbeddedResources();
    }
    if (std::filesystem::exists(embeddedPath)) {
        DEBUG_LOGF("[INFO] getDaemonPath: found at %s\n", embeddedPath.c_str());
        return embeddedPath;
    }
#endif

    // Check local directory
    if (std::filesystem::exists(daemonName)) {
        DEBUG_LOGF("[INFO] getDaemonPath: found in local directory\n");
        return daemonName;
    }
    
    DEBUG_LOGF("[ERROR] getDaemonPath: daemon binary not found anywhere\n");
    return "";
}

bool needsXmrigExtraction()
{
#ifdef HAS_EMBEDDED_XMRIG
    std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
    std::string xmrigPath = daemonDir + "\\xmrig.exe";
#else
    std::string xmrigPath = daemonDir + "/xmrig";
#endif
    const auto* res = getEmbeddedResource(RESOURCE_XMRIG);
    return resourceNeedsUpdate(res, xmrigPath);
#else
    return false;
#endif
}

bool hasXmrigAvailable()
{
#ifdef HAS_EMBEDDED_XMRIG
    return true;
#else
    // Check if xmrig exists alongside the executable
#ifdef _WIN32
    return std::filesystem::exists("xmrig.exe");
#else
    return std::filesystem::exists("xmrig");
#endif
#endif
}

bool forceExtractXmrig()
{
#ifdef HAS_EMBEDDED_XMRIG
    std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
    std::string dest = daemonDir + "\\" + RESOURCE_XMRIG;
#else
    std::string dest = daemonDir + "/" + RESOURCE_XMRIG;
#endif
    const EmbeddedResource* xmrigRes = getEmbeddedResource(RESOURCE_XMRIG);
    if (!xmrigRes) {
        DEBUG_LOGF("[ERROR] forceExtractXmrig: no embedded xmrig resource\n");
        return false;
    }
    DEBUG_LOGF("[INFO] forceExtractXmrig: extracting xmrig (%zu MB) to %s\n",
            xmrigRes->size / (1024*1024), dest.c_str());
    if (!extractResource(xmrigRes, dest)) {
        DEBUG_LOGF("[ERROR] forceExtractXmrig: extraction failed\n");
        return false;
    }
#ifndef _WIN32
    // Set executable permission on Linux
    chmod(dest.c_str(), 0755);
#endif
    return true;
#else
    return false;
#endif
}

// Scan a binary blob for the daemon's version stamp: 'v' <maj>.<min>.<rev> optionally followed by
// '-' <commit hash (>=6 hex)>, e.g. "v1.0.2-ddd851dc1". Returns the first match, or "" if none.
static std::string scanBinaryVersion(const uint8_t* data, std::size_t size)
{
    if (!data || size < 6) return "";
    auto isdig = [](uint8_t c) { return std::isdigit(static_cast<unsigned char>(c)) != 0; };
    auto isxd  = [](uint8_t c) { return std::isxdigit(static_cast<unsigned char>(c)) != 0; };
    for (std::size_t i = 0; i + 5 < size; ++i) {
        if (data[i] != 'v') continue;
        std::size_t k = i + 1, s;
        s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue;   // major
        if (k >= size || data[k] != '.') continue; ++k;
        s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue;   // minor
        if (k >= size || data[k] != '.') continue; ++k;
        s = k; while (k < size && isdig(data[k])) ++k; if (k == s) continue;   // revision
        std::size_t end = k;
        if (k < size && data[k] == '-') {                                      // optional -<commit>
            std::size_t h = k + 1, hs = h;
            while (h < size && isxd(data[h])) ++h;
            if (h - hs >= 6) end = h;
        }
        return std::string(reinterpret_cast<const char*>(data) + i, end - i);
    }
    return "";
}

DaemonBinaryInfo getInstalledDaemonInfo()
{
    DaemonBinaryInfo info;
    std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
    info.path = daemonDir + "\\" + RESOURCE_DRAGONXD;
#else
    info.path = daemonDir + "/" + RESOURCE_DRAGONXD;
#endif
    std::error_code ec;
    if (!std::filesystem::exists(info.path, ec)) return info;  // exists stays false
    info.exists = true;
    info.size = std::filesystem::file_size(info.path, ec);
    if (ec) info.size = 0;

    auto ftime = std::filesystem::last_write_time(info.path, ec);
    if (!ec) {
        // Convert filesystem clock → system_clock epoch (pre-C++20 portable approximation).
        auto sysTime = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
            ftime - decltype(ftime)::clock::now() + std::chrono::system_clock::now());
        info.modifiedEpoch =
            static_cast<std::int64_t>(std::chrono::system_clock::to_time_t(sysTime));
    }

    // Read the binary and scan for its version stamp (one-off; caller caches the result).
    std::ifstream f(info.path, std::ios::binary);
    if (f) {
        f.seekg(0, std::ios::end);
        std::streamoff len = f.tellg();
        f.seekg(0, std::ios::beg);
        if (len > 0) {
            std::vector<uint8_t> buf(static_cast<std::size_t>(len));
            f.read(reinterpret_cast<char*>(buf.data()), len);
            info.version = scanBinaryVersion(buf.data(), static_cast<std::size_t>(f.gcount()));
        }
    }
    return info;
}

BundledDaemonInfo getBundledDaemonInfo()
{
    BundledDaemonInfo info;
#ifdef HAS_EMBEDDED_DAEMON
    const EmbeddedResource* res = getEmbeddedResource(RESOURCE_DRAGONXD);
    if (res && res->data && res->size > 0) {
        info.available = true;
        info.size = res->size;
        // The embedded bytes are constant for this build — scan once.
        static const std::string cachedVersion = scanBinaryVersion(res->data, res->size);
        info.version = cachedVersion;
    }
#endif
    return info;
}

bool reextractBundledDaemon()
{
#ifdef HAS_EMBEDDED_DAEMON
    std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
    const char pathSep = '\\';
#else
    const char pathSep = '/';
#endif
    bool ok = true;
    bool wroteAny = false;
    const char* names[] = { RESOURCE_DRAGONXD, RESOURCE_DRAGONX_CLI, RESOURCE_DRAGONX_TX };
    for (const char* name : names) {
        const EmbeddedResource* res = getEmbeddedResource(name);
        if (!res) continue;
        std::string dest = daemonDir + pathSep + name;
        DEBUG_LOGF("[INFO] reextractBundledDaemon: writing %s (%zu MB)\n", name, res->size / (1024*1024));
        if (!extractResource(res, dest)) {
            DEBUG_LOGF("[ERROR] reextractBundledDaemon: failed to write %s\n", name);
            ok = false;
            continue;
        }
        wroteAny = true;
#ifndef _WIN32
        chmod(dest.c_str(), 0755);
#endif
    }
    return ok && wroteAny;
#else
    return false;
#endif
}

std::string getXmrigPath()
{
    std::string daemonDir = getDaemonDirectory();
#ifdef _WIN32
    const char pathSep = '\\';
    const char* xmrigName = "xmrig.exe";
#else
    const char pathSep = '/';
    const char* xmrigName = "xmrig";
#endif

    DEBUG_LOGF("[DEBUG] getXmrigPath: daemonDir=%s\n", daemonDir.c_str());

#ifdef HAS_EMBEDDED_XMRIG
    // Extract if needed — force re-extract in case Defender deleted it
    std::string embeddedPath = daemonDir + pathSep + xmrigName;
    DEBUG_LOGF("[DEBUG] getXmrigPath: checking embedded path %s\n", embeddedPath.c_str());
    if (!std::filesystem::exists(embeddedPath)) {
        DEBUG_LOGF("[DEBUG] getXmrigPath: not found, force re-extracting xmrig\n");
        forceExtractXmrig();
    }
    if (std::filesystem::exists(embeddedPath)) {
        DEBUG_LOGF("[INFO] getXmrigPath: found at %s\n", embeddedPath.c_str());
        return embeddedPath;
    }
#endif

    // Check local directory
    if (std::filesystem::exists(xmrigName)) {
        DEBUG_LOGF("[INFO] getXmrigPath: found in local directory\n");
        return xmrigName;
    }
    
    // Check alongside the wallet executable (zip / AppImage distributions)
    {
        std::string exeDir = util::Platform::getExecutableDirectory();
        if (!exeDir.empty()) {
            std::string exeDirPath = exeDir + "/" + xmrigName;
            if (std::filesystem::exists(exeDirPath)) {
                DEBUG_LOGF("[INFO] getXmrigPath: found alongside wallet at %s\n", exeDirPath.c_str());
                return exeDirPath;
            }
        }
    }

    // Check development paths
#ifdef _WIN32
    // Not applicable for cross-compiled Windows builds
#else
    // Linux development build — resolve relative to executable directory
    // (build/bin/), not CWD which may differ at runtime
    std::string exeDir = util::Platform::getExecutableDirectory();
    std::vector<std::string> devPaths = {
        exeDir + "/../../external/xmrig-HAC/xmrig/build/xmrig",   // from build/linux/bin/
        exeDir + "/../external/xmrig-HAC/xmrig/build/xmrig",      // from build/linux/
        "../external/xmrig-HAC/xmrig/build/xmrig",                 // CWD = build/linux/
        "../../external/xmrig-HAC/xmrig/build/xmrig",              // CWD = build/linux/bin/
        "external/xmrig-HAC/xmrig/build/xmrig",                    // CWD = project root
    };
    for (const auto& dp : devPaths) {
        if (std::filesystem::exists(dp)) {
            std::string resolved = std::filesystem::canonical(dp).string();
            DEBUG_LOGF("[INFO] getXmrigPath: found dev binary at %s\n", resolved.c_str());
            return resolved;
        }
    }
#endif
    
    DEBUG_LOGF("[ERROR] getXmrigPath: xmrig binary not found anywhere\n");
    return "";
}

} // namespace resources
} // namespace dragonx
