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

#pragma once

#include "types.h"
#include <atomic>
#include <string>
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <nlohmann/json.hpp>

namespace dragonx {
namespace rpc {

using json = nlohmann::json;
using Callback = std::function<void(const json&)>;
using ErrorCallback = std::function<void(const std::string&)>;

/**
 * @brief A JSON-RPC error carrying the daemon's numeric error code.
 *
 * what() preserves the exact human-readable message (so existing string matching
 * still works); `code` exposes the JSON-RPC error code — notably -28 (RPC_IN_WARMUP)
 * for a daemon still starting up. Derives from std::runtime_error, so every existing
 * `catch (const std::exception&)` continues to handle it unchanged.
 */
class RpcError : public std::runtime_error {
public:
    RpcError(int errorCode, const std::string& message)
        : std::runtime_error(message), code(errorCode) {}
    int code = 0;
};

/**
 * @brief JSON-RPC client for dragonxd
 * 
 * Handles all communication with the dragonxd daemon via JSON-RPC.
 */
class RPCClient {
public:
    using TraceCallback = std::function<void(const std::string& source, const std::string& method)>;

    class TraceScope {
    public:
        explicit TraceScope(std::string source);
        ~TraceScope();

        TraceScope(const TraceScope&) = delete;
        TraceScope& operator=(const TraceScope&) = delete;

    private:
        std::string previous_;
    };

    RPCClient();
    ~RPCClient();

    // Non-copyable
    RPCClient(const RPCClient&) = delete;
    RPCClient& operator=(const RPCClient&) = delete;

    /**
     * @brief Connect to dragonxd
     * @param host RPC host (default: 127.0.0.1)
     * @param port RPC port (default: 18031)
     * @param user RPC username
     * @param password RPC password
     * @return true if connection successful
     */
    bool connect(const std::string& host, const std::string& port,
                 const std::string& user, const std::string& password);

    bool connect(const std::string& host, const std::string& port,
                 const std::string& user, const std::string& password,
                 bool useTls);

    /**
     * @brief Disconnect from dragonxd
     */
    void disconnect();

    /**
     * @brief Check if connected
     */
    bool isConnected() const { return connected_; }

    /**
     * @brief Ask an in-flight call() to abort as soon as possible (thread-safe).
     *
     * Set from another thread (e.g. before stop()-ing the worker on disconnect/shutdown):
     * a curl progress callback aborts the transfer, so a blocked curl_easy_perform returns
     * promptly instead of freezing the UI thread's join() until the request timeout. Cleared
     * on the next connect(); abortRequested() is read by the progress callback.
     */
    void requestAbort();
    void clearAbort();
    bool abortRequested() const noexcept { return abort_.load(std::memory_order_relaxed); }

    /**
     * @brief True if the last connect() succeeded but daemon returned a warmup error.
     * The curl handle is valid and auth succeeded — RPC calls will throw warmup errors
     * until the daemon finishes initializing.
     */
    bool isWarmingUp() const { return warming_up_; }

    /**
     * @brief The warmup status message (e.g. "Activating best chain...").
     * Empty when not in warmup.
     */
    const std::string& getWarmupStatus() const { return warmup_status_; }

    /**
     * @brief Get the error message from the last failed connect() attempt.
     */
    const std::string& getLastConnectError() const { return last_connect_error_; }
    json getLastConnectInfo() const;

    static void setTraceCallback(TraceCallback callback);
    static void setTraceEnabled(bool enabled);
    static bool isTraceEnabled();
    static std::string currentTraceSource();
    static void setTraceSource(std::string source);

    /**
     * @brief Make a raw RPC call
     * @param method RPC method name
     * @param params Method parameters (optional)
     * @return JSON response or null on error
     */
    json call(const std::string& method, const json& params = json::array());

