// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// XmrigUpdater — fetch + verify + install the latest DRG-XMRig miner from the DragonX Gitea.
//
// Flow (mirrors util/Bootstrap): query the Gitea releases API for the latest release, pick the
// asset matching this platform (…-linux-x64.zip / …-win-x64.zip), download it, verify its
// published SHA-256, then extract the miner binary (flattening the versioned subdir the archive
// nests it in) into the target directory and verify the extracted binary's SHA-256 before it is
// made executable. All network/disk work runs on a background thread; progress is polled
// thread-safely from the UI thread.
//
// Security: download-and-execute, so verification is mandatory — TLS is verified (libcurl
// defaults), the host is the project's own Gitea over HTTPS, and BOTH the archive and the inner
// binary are checked against the SHA-256 checksums published in the release body. The checksums
// live in the release body markdown (no SHA256SUMS asset), parsed as "<hex>  <name>" lines.

#pragma once

#include <atomic>
#include <cstddef>
#include <map>
#include <mutex>
#include <string>
#include <thread>
#include <vector>

namespace dragonx {
namespace util {

struct XmrigReleaseAsset {
    std::string name;
    std::string downloadUrl;
    long long   size = 0;
};

struct XmrigRelease {
    bool ok = false;
    std::string tag;                       // e.g. "v1.0.0"
    std::string name;                      // human title (e.g. "DRG-XMRig v6.25.3")
    std::string body;                      // release notes markdown (holds the checksum blocks)
    bool prerelease = false;               // marked pre-release on the Gitea release
    std::string publishedAt;               // ISO-8601 publish timestamp (date shown in the UI)
    std::vector<XmrigReleaseAsset> assets;
    std::string error;
};

// ── Release signature (opt-in supply-chain hardening) ────────────────────────
//
// Pinned ed25519 public key (base64, 32 bytes) used to verify a detached signature over the
// downloaded archive. EMPTY by default = signature verification DISABLED (TLS + the release's
// published SHA-256 are the only gates, unchanged behavior). To enable: paste the base64 public
// key here and have the release publish a "<archive-name>.sig" asset containing the base64 (or
// raw 64-byte) ed25519 signature of the archive bytes (see scripts/sign-xmrig-release.sh). Once a
// key is set, an install verifies the signature when the .sig asset is present; set
// kXmrigRequireSignature=true to additionally refuse installs that publish no signature.
inline constexpr const char* kXmrigSignaturePublicKeyBase64 =
    "j/9M+0E8NgcudP1q+23ar5uzwAFzQled8TtkFMaou6Q=";
inline constexpr bool        kXmrigRequireSignature = true;   // enforced: refuse installs without a valid .sig

// ── Pure helpers (no I/O; unit-tested) ───────────────────────────────────────

// Parse the Gitea GET /releases/latest JSON into an XmrigRelease (ok=false + error on failure).
XmrigRelease parseXmrigRelease(const std::string& json);

// Parse the Gitea GET /releases (array) JSON into the list of releases, newest first, skipping
// drafts. Empty on parse failure. Lets the user browse + pin a specific (or pre-release) version.
std::vector<XmrigRelease> parseXmrigReleaseList(const std::string& json);

// The asset-name token for the host platform: "linux-x64", "win-x64", "macos-x64",
// "macos-arm64", or "" if unknown/unsupported.
std::string currentXmrigPlatformToken();

// Index of the asset whose name matches the platform token (e.g. ends with "-linux-x64.zip"),
// or -1 if none (e.g. no macOS build is published).
int selectXmrigAsset(const XmrigRelease& release, const std::string& platformToken);

// Parse "<sha256hex>  <name> …" lines from the release body into { name -> lowercase-hex }.
// Keys are the first whitespace-delimited token after the hash, so this captures both the archive
// checksums (keyed by zip filename) and the inner-binary checksums (keyed by "xmrig"/"xmrig.exe").
std::map<std::string, std::string> parseXmrigChecksums(const std::string& body);

// Lowercase-hex SHA-256 of a buffer (libsodium). Empty string on failure.
std::string sha256Hex(const void* data, std::size_t len);

// The binary file basenames to extract for a platform: {"xmrig"} on POSIX,
// {"xmrig.exe", "WinRing0x64.sys"} on Windows. First entry is always the miner binary.
std::vector<std::string> xmrigExtractBasenames(const std::string& platformToken);

// Index of the detached-signature asset for a given archive (name "<archive>.sig" or
// "<archive>.minisig"), or -1 if none is published.
int selectXmrigSignatureAsset(const XmrigRelease& release, const std::string& archiveName);

// Verify a detached ed25519 signature over `data`. `signatureFileContent` is the .sig asset body
// (base64 of, or raw, a 64-byte ed25519 signature; whitespace tolerated). `pubKeyBase64` is the
// base64 32-byte public key. Returns true only on a cryptographically valid signature.
bool verifyXmrigSignature(const std::string& data,
                          const std::string& signatureFileContent,
                          const std::string& pubKeyBase64);

// ── Background worker ────────────────────────────────────────────────────────

class XmrigUpdater {
public:
    enum class State {
        Idle,
        Checking,
        UpToDate,
        UpdateAvailable,
        Unavailable,    // no miner build is published for this platform (terminal, not an error)
        Listing,        // fetching the full release list (Browse all releases)
        ReleaseList,    // release list fetched; awaiting the user's pick
        Downloading,
        Verifying,
        Extracting,
        Done,
        Failed
    };

