// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// Deterministic in-process fake SDXL backend for lite-wallet tests.
//
// Provides a LiteClientBridgeApi whose function pointers return canned JSON and
// track owned-string allocation/free counts, so tests can drive the lite bridge
// (and, from M1 on, the lite services) without a real litelib_* library, network,
// or the DRAGONX_ENABLE_LITE_BACKEND build flag. Build a bridge with:
//
//     auto bridge = dragonx::wallet::LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi());
//
// Invariant for leak/double-free checks: after a test, g_liteFakeAlloc == g_liteFakeFreed.

#pragma once

#include "wallet/lite_client_bridge.h"

#include <atomic>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <thread>

namespace dragonx {
namespace test {

// Owned-string accounting (atomic: a detached sync thread may touch these concurrently).
inline std::atomic<long> g_liteFakeAlloc{0};   // owned strings handed to the bridge
inline std::atomic<long> g_liteFakeFreed{0};   // owned strings released via freeString
inline bool g_liteFakeWalletExists = true;
inline bool g_liteFakeServerOnline = true;
inline bool g_liteFakeShutdownCalled = false;
inline std::atomic<bool> g_liteFakeSyncBlock{false};  // when true, the "sync" command blocks
inline std::atomic<bool> g_liteFakeBadBalance{false}; // when true, "balance" returns invalid JSON
inline std::atomic<bool> g_liteFakeSendFails{false};  // when true, "send"/"shield" return {"error":..}
inline std::atomic<long> g_liteFakeSaveCount{0};      // counts "save" commands (persistence checks)
inline std::atomic<bool> g_liteFakeEncrypted{false};  // wallet-encryption state machine (encrypt/...)
inline std::atomic<bool> g_liteFakeLocked{false};     // spending-keys-locked state
// initialize_existing fails for any server URL containing this substring (open-failover tests).
inline std::string g_liteFakeDeadServerSubstr;
// initialize_existing returns a warming-up (-28) error for URLs containing this substring.
inline std::string g_liteFakeWarmupServerSubstr;

inline void resetLiteFakeCounters()
{
    g_liteFakeAlloc = 0;
    g_liteFakeFreed = 0;
    g_liteFakeShutdownCalled = false;
    g_liteFakeDeadServerSubstr.clear();
    g_liteFakeWarmupServerSubstr.clear();
}

inline char* liteFakeDup(const char* s)
{
    char* p = static_cast<char*>(std::malloc(std::strlen(s) + 1));
    std::strcpy(p, s);
    ++g_liteFakeAlloc;
    return p;
}

inline bool liteFakeWalletExists(const char*) { return g_liteFakeWalletExists; }
// Match the real litelib_* return shapes so tests exercise the production walletReady path:
// create/restore return a seed object ({"seed":..,"birthday":..}); open returns the bare
// string "OK" (NOT JSON) — see litelib_initialize_existing.
inline char* liteFakeInitNew(bool, const char* server)
{
    // Honor the dead/warmup substrings so create-with-failover can be exercised (mirrors how the
    // real initialize_new contacts the server first and fails before writing a wallet).
    if (server) {
        const std::string s(server);
        if (!g_liteFakeWarmupServerSubstr.empty() &&
            s.find(g_liteFakeWarmupServerSubstr) != std::string::npos)
            return liteFakeDup("Error: grpc-message: \"error requesting block: -28: "
                               "Activating best chain...\"");
        if (!g_liteFakeDeadServerSubstr.empty() &&
            s.find(g_liteFakeDeadServerSubstr) != std::string::npos)
            return liteFakeDup("Error: could not connect to server");
    }
    return liteFakeDup("{\"seed\":\"fake seed phrase words\",\"birthday\":0}");
}
inline char* liteFakeInitFromPhrase(bool, const char*, const char*,
                                    unsigned long long, unsigned long long, bool)
{
    return liteFakeDup("{\"seed\":\"fake seed phrase words\",\"birthday\":0}");
}
// initialize_existing fails (server unreachable) for any server URL containing
// g_liteFakeDeadServerSubstr, so tests can exercise the controller's open-with-failover.
inline char* liteFakeInitExisting(bool, const char* server)
{
    if (server) {
        const std::string s(server);
        if (!g_liteFakeWarmupServerSubstr.empty() &&
            s.find(g_liteFakeWarmupServerSubstr) != std::string::npos) {
            // Server is up but warming up — mirrors the real -28 / "Activating best chain".
            return liteFakeDup("Error: grpc-message: \"error requesting block: -28: "
                               "Activating best chain...\"");
        }
        if (!g_liteFakeDeadServerSubstr.empty() &&
            s.find(g_liteFakeDeadServerSubstr) != std::string::npos) {
            return liteFakeDup("Error: could not connect to server");  // bridge maps to ok=false
        }
    }
    return liteFakeDup("OK");
}
inline char* liteFakeExecute(const char* command, const char* args)
{
    // new-address generation returns a JSON array with the new address (type from args: zs/R).
    if (command && std::strcmp(command, "new") == 0) {
        return liteFakeDup(args && std::strcmp(args, "R") == 0 ? "[\"R1fakenew\"]" : "[\"zs1fakenew\"]");
    }
    // A command named "boom" yields an "Error:"-prefixed response (the bridge's
    // looksLikeError contract maps that to ok=false).
    if (command && std::strcmp(command, "boom") == 0) {
        return liteFakeDup("Error: simulated lite backend failure");
    }
    // Command-appropriate canned responses matching the litelib JSON shapes (see
    // tests/fixtures/lite/result_parsers.json), so the gateway/sync refresh path parses.
    if (command) {
        const char* c = command;
        if (std::strcmp(c, "sync") == 0) {
            // Simulate the real backend's blocking full sync when requested, so tests can
            // verify shutdown doesn't hang on an in-flight sync.
            while (g_liteFakeSyncBlock.load()) std::this_thread::sleep_for(std::chrono::milliseconds(5));
            return liteFakeDup("{\"result\":\"success\"}");
        }
        if (std::strcmp(c, "syncstatus") == 0)  // real backend shape: "syncing" is a string
            return liteFakeDup("{\"syncing\":\"true\",\"synced_blocks\":1000,\"total_blocks\":1000}");
        if (std::strcmp(c, "balance") == 0 && g_liteFakeBadBalance.load())
            return liteFakeDup("not-json");  // simulate a shape/parse failure for one command
        if (std::strcmp(c, "balance") == 0)
            return liteFakeDup("{\"tbalance\":100000000,\"zbalance\":200000000,\"unconfirmed\":50000000,"
                               "\"verified_zbalance\":180000000,\"spendable_zbalance\":170000000}");
        if (std::strcmp(c, "addresses") == 0)
            return liteFakeDup("{\"z_addresses\":[\"zs1fakeaddr\"],\"t_addresses\":[\"R1fakeaddr\"]}");
        if (std::strcmp(c, "notes") == 0)
            return liteFakeDup("{\"unspent_notes\":[],\"utxos\":[],\"pending_notes\":[],\"pending_utxos\":[]}");
        if (std::strcmp(c, "list") == 0)
            return liteFakeDup("[{\"txid\":\"faketx\",\"datetime\":1700000000,\"block_height\":990,"
                               "\"unconfirmed\":false,\"address\":\"zs1fakeaddr\",\"amount\":150000000,\"memo\":\"\"}]");
        if (std::strcmp(c, "height") == 0) return liteFakeDup("{\"height\":1000}");
        if (std::strcmp(c, "info") == 0)
            return liteFakeDup("{\"chain_name\":\"main\",\"version\":\"sdxl-fake\",\"latest_block_height\":1000}");
        // M4 spend/backup commands. send/shield report failure via {"error":..} (NOT an
        // "Error:" prefix), matching the real backend's object!{ "error" => e } shape.
        if (std::strcmp(c, "send") == 0) {
            if (g_liteFakeSendFails.load()) return liteFakeDup("{\n  \"error\": \"insufficient funds\"\n}");
            return liteFakeDup("{\n  \"txid\": \"faketxid123\"\n}");
        }
        if (std::strcmp(c, "shield") == 0) {
            if (g_liteFakeSendFails.load()) return liteFakeDup("{\n  \"error\": \"nothing to shield\"\n}");
            return liteFakeDup("{\n  \"txid\": \"fakeshieldtxid\"\n}");
        }
        if (std::strcmp(c, "export") == 0)
            return liteFakeDup("[{\"address\":\"zs1fakeaddr\",\"private_key\":\"SECRET-ZKEY\","
                               "\"viewing_key\":\"zxviewsFAKE\"}]");
        if (std::strcmp(c, "seed") == 0)
            return liteFakeDup("{\"seed\":\"fake seed phrase words\",\"birthday\":0}");
        // import (shielded key) / timport (transparent WIF): do_import_* pretty-print JSON.
        if (std::strcmp(c, "import") == 0 || std::strcmp(c, "timport") == 0)
            return liteFakeDup("{\"result\":\"success\",\"address\":\"zs1imported\"}");
        if (std::strcmp(c, "save") == 0) {
            ++g_liteFakeSaveCount;
            return liteFakeDup("{\"result\":\"success\"}");
        }
        // Encryption state machine. encrypt -> encrypted AND locked (matches the real backend,
        // which removes keys from memory right after encrypting — verified via lite_smoke --encrypt);
        // unlock/lock toggle locked; decrypt clears it; encryptionstatus reports {encrypted,locked}.
        if (std::strcmp(c, "encrypt") == 0) {
            g_liteFakeEncrypted = true; g_liteFakeLocked = true;
            return liteFakeDup("{\"result\":\"success\"}");
        }
        if (std::strcmp(c, "unlock") == 0) {
            g_liteFakeLocked = false;
            return liteFakeDup("{\"result\":\"success\"}");
        }
        if (std::strcmp(c, "lock") == 0) {
            g_liteFakeLocked = true;
            return liteFakeDup("{\"result\":\"success\"}");
        }
        if (std::strcmp(c, "decrypt") == 0) {
            g_liteFakeEncrypted = false; g_liteFakeLocked = false;
            return liteFakeDup("{\"result\":\"success\"}");
        }
        if (std::strcmp(c, "encryptionstatus") == 0) {
            return liteFakeDup(g_liteFakeEncrypted.load()
                ? (g_liteFakeLocked.load() ? "{\"encrypted\":true,\"locked\":true}"
                                           : "{\"encrypted\":true,\"locked\":false}")
                : "{\"encrypted\":false,\"locked\":false}");
        }
    }
    // Default for any other/unknown command.
    return liteFakeDup("{\"version\":\"sdxl-fake\"}");
}
inline void liteFakeFree(char* v)
{
    if (v) {
        ++g_liteFakeFreed;
        std::free(v);
    }
}
inline bool liteFakeServerOnline(const char*) { return g_liteFakeServerOnline; }
inline void liteFakeShutdown() { g_liteFakeShutdownCalled = true; }

inline dragonx::wallet::LiteClientBridgeApi makeFakeLiteApi()
{
    dragonx::wallet::LiteClientBridgeApi api;
    api.walletExists = &liteFakeWalletExists;
    api.initializeNew = &liteFakeInitNew;
    api.initializeNewFromPhrase = &liteFakeInitFromPhrase;
    api.initializeExisting = &liteFakeInitExisting;
    api.execute = &liteFakeExecute;
    api.freeString = &liteFakeFree;
    api.checkServerOnline = &liteFakeServerOnline;
    api.shutdown = &liteFakeShutdown;
    return api;
}

} // namespace test
} // namespace dragonx