    /**
     * @brief Like call(), but zeroes the raw HTTP response body after parsing (B7).
     *
     * For RPCs whose response carries a secret (z_exportmnemonic / z_exportkey), the parsed
     * value is scrubbed by the caller — but the raw response string this method builds also
     * holds that secret and is otherwise freed without zeroing. Use this variant so the largest,
     * longest-lived plaintext copy doesn't linger in freed heap. Slightly slower (an extra wipe);
     * only worth it for secret-bearing calls.
     */
    json callSecret(const std::string& method, const json& params = json::array());

    /**
     * @brief callSecret() for RPCs whose result is a bare secret string (z_exportkey / dumpprivkey /
     *        z_exportviewingkey). Returns the result string with BOTH the raw response body AND the
     *        parsed json's own copy of the secret zeroed — so no un-scrubbed heap copy survives the
     *        call. The returned std::string is the only remaining copy; the caller owns it and must
     *        wipe it (sodium_memzero) when done. Throws if the result is not a JSON string.
     */
    std::string callSecretString(const std::string& method, const json& params = json::array());

    /**
     * @brief Make a raw RPC call with a custom timeout
     * @param method RPC method name
     * @param params Method parameters
     * @param timeoutSec Timeout in seconds (0 = no timeout)
     * @return JSON response or throws on error
     */
    json call(const std::string& method, const json& params, long timeoutSec);

    /**
     * @brief Make a raw RPC call and return the result field as a string
     * @param method RPC method name
     * @param params Method parameters (optional)
     * @return Raw JSON string of the "result" field (preserves key order)
     */
    std::string callRaw(const std::string& method, const json& params = json::array());

    // High-level API methods - mirror the Qt version

    // Info methods
    void getInfo(Callback cb, ErrorCallback err = nullptr);
    void getBlockchainInfo(Callback cb, ErrorCallback err = nullptr);
    void getMiningInfo(Callback cb, ErrorCallback err = nullptr);

    // Balance methods
    void getBalance(Callback cb, ErrorCallback err = nullptr);
    void z_getTotalBalance(Callback cb, ErrorCallback err = nullptr);
    void listUnspent(Callback cb, ErrorCallback err = nullptr);
    void z_listUnspent(Callback cb, ErrorCallback err = nullptr);

    // Address methods
    void getAddressesByAccount(Callback cb, ErrorCallback err = nullptr);
    void z_listAddresses(Callback cb, ErrorCallback err = nullptr);
    void getNewAddress(Callback cb, ErrorCallback err = nullptr);
    void z_getNewAddress(Callback cb, ErrorCallback err = nullptr);

    // Transaction methods
    void listTransactions(int count, Callback cb, ErrorCallback err = nullptr);
    void z_viewTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
    void getRawTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
    void sendToAddress(const std::string& address, double amount, Callback cb, ErrorCallback err = nullptr);
    void z_sendMany(const std::string& from, const json& recipients, Callback cb, ErrorCallback err = nullptr);

    // Mining methods
    void setGenerate(bool generate, int threads, Callback cb, ErrorCallback err = nullptr);
    void getNetworkHashPS(Callback cb, ErrorCallback err = nullptr);
    void getLocalHashrate(Callback cb, ErrorCallback err = nullptr);

    // Peer methods
    void getPeerInfo(Callback cb, ErrorCallback err = nullptr);
    void listBanned(Callback cb, ErrorCallback err = nullptr);
    void setBan(const std::string& ip, const std::string& command, Callback cb, ErrorCallback err = nullptr, int bantime = 86400);
    void clearBanned(Callback cb, ErrorCallback err = nullptr);

    // Key management
    void dumpPrivKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
    void z_exportKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
    void z_exportViewingKey(const std::string& address, Callback cb, ErrorCallback err = nullptr);
    void importPrivKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err = nullptr);
    void z_importKey(const std::string& key, bool rescan, Callback cb, ErrorCallback err = nullptr);

    // Utility
    void validateAddress(const std::string& address, Callback cb, ErrorCallback err = nullptr);
    void z_validateAddress(const std::string& address, Callback cb, ErrorCallback err = nullptr);
    void getBlock(const std::string& hash_or_height, Callback cb, ErrorCallback err = nullptr);
    void getBlockHash(int height, Callback cb, ErrorCallback err = nullptr);
    void getTransaction(const std::string& txid, Callback cb, ErrorCallback err = nullptr);
    void getWalletInfo(Callback cb, ErrorCallback err = nullptr);
    void stop(Callback cb, ErrorCallback err = nullptr);
    
