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

#include "http_download.h"

#include "logger.h"

#include <curl/curl.h>

#include <cstdio>
#include <filesystem>
#include <system_error>

namespace fs = std::filesystem;

namespace dragonx {
namespace util {

namespace {

// Cap for in-memory text/JSON responses (release metadata, price/candle data). Far above any
// legitimate body, but bounds memory if a hostile/MITM'd server streams an unbounded response.
constexpr std::size_t kMaxMetadataBytes = 16u * 1024 * 1024;

size_t writeStringCb(void* contents, size_t size, size_t nmemb, void* userp)
{
    auto* s = static_cast<std::string*>(userp);
    const size_t n = size * nmemb;
    // Hard cap: a chunked response omits Content-Length, so CURLOPT_MAXFILESIZE cannot catch it —
    // aborting here (short count) makes curl fail the transfer instead of exhausting memory.
    if (s->size() + n > kMaxMetadataBytes) return 0;
    s->append(static_cast<char*>(contents), n);
    return n;
}

size_t writeFileCb(void* contents, size_t size, size_t nmemb, void* userp)
{
    return std::fwrite(contents, size, nmemb, static_cast<FILE*>(userp));
}

// Bridge libcurl's C progress callback to the caller's std::function: publish live byte counts
// and abort the transfer (return non-zero) when the callback returns false (cancellation).
int xferCb(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t, curl_off_t)
{
    auto* fn = static_cast<const std::function<bool(double, double)>*>(clientp);
    return (*fn)(static_cast<double>(dlnow), static_cast<double>(dltotal)) ? 0 : 1;
}

} // namespace

std::string httpGetString(const std::string& url, const char* logTag)
{
    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);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "ObsidianDragon/1.0");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15L);
    curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE, static_cast<curl_off_t>(kMaxMetadataBytes));  // reject oversized metadata
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
    const CURLcode res = curl_easy_perform(curl);
    long httpCode = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
    curl_easy_cleanup(curl);
    if (res != CURLE_OK || httpCode < 200 || httpCode >= 300) {
        DEBUG_LOGF("%s GET %s failed: %s (HTTP %ld)\n",
                   logTag, url.c_str(), curl_easy_strerror(res), httpCode);
        return {};
    }
    return result;
}

bool httpDownloadToFile(const std::string& url, const std::string& destPath,
                        const char* logTag, long long maxBytes,
                        const std::function<bool(double, double)>& onProgress)
{
    FILE* fp = std::fopen(destPath.c_str(), "wb");
    if (!fp) return false;
    CURL* curl = curl_easy_init();
    if (!curl) { std::fclose(fp); return false; }
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFileCb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "ObsidianDragon/1.0");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
    curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1024L);
    curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L);
    curl_easy_setopt(curl, CURLOPT_MAXFILESIZE_LARGE, static_cast<curl_off_t>(maxBytes));  // refuse oversized bodies
    // Live progress + cancellation: the callback publishes byte counts and aborts on cancel().
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
    curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferCb);
    curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &onProgress);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
    const CURLcode res = curl_easy_perform(curl);
    long httpCode = 0;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
    curl_easy_cleanup(curl);
    std::fclose(fp);
    if (res != CURLE_OK || httpCode < 200 || httpCode >= 300) {
        DEBUG_LOGF("%s download %s failed: %s (HTTP %ld)\n",
                   logTag, url.c_str(), curl_easy_strerror(res), httpCode);
        std::error_code ec; fs::remove(destPath, ec);
        return false;
    }
    return true;
}

} // namespace util
} // namespace dragonx
