// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// daemon_startup_diagnosis.h — pure classifiers over a crashed daemon's captured console output,
// so the app can offer a targeted one-click fix instead of a bare "daemon crashed" / silent no-funds.

#pragma once

#include <string>
#include <utility>
#include <vector>

namespace dragonx {
namespace daemon {

// True when dragonxd aborted because its BLOCK DATABASE could not be loaded — either a
// daemon-vs-chaindata serialization-format mismatch after a daemon update (the deterministic
// "non-canonical optional discriminant" → "Error loading block database" → "Aborted block database
// rebuild. Exiting." sequence) or a genuinely corrupt/incomplete block index. In BOTH cases the fix
// is the same: `-reindex` rebuilds the index + chainstate from the intact raw blocks (blk*.dat).
// This is what otherwise silently presents as a wallet with zero balance — the node never starts.
inline bool blockDbOutputLooksBroken(const std::string& out)
{
    return out.find("Error loading block database")        != std::string::npos
        || out.find("non-canonical optional discriminant") != std::string::npos
        || out.find("Aborted block database rebuild")      != std::string::npos
        || out.find("LoadBlockIndex()")                    != std::string::npos;  // "... : failed to read value"
}

// True when dragonxd AUTO-RECOVERED the wallet on startup: on any BDB-verify failure it moves the
// original wallet.dat to "wallet.{timestamp}.bak", salvages readable keys into a fresh wallet.dat, and
// keeps running — no flag required (CWallet::Verify → CDBEnv::Verify(walletFile, CWalletDB::Recover)).
// The salvage can be incomplete (or a false positive from stale/cross-platform BDB env state), so the
// node silently comes up on a possibly-empty wallet — which reads as fund loss unless we surface it.
inline bool walletAutoRecovered(const std::string& out)
{
    // Cover BOTH salvage outcomes. A successful salvage prints the "data salvaged"/"saved as wallet.<ts>.bak"
    // warning; a FAILED one (e.g. an inconsistent-but-readable file where aggressive salvage finds no
    // records) prints "salvage failed"/"found no records". In every case CWalletDB::Recover first logs
    // "Renamed <wallet> to wallet.<ts>.bak" and CDBEnv::Salvage logs its own banner — those two fire the
    // instant a salvage begins, before the daemon may abort, so they're the earliest reliable signal.
    return out.find("CDBEnv::Salvage")                     != std::string::npos   // salvage is running
        || out.find("wallet.dat corrupt, data salvaged")   != std::string::npos   // RECOVER_OK
        || out.find("Original wallet.dat saved as wallet.") != std::string::npos
        || out.find("wallet.dat corrupt, salvage failed")  != std::string::npos   // RECOVER_FAIL
        || out.find("found no records in wallet")           != std::string::npos   // aggressive salvage empty
        || (out.find("Renamed ") != std::string::npos && out.find(" to wallet.") != std::string::npos
            && out.find(".bak") != std::string::npos);                             // Recover moved wallet.dat aside
}

// True when dragonxd (v1.3.0+) opened the wallet in DEGRADED mode: a wallet.dat that lost its hdchain
// record (e.g. an old `-salvagewallet` output) now OPENS — existing keys stay intact and spendable —
// instead of aborting, but the daemon can no longer derive NEW HD keys, so z_getnewaddress /
// z_shieldcoinbase / a t->z z_sendmany fail with "HD seed not found". The only signal is a startup log
// line; pre-1.3.0 daemons never emit it, so this classifier is naturally a no-op against them.
inline bool walletOpenedDegraded(const std::string& out)
{
    return out.find("Wallet opened in DEGRADED mode") != std::string::npos;
}

// If `name` is a daemon salvage backup "wallet.<unixtime>.bak", return its timestamp; else -1.
inline long long parseWalletSalvageBakTs(const std::string& name)
{
    if (name.rfind("wallet.", 0) != 0) return -1;                                   // must start "wallet."
    if (name.size() < 12 || name.compare(name.size() - 4, 4, ".bak") != 0) return -1;  // ...and end ".bak"
    const std::string mid = name.substr(7, name.size() - 7 - 4);                    // digits between the dots
    if (mid.empty() || mid.size() > 18) return -1;
    for (char c : mid) if (c < '0' || c > '9') return -1;
    long long ts = 0;
    for (char c : mid) ts = ts * 10 + (c - '0');
    return ts;
}

// Most RECENT salvage backup (highest timestamp). Pure, testable.
inline std::string newestWalletSalvageBak(const std::vector<std::string>& filenames)
{
    long long best = -1;
    std::string bestName;
    for (const auto& f : filenames) {
        const long long ts = parseWalletSalvageBakTs(f);
        if (ts > best) { best = ts; bestName = f; }
    }
    return bestName;
}

// LARGEST salvage backup, from (filename, fileSize) pairs — the least-salvaged one, i.e. the original.
// This is what "Restore original wallet" should use: a salvage CASCADE shrinks the wallet each round, so
// the newest .bak is the WORST and the largest is the pristine pre-salvage original (an emptied salvage
// is tiny; a real wallet is large). Ties break toward the newest timestamp. Returns "" if none present.
inline std::string largestWalletSalvageBak(const std::vector<std::pair<std::string, unsigned long long>>& files)
{
    std::string bestName;
    unsigned long long bestSize = 0;
    long long bestTs = -1;
    for (const auto& fp : files) {
        const long long ts = parseWalletSalvageBakTs(fp.first);
        if (ts < 0) continue;
        if (fp.second > bestSize || (fp.second == bestSize && ts > bestTs)) {
            bestSize = fp.second; bestTs = ts; bestName = fp.first;
        }
    }
    return bestName;
}

}  // namespace daemon
}  // namespace dragonx