    // Wallet maintenance
    void rescanBlockchain(int startHeight, Callback cb, ErrorCallback err = nullptr);
    
    // Wallet encryption & locking
    void encryptWallet(const std::string& passphrase, Callback cb, ErrorCallback err = nullptr);
    void walletPassphrase(const std::string& passphrase, int timeout, Callback cb, ErrorCallback err = nullptr);
    void walletLock(Callback cb, ErrorCallback err = nullptr);
    void walletPassphraseChange(const std::string& oldPass, const std::string& newPass,
                                Callback cb, ErrorCallback err = nullptr);

    // Wallet export/import (for decrypt flow)
    void z_exportWallet(const std::string& filename, Callback cb, ErrorCallback err = nullptr);
    void z_importWallet(const std::string& filename, Callback cb, ErrorCallback err = nullptr);
    
    // Shielding operations
    void z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
                          double fee, int limit, Callback cb, ErrorCallback err = nullptr);
    void z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
                          double fee, int limit, Callback cb, ErrorCallback err = nullptr);
    
    // Operation status monitoring
    void z_getOperationStatus(const std::vector<std::string>& opids, Callback cb, ErrorCallback err = nullptr);
    void z_getOperationResult(const std::vector<std::string>& opids, Callback cb, ErrorCallback err = nullptr);
    
    // Received transactions
    void z_listReceivedByAddress(const std::string& address, int minconf, Callback cb, ErrorCallback err = nullptr);

    // Unified callback versions (result + error)
    using UnifiedCallback = std::function<void(const json& result, const std::string& error)>;
    void getInfo(UnifiedCallback cb);
    void rescanBlockchain(int startHeight, UnifiedCallback cb);
    void z_shieldCoinbase(const std::string& fromAddr, const std::string& toAddr,
                          double fee, int limit, UnifiedCallback cb);
    void z_mergeToAddress(const std::vector<std::string>& fromAddrs, const std::string& toAddr,
                          double fee, int limit, UnifiedCallback cb);
    void z_getOperationStatus(const std::vector<std::string>& opids, UnifiedCallback cb);
    void getBlock(int height, UnifiedCallback cb);

private:
    json makePayload(const std::string& method, const json& params = json::array());
    void doRPC(const std::string& method, const json& params, Callback cb, ErrorCallback err);

    // Shared blocking-call core: dumps the payload, performs the request (throwing on a
    // curl transport error) and returns the response body plus HTTP status. Caller must
    // hold curl_mutex_ and have verified impl_->curl.
    std::string performCall(const std::string& method, const json& params, long& httpCodeOut);
    // Centralizes the HTTP-code check and JSON error->RpcError extraction, returning
    // response["result"] on success. When scrubSource is true (secret-bearing calls), the intermediate
    // parse tree's string values are zeroed before it is discarded — parseRpcResult returns a COPY of
    // the result node, so its own tree would otherwise free the secret un-wiped (B7).
    static json parseRpcResult(long httpCode, const std::string& body, bool scrubSource = false);

    // Splits a UnifiedCallback into the (Callback, ErrorCallback) pair used by doRPC.
    static std::pair<Callback, ErrorCallback> splitUnified(UnifiedCallback cb);

    std::string host_;
    std::string port_;
    std::string auth_;  // Base64 encoded "user:password"
    bool connected_ = false;
    std::atomic<bool> abort_{false};  // set cross-thread to abort an in-flight transfer
    bool warming_up_ = false;
    std::string warmup_status_;
    std::string last_connect_error_;
    json last_connect_info_;
    mutable std::recursive_mutex curl_mutex_;  // serializes all curl handle access

    // HTTP client (implementation hidden)
    class Impl;
    std::unique_ptr<Impl> impl_;
};

} // namespace rpc
} // namespace dragonx