    struct Progress {
        State       state = State::Idle;
        double      downloaded_bytes = 0;
        double      total_bytes = 0;
        float       percent = 0.0f;
        std::string status_text;
        std::string error;            // non-empty on Failed
        std::string latest_tag;       // tag reported by the API (once checked)
        std::string installed_tag;    // caller-supplied current install (for update detection)
        bool        update_available = false;
    };

    // Gitea releases API for the DRG-XMRig fork.
    static constexpr const char* kApiUrl =
        "https://git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/latest";
    // Full release list (newest first, includes pre-releases) for the "Browse all releases" picker.
    static constexpr const char* kReleasesUrl =
        "https://git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases?limit=50";

    XmrigUpdater() = default;
    ~XmrigUpdater();
    XmrigUpdater(const XmrigUpdater&) = delete;
    XmrigUpdater& operator=(const XmrigUpdater&) = delete;

    // Query the latest release on a background thread. `installedTag` (may be empty/unknown) is
    // compared to the API tag to set Progress.update_available. End state: UpToDate / UpdateAvailable
    // / Failed.
    void startCheck(const std::string& installedTag);

    // Download → verify archive → extract (flatten) → verify binary → install into `targetDir` on a
    // background thread. Re-fetches the latest release so it is self-contained. End state: Done /
    // Failed. On Done, getProgress().latest_tag is the version that should be persisted as installed.
    void startInstall(const std::string& targetDir);

    // Fetch the full release list on a background thread (for "Browse all releases"). End state:
    // ReleaseList (then getReleases() holds the list, newest first) / Failed.
    void startListReleases();

    // Snapshot of the release list fetched by startListReleases().
    std::vector<XmrigRelease> getReleases() const;

    // Install a SPECIFIC release (chosen from the browse list) into `targetDir` — same verify/extract
    // path as startInstall, but pinned to `release` instead of latest. End state: Done / Failed.
    void startInstallRelease(const std::string& targetDir, XmrigRelease release);

    void cancel();
    Progress getProgress() const;
    bool isDone() const;   // true once the worker reached a terminal state (Done/Failed/Unavailable)

    // Internal: called by the libcurl progress callback. Publishes live download bytes and returns
    // false to ask curl to abort (when cancel() was requested). Public only so the C callback in the
    // .cpp can reach it without leaking curl types into this header.
    bool onDownloadProgress(double downloadedBytes, double totalBytes);

private:
    void runCheck(std::string installedTag);
    void runListReleases();
    void runInstall(std::string targetDir);
    void installResolved(const std::string& targetDir, const XmrigRelease& rel);  // shared install body
    void setProgress(State state, const std::string& text, double done = 0, double total = 0);
    bool downloadToFile(const std::string& url, const std::string& destPath);
    std::string httpGet(const std::string& url);

    mutable std::mutex mutex_;
    Progress progress_;
    std::vector<XmrigRelease> releases_;
    std::atomic<bool> cancel_requested_{false};
    std::atomic<bool> worker_running_{false};
    std::thread worker_;
};

} // namespace util
} // namespace dragonx
