// dragonx-wallet-rebuild — offline recovery helper for a BDB-inconsistent wallet.dat.
//
// Some wallet.dat files are valid Berkeley DB btrees whose EXTENT metadata is stale (the "main"
// subdatabase metapage records a low last_pgno while its live data spans thousands of pages further
// in the file). A tolerant page-walk reads every record, but the daemon's Berkeley DB `verify`
// rejects the file and auto-salvages it — which finds nothing and, on each restart, shrinks the
// wallet to empty (the "salvage cascade" that looks like fund loss). The keys are intact; only the
// DB envelope is broken.
//
// This tool fixes it the way it must be fixed — offline, on the file, before any daemon touches it:
//   1) read all (key,value) records with the tolerant walker (util/wallet_file_probe.h), then
//   2) write them VERBATIM into a fresh, consistent Berkeley DB "main" btree via real libdb put()s,
//      so libdb computes correct extent/metapage bookkeeping itself (sidestepping the whole defect).
// Records are copied byte-for-byte: encrypted key material (ckey/csapzkey/mkey) passes through as
// opaque ciphertext — no passphrase, no decryption, no key material ever interpreted. Only large `tx`
// history values (which live in BDB overflow pages) are skipped; a wallet rescan rebuilds those.
//
// Usage:  dragonx-wallet-rebuild <source-wallet.dat> <output-wallet.dat>
// Output: a single JSON line on stdout; exit 0 on success, non-zero on failure. Never touches the
// source (opens it read-only); refuses to overwrite an existing output (DB_EXCL).

#include "util/wallet_file_probe.h"

#include <db.h>

#include <cstdio>
#include <cstring>
#include <string>

int main(int argc, char** argv)
{
    if (argc < 3) {
        std::fprintf(stderr, "usage: dragonx-wallet-rebuild <source-wallet.dat> <output-wallet.dat>\n");
        return 2;
    }
    const char* src = argv[1];
    const char* dst = argv[2];

    // --- 1) tolerant read (no libdb; reads records the daemon's BDB can't) ---
    const auto rec = dragonx::util::extractWalletBtreeRecords(src);
    if (!rec.parsed) {
        std::printf("{\"ok\":false,\"error\":\"source is not a readable Berkeley DB btree wallet\"}\n");
        return 3;
    }
    if (rec.keyRecords == 0) {
        // Refuse to produce a keyless wallet — nothing to recover, and installing it would look like loss.
        std::printf("{\"ok\":false,\"error\":\"no key records found in source\",\"read\":%zu}\n",
                    rec.records.size());
        return 4;
    }

    // --- 2) write a fresh, consistent BDB "main" btree (what CWalletDB expects) ---
    DB* db = nullptr;
    int r = db_create(&db, nullptr, 0);
    if (r != 0) {
        std::printf("{\"ok\":false,\"error\":\"db_create: %s\"}\n", db_strerror(r));
        return 5;
    }
    // DB_EXCL: never clobber an existing file — the caller passes a fresh path.
    r = db->open(db, nullptr, dst, "main", DB_BTREE, DB_CREATE | DB_EXCL, 0600);
    if (r != 0) {
        std::printf("{\"ok\":false,\"error\":\"open output: %s\"}\n", db_strerror(r));
        db->close(db, 0);
        return 6;
    }
    long wrote = 0;
    for (const auto& kv : rec.records) {
        DBT k, v;
        std::memset(&k, 0, sizeof k);
        std::memset(&v, 0, sizeof v);
        k.data = const_cast<char*>(kv.first.data());   k.size = static_cast<u_int32_t>(kv.first.size());
        v.data = const_cast<char*>(kv.second.data());  v.size = static_cast<u_int32_t>(kv.second.size());
        r = db->put(db, nullptr, &k, &v, 0);
        if (r != 0) {
            std::printf("{\"ok\":false,\"error\":\"put failed: %s\",\"wrote\":%ld}\n", db_strerror(r), wrote);
            db->close(db, 0);
            return 7;
        }
        ++wrote;
    }
    r = db->close(db, 0);   // close flushes correct metadata
    if (r != 0) {
        std::printf("{\"ok\":false,\"error\":\"close: %s\",\"wrote\":%ld}\n", db_strerror(r), wrote);
        return 8;
    }

    std::printf("{\"ok\":true,\"read\":%zu,\"keyRecords\":%d,\"skippedOverflow\":%d,\"wrote\":%ld,\"complete\":%s}\n",
                rec.records.size(), rec.keyRecords, rec.skippedOverflow, wrote, rec.complete ? "true" : "false");
    return 0;
}
