// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#include "pool_stats_service.h"

#include <curl/curl.h>

namespace dragonx {
namespace util {

namespace {

size_t writeStringCb(void* contents, size_t size, size_t nmemb, void* userp)
{
    auto* s = static_cast<std::string*>(userp);
    const size_t add = size * nmemb;
    // Pool stats JSON is tiny; refuse an unbounded body from a hostile/MITM'd endpoint (returning < add
    // aborts the transfer) so it can't grow this string until OOM. (M-02)
    constexpr size_t kMaxPoolStatsBytes = 1u << 20;  // 1 MiB
    if (s->size() + add > kMaxPoolStatsBytes) return 0;
    s->append(static_cast<char*>(contents), add);
    return add;
}

// Returning non-zero asks libcurl to abort the transfer — used so shutdown doesn't
// block on an in-flight fetch.
int xferInfoCb(void* clientp, curl_off_t, curl_off_t, curl_off_t, curl_off_t)
{
    const auto* self = static_cast<const PoolStatsService*>(clientp);
    return (self && self->cancelRequested()) ? 1 : 0;
}

} // namespace

PoolStatsService::~PoolStatsService()
{
    cancel_requested_ = true;
    if (worker_.joinable()) worker_.join();
}

void PoolStatsService::refresh(const std::vector<KnownPool>& pools)
{
    if (worker_running_.exchange(true)) return;  // already refreshing
    if (worker_.joinable()) worker_.join();      // reap the previous finished worker
    worker_ = std::thread([this, pools]() {
        run(pools);
        worker_running_ = false;
    });
}

std::string PoolStatsService::httpGet(const std::string& url)
{
    CURL* curl = curl_easy_init();
    if (!curl) return {};

    std::string result;
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    // pool.dragonx.cc sits behind Cloudflare and 403s odd User-Agents — present a
    // browser-like UA and accept compressed responses.
    curl_easy_setopt(curl, CURLOPT_USERAGENT,
                     "Mozilla/5.0 (compatible; ObsidianDragon)");
    curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 8L);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
    curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferInfoCb);
    curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this);

    struct curl_slist* hdrs = nullptr;
    hdrs = curl_slist_append(hdrs, "Accept: application/json");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);

    const CURLcode res = curl_easy_perform(curl);
    long httpCode = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
    curl_slist_free_all(hdrs);
    curl_easy_cleanup(curl);

    if (res != CURLE_OK || httpCode < 200 || httpCode >= 300) return {};
    return result;
}

void PoolStatsService::run(std::vector<KnownPool> pools)
{
    std::map<std::string, PoolHashrate> results;
    for (const auto& p : pools) {
        if (cancel_requested_.load()) break;
        PoolHashrate hr;
        hr.id = p.id;
        const std::string body = httpGet(p.statsUrl);
        if (!body.empty()) {
            bool ok = false;
            const double v = parsePoolHashrate(p.schema, body, p.miningcorePoolId, ok);
            hr.ok = ok;
            hr.hashrateHs = ok ? v : 0.0;

            bool feeOk = false;
            const double fee = parsePoolFee(p.schema, body, p.miningcorePoolId, feeOk);
            // Only trust a sane fee; anything else leaves feePercent < 0 so the UI
            // falls back to the compile-time KnownPool.feePercent.
            if (feeOk && fee >= 0.0 && fee <= 100.0) hr.feePercent = fee;
        }
        results[p.id] = hr;
    }

    std::lock_guard<std::mutex> lk(mutex_);
    snapshot_.byId = std::move(results);
    snapshot_.ready = true;
}

PoolStatsService::Snapshot PoolStatsService::snapshot() const
{
    std::lock_guard<std::mutex> lk(mutex_);
    return snapshot_;
}

} // namespace util
} // namespace dragonx
