#include "daemon/seed_wallet_creator.h"

#include <chrono>
#include <filesystem>
#include <thread>

#include <sodium.h>

#include "daemon/embedded_daemon.h"
#include "rpc/rpc_client.h"
#include "util/platform.h"

namespace fs = std::filesystem;

namespace dragonx {
namespace daemon {

namespace {

// Random alphanumeric token for the isolated node's throwaway RPC credentials (libsodium CSPRNG;
// sodium_init() has already run at app startup for the chat crypto).
std::string randomToken(int n)
{
    static const char cs[] =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    std::string s;
    s.reserve(n);
    for (int i = 0; i < n; ++i)
        s.push_back(cs[randombytes_uniform(sizeof(cs) - 1)]);
    return s;
}

// A free localhost port for the isolated node — just above the default so it never collides with
// the main daemon (which keeps running on the default port throughout).
int pickFreePort()
{
    for (int p = 21770; p < 21900; ++p)
        if (!EmbeddedDaemon::tcpPortInUse(p))
            return p;
    return 0;
}

}  // namespace

SeedWalletResult SeedWalletCreator::create(bool keepDatadir,
                                           const std::function<void(const std::string&)>& progress)
{
    auto report = [&](const std::string& m) { if (progress) progress(m); };
    SeedWalletResult r;
    std::error_code ec;

    // 1. Isolated throwaway datadir. The Komodo/Hush daemon requires the datadir's basename to be
    //    the assetchain name (DRAGONX) — mirroring ~/.hush/DRAGONX — or it mis-resolves its conf and
    //    RPC port. So the wallet lives in <base>/DRAGONX; `base` is the migration root we clean up.
    const std::string base = util::Platform::getConfigDir() + "/seed-migrate";
    const std::string dataDir = base + "/DRAGONX";
    // W3-2: never blindly wipe a pre-existing temp seed wallet. A prior migration that swept funds into
    // it but was abandoned or crashed before adopting would otherwise have its (fund-bearing) wallet
    // destroyed here. A completed migration removes this dir on adopt, so a leftover means an unfinished
    // one — refuse and point the user at it rather than silently destroying it.
    if (fs::exists(dataDir + "/wallet.dat")) {
        r.error = "A previous seed migration looks unfinished — its temporary wallet is still at\n" + base +
                  "\nResume or cancel it first. If you are certain its funds are already in your main "
                  "wallet, delete that folder and try again.";
        return r;
    }
    fs::remove_all(base, ec);
    fs::create_directories(dataDir, ec);
    if (ec) { r.error = "Could not create the temporary wallet directory."; return r; }

    // 2. Free port + fresh throwaway RPC credentials for the isolated node.
    const int port = pickFreePort();
    if (port <= 0) { r.error = "No free local port for the isolated node."; return r; }
    const std::string user = randomToken(16);
    const std::string pass = randomToken(32);

    // 3. Minimal conf for the isolated node (own creds/port). The DRAGONX RPC is plaintext HTTP on
    //    localhost — `-tls=only` applies to P2P, not the RPC — so the client below connects without
    //    TLS, exactly as the main GUI does (its conf has no rpctls key either).
    const std::string conf = "rpcuser=" + user + "\n"
                             "rpcpassword=" + pass + "\n"
                             "rpcport=" + std::to_string(port) + "\n"
                             "server=1\n";
    if (!util::Platform::writeFileAtomically(dataDir + "/DRAGONX.conf", conf,
                                             /*restrictPermissions=*/true)) {
        r.error = "Could not write the isolated node config.";
        fs::remove_all(base, ec);
        return r;
    }

    // 4. Start the isolated daemon: fresh mnemonic wallet (-usemnemonic=1), no network/sync.
    report("Starting an isolated node…");
    EmbeddedDaemon temp;
    temp.setSkipPortCheck(true);  // runs on `port`, beside the main daemon on the default port
    temp.setNextStartOverride(dataDir, {"-usemnemonic=1", "-connect=0", "-listen=0",
                                        "-maxconnections=0"});
    if (!temp.start("")) {
        r.error = "Could not start the isolated node: " + temp.getLastError();
        fs::remove_all(base, ec);
        return r;
    }

    // 5. Connect to it, retrying until the RPC is responsive and past warmup.
    report("Creating your new seed wallet…");
    rpc::RPCClient cli;
    bool ready = false;
    for (int i = 0; i < 90 && !ready; ++i) {
        if (cli.connect("127.0.0.1", std::to_string(port), user, pass, /*useTls=*/false)) {
            try { cli.call("getinfo"); ready = true; }   // succeeds only once past warmup (-28)
            catch (...) { cli.disconnect(); }
        }
        if (!ready) std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    if (!ready) {
        r.error = "The isolated node did not become ready in time.";
        temp.stop(20000);
        fs::remove_all(base, ec);
        return r;
    }

    // 6. Export the new seed phrase + a fresh shielded receive address (the future sweep target).
    try {
        auto m = cli.callSecret("z_exportmnemonic");   // zero the raw body too (B7)
        if (m.contains("mnemonic") && m["mnemonic"].is_string()) {
            // Take our copy, then scrub the json node's own copy so it isn't freed in the clear (B7).
            auto& mn = m["mnemonic"].get_ref<std::string&>();
            r.seedPhrase = mn;
            if (!mn.empty()) sodium_memzero(&mn[0], mn.size());
        }
        r.destAddress = cli.call("z_getnewaddress").get<std::string>();
        r.ok = !r.seedPhrase.empty() && !r.destAddress.empty();
        if (!r.ok) r.error = "The isolated node returned an empty seed or address.";
    } catch (const std::exception& e) {
        const std::string what = e.what();
        // "Method not found" (JSON-RPC -32601) means this dragonxd predates mnemonic support —
        // it has no z_exportmnemonic RPC (the older bundled binary). Migrate-to-seed can't work
        // until the daemon is updated, so give an actionable message, not the raw RPC error.
        if (what.find("Method not found") != std::string::npos ||
            what.find("-32601") != std::string::npos) {
            r.error = "This DragonX daemon is too old to create a seed wallet — it lacks mnemonic "
                      "support (the z_exportmnemonic RPC). Update to the latest DragonX daemon "
                      "(Settings -> NODE & SECURITY -> Check for updates, or Install bundled), then "
                      "try again.";
        } else {
            r.error = std::string("Seed export failed: ") + what;
        }
    }

    // W1-2: never hand back a live seed on a failure path. If the mnemonic was exported but a
    // later step failed (empty address, or z_getnewaddress threw), the caller discards this
    // result without wiping it, which would leave the seed resident. Success keeps it deliberately.
    if (!r.ok && !r.seedPhrase.empty()) {
        sodium_memzero(&r.seedPhrase[0], r.seedPhrase.size());
        r.seedPhrase.clear();
    }

    // 7. Stop the isolated node (graceful; it flushes its tiny empty chain quickly).
    cli.disconnect();
    temp.stop(20000);

    // 8. Keep the temp wallet for a later sweep/adopt step, or scrub it. tempDatadir is the
    //    migration root `base`; the new wallet.dat lives in <base>/DRAGONX.
    r.tempDatadir = base;
    if (!keepDatadir || !r.ok) {
        fs::remove_all(base, ec);
        r.tempDatadir.clear();
    }
    return r;
}

}  // namespace daemon
}  // namespace dragonx
