// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// App-owned controller that drives the lite wallet. It constructs and owns the
// lite services with bridge calls ENABLED (the services otherwise default to
// allowBridgeCalls=false and are never instantiated), executes real create/open/
// restore operations through the linked SDXL backend, securely wipes secrets after
// use, tracks wallet-open state, and invokes a persistence callback on success.
//
// Construction:
//   - Production:  LiteWalletController::createLinked(caps, connectionSettings)
//                  (uses LiteClientBridge::linkedSdxl(); requires DRAGONX_ENABLE_LITE_BACKEND).
//   - Tests:       construct directly with an injected bridge
//                  (e.g. LiteClientBridge::fromApi(makeFakeLiteApi())).

#pragma once

#include "lite_client_bridge.h"
#include "lite_connection_service.h"
#include "lite_wallet_lifecycle_service.h"
#include "lite_wallet_gateway.h"
#include "lite_rollout_policy.h"
#include "lite_sync_service.h"
#include "lite_wallet_state_mapper.h"
#include "wallet_backend.h"
#include "wallet_capabilities.h"

#include <atomic>
#include <condition_variable>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <vector>

namespace dragonx {
struct WalletState;  // data/wallet_state.h

namespace wallet {

// Securely zero and clear a string holding secret material (seed/passphrase).
void secureWipeLiteSecret(std::string& secret);

// Apply a normalized lite refresh model onto the app's WalletState (the last hop the
// existing Balance/Receive/Transactions tabs read from). Converts zatoshis -> DRGX,
// splits addresses into shielded/transparent, and maps sync progress. Mutates in place
// (WalletState is non-copyable); only sections present in the model are touched.
void applyLiteRefreshModelToWalletState(const LiteWalletAppRefreshModel& model,
                                        dragonx::WalletState& state);

struct LiteWalletControllerOptions {
    bool allowBridgeCalls = true;
    bool rolloutBlocked = false;       // runtime kill-switch / staged-rollout gate is blocking
    std::string rolloutMessage;        // user-facing reason when rolloutBlocked
};

struct LiteNewAddressResult {
    bool ok = false;
    std::string address;
    std::string error;
};

// Result of an interactive console command (lite Console tab). `response` is the raw backend
// output (JSON or text). It may contain SECRET material (e.g. seed/export) because the user can
// run any command, so it is shown only in the console view and NEVER routed through LiteDiagnostics.
struct LiteConsoleResult {
    std::string command;    // the command line that was run, echoed back
    std::string response;   // raw backend response (or an error message)
    bool ok = false;
};

// A single send recipient. amountZatoshis is in zatoshis (1e-8 DRGX); memo is ignored by the
// backend for transparent destination addresses.
struct LiteSendRecipient {
    std::string address;
    std::uint64_t amountZatoshis = 0;
    std::string memo;
};

struct LiteSendRequest {
    std::vector<LiteSendRecipient> recipients;
};

// send and shield both broadcast a transaction and yield its txid (or an error). NOTE: the
// backend reports failure via {"error":..} in the response body, NOT the bridge's "Error:"
// prefix, so the result is derived by inspecting the parsed JSON.
struct LiteBroadcastResult {
    bool ok = false;
    std::string txid;
    std::string error;
};

// importKey outcome. `detail` is the backend's (non-secret) confirmation payload.
struct LiteImportResult {
    bool ok = false;
    std::string detail;
    std::string error;
};

// SECRET RESULT: holds exported per-address private keys ({address, private_key, viewing_key}).
// Treat privateKeysJson as sensitive: never log it; wipe it after the user has saved the backup.
struct LiteExportResult {
    bool ok = false;
    std::string privateKeysJson;
    std::string error;
};

// SECRET RESULT: holds the wallet seed phrase. Treat seedPhrase as sensitive: never log it;
// wipe it after the user has saved the backup.
struct LiteSeedResult {
    bool ok = false;
    std::string seedPhrase;
    std::uint64_t birthday = 0;
    std::string error;
};

// Wallet encryption state (from the backend `encryptionstatus`). `locked` means the spending
// keys are not in memory (an encrypted wallet loads locked; unlock to spend).
struct LiteEncryptionStatus {
    bool ok = false;
    bool encrypted = false;
    bool locked = false;
    std::string error;
};

// Result of an encrypt/decrypt operation (the passphrase is wiped by the controller).
struct LiteEncryptionResult {
    bool ok = false;
    std::string error;
};

class LiteWalletController {
public:
    LiteWalletController(WalletCapabilities capabilities,
                         LiteConnectionSettings connectionSettings,
                         LiteClientBridge bridge,
                         LiteWalletControllerOptions options = LiteWalletControllerOptions{});

    ~LiteWalletController();  // stops + joins the background refresh worker

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

    // Production factory: links the SDXL backend compiled into this build. The optional rollout
    // decision (runtime kill-switch / staged rollout) gates lifecycle execution: when not allowed,
    // availability() reports RolloutDisabled and create/open/restore are blocked with its message.
    static std::unique_ptr<LiteWalletController> createLinked(
        WalletCapabilities capabilities,
        LiteConnectionSettings connectionSettings,
        LiteRolloutDecision rollout = LiteRolloutDecision{});

    // Invoked after a wallet becomes ready, so the owner can persist settings.
    void setPersistCallback(std::function<void()> callback) { persist_ = std::move(callback); }

    bool walletOpen() const { return walletOpen_; }
    // True if a wallet file already exists on disk for this chain. Use to auto-open on startup
    // (open via openWallet({}) needs no passphrase — initialize_existing just loads the file).
    bool walletExists() const;
    const WalletBackendStatus& status() const { return status_; }
    LiteWalletLifecycleAvailability availability() const { return lifecycle_.availability(); }

    // Execute a real lifecycle operation. The request is taken by value; its secret
    // fields are securely wiped before returning. On a ready wallet, walletOpen()
    // becomes true and the persist callback (if any) fires.
    LiteWalletLifecycleResult createWallet(LiteWalletCreateRequest request);
    LiteWalletLifecycleResult openWallet(LiteWalletOpenRequest request);
    LiteWalletLifecycleResult restoreWallet(LiteWalletRestoreRequest request);

    // Asynchronous open of an EXISTING wallet WITH server failover: tries the selected lite
    // server first, then the other usable defaults, on a background thread — so an unreachable
    // server never freezes the UI and a single dead server no longer strands the wallet.
    // Non-blocking; call pumpAsyncOpen() each tick to finalize. Returns false if a wallet is
    // already open, an open is already in progress, no wallet exists, or the rollout gate blocks
    // lite. On all-servers-fail, status()/lastOpenError() report the reason.
    bool beginOpenExisting();
    // Asynchronously CREATE a new wallet (new seed) with the same server failover as open:
    // initialize_new contacts the server before writing any file, so an unreachable server fails
    // cleanly and the next is tried. On success the seed lives in the wallet — read it back with
    // exportSeed(). Non-blocking; finalized by pumpAsyncOpen(). Returns false if a wallet is open,
    // an open/create is in progress, the rollout gate blocks lite, or no usable server exists.
    bool beginCreateWallet();
    // Finalize a completed async open/create on the calling (main) thread: flips walletOpen()/status
    // and starts sync, or records the failure. Cheap no-op when nothing is pending. Call each tick.
    void pumpAsyncOpen();
    bool openInProgress() const { return openRunning_ && openRunning_->load(); }

    // Asynchronous FULL lifecycle (create / open / restore WITH passphrase + restore params) with
    // the same server failover as beginOpenExisting(). Unlike beginCreateWallet() (plain new seed,
    // no passphrase), these carry the complete request so the Settings page can create with
    // encryption, open an encrypted wallet, or restore from a seed without freezing the UI on a
    // flaky server. Non-blocking; finalized by pumpLifecycleResult() on the main thread. The
    // request is taken by value and its secret fields are securely wiped once the attempt finishes.
    // Returns false if a wallet is open, a lifecycle/open is already in progress, or no usable
    // server exists (the request's secrets are still wiped on that path).
    bool beginCreateWalletAsync(LiteWalletCreateRequest request);
    bool beginOpenWalletAsync(LiteWalletOpenRequest request);
    bool beginRestoreWalletAsync(LiteWalletRestoreRequest request);
    bool lifecycleRequestInProgress() const { return lifecycleRunning_ && lifecycleRunning_->load(); }
    // Finalize a completed async lifecycle request on the main thread (flip walletOpen()/status,
    // persist, start sync) and cache the result for the UI. Cheap no-op when nothing is pending.
    // Call each tick alongside pumpAsyncOpen().
    void pumpLifecycleResult();
    // The most recent finalized async-lifecycle result (create/open/restore), for UI display.
    const LiteWalletLifecycleResult& lastLifecycleResult() const { return lastLifecycleResult_; }
    const std::string& lastOpenError() const { return lastOpenError_; }
    // True if the last failed open hit a server that was merely warming up (JSON-RPC -28 /
    // "Activating best chain"): the server is healthy and will be ready shortly, so the caller
    // should retry sooner rather than waiting out the normal interval.
    bool lastOpenWasWarmup() const { return lastOpenWarming_; }

    bool syncStarted() const { return syncStarted_; }
    bool syncComplete() const { return syncDone_ && syncDone_->load(); }

    // Launch the backend sync on a detached background thread (NON-blocking; the backend's
    // `sync` command runs a full, uninterruptible chain scan). Auto-invoked when a lifecycle
    // op produces a ready wallet; safe to call once.
    void startSync();

    // Re-download and re-scan every block from the lite server: runs the backend `rescan`
    // command (which clears the wallet's synced block cache and re-syncs from its birthday
    // height) on a detached thread, reusing the sync progress + refresh machinery. No-op if no
    // wallet is open or a scan is already running. True if a rescan was actually started.
    bool startRescan();

    // True while the initial sync OR a rescan is actively scanning (not yet complete).
    bool scanInProgress() const { return syncStarted_ && !(syncDone_ && syncDone_->load()); }

    // Generate a new address (shielded if true, else transparent) via the backend. Fast (local
    // key derivation), safe to call on the UI thread; the next refresh lists the new address.
    LiteNewAddressResult newAddress(bool shielded);

    // --- M4: spend & backup ---------------------------------------------------------------
    // Build & broadcast a transaction (send) or shield all transparent funds. Proving can take
    // seconds, so the public entry points are ASYNCHRONOUS: they run the blocking core on a
    // detached thread and deliver the result to a main-thread slot drained by
    // takeBroadcastResult(). Each returns false if a broadcast is already in flight or no wallet
    // is open. Only one broadcast runs at a time. After a successful broadcast the periodic
    // refresh surfaces the new (unconfirmed) transaction and tracks its confirmations.
    bool sendTransaction(const LiteSendRequest& request);
    bool shieldFunds(const std::string& optionalAddress = {});
    bool broadcastInProgress() const { return broadcastRunning_ && broadcastRunning_->load(); }
    bool takeBroadcastResult(LiteBroadcastResult& out);

    // --- Interactive console (lite Console tab) ---------------------------------------------
    // Run an arbitrary backend command — the same verbs as silentdragonxlite-cli (balance, info,
    // height, list, notes, addresses, sync, syncstatus, new, send, shield, encrypt, …). ASYNC:
    // some commands (sync/rescan/send/shield/import) block, so the bridge call runs on a detached
    // thread (captures the shared bridge, never `this`) and the result is delivered to a main-
    // thread slot drained by takeConsoleResult(). `commandLine` is "<command> [args]" — the first
    // whitespace-delimited token is the command and the remainder is passed as the single arg
    // string the backend expects (litelib_execute does not split args; use the JSON form for send).
    // Returns false if a console command is already running or no backend is linked. The caller
    // (tab) intercepts `clear`/`quit` BEFORE calling this — backend `clear` wipes wallet history.
    // Responses may contain secrets and are NEVER logged to LiteDiagnostics.
    bool runConsoleCommand(std::string commandLine);
    bool consoleCommandInProgress() const { return consoleRunning_ && consoleRunning_->load(); }
    bool takeConsoleResult(LiteConsoleResult& out);

    // Synchronous cores for send/shield (block on the backend; safe off the UI thread). Used by
    // the async entry points above and directly by tests.
    LiteBroadcastResult sendTransactionBlocking(const LiteSendRequest& request);
    LiteBroadcastResult shieldFundsBlocking(const std::string& optionalAddress = {});

    // Fast, local secret operations (synchronous). importKey takes the key BY VALUE and securely
    // wipes it before returning (transparent WIF vs shielded key is auto-detected). exportPrivate
    // Keys / exportSeed return SECRET material the caller must treat as sensitive and wipe.
    LiteImportResult importKey(std::string spendingOrViewingKey);
    LiteExportResult exportPrivateKeys(const std::string& optionalAddress = {});
    LiteSeedResult exportSeed();

    // Persist the wallet to disk (backend `save`). The backend auto-saves on new-address/import,
    // but NOT after sync/send/shield — the controller triggers a save at those points so a scan
    // (~30 min on first run) and sent transactions survive a restart. Also callable explicitly.
    // Returns false when no wallet is open or the backend save fails.
    bool saveWallet();

    // --- Wallet encryption (passphrase). All take the passphrase BY VALUE and securely wipe it. ---
    // encrypt: set a passphrase on an unencrypted wallet. unlock/lock: bring the spending keys
    // into / out of memory. decrypt: permanently remove encryption. encryptionStatus: query state
    // (also folded into the periodic refresh so WalletState.isLocked()/isEncrypted() track it).
    LiteEncryptionResult encryptWallet(std::string passphrase);
    bool unlockWallet(std::string passphrase);
    bool lockWallet();
    LiteEncryptionResult decryptWallet(std::string passphrase);
    LiteEncryptionStatus encryptionStatus();

    // Poll sync status + fetch balance/addresses/transactions, and apply the result into the
    // app's WalletState. Returns true if state was updated. Safe no-op when no wallet is open.
    // Synchronous (blocks on the backend); used by tests and as the worker's unit of work.
    bool refreshWalletState(dragonx::WalletState& state);

    // Synchronous refresh that returns the mapped model (or nullopt). Pure w.r.t. shared
    // state; safe to call from the background worker. nullopt when no wallet is open or the
    // refresh produced nothing usable.
    std::optional<LiteWalletAppRefreshModel> refreshModel();

    // Main-thread handoff: if the background worker has produced a fresh model since the last
    // call, move it into `out` and return true. Apply it with applyLiteRefreshModelToWalletState.
    bool takeRefreshedModel(LiteWalletAppRefreshModel& out);

private:
    void onLifecycleResult(const LiteWalletLifecycleResult& result);
    void startWorker();
    void stopWorker();
    void workerLoop();
    // Launch `op` (a self-contained broadcast that must NOT capture `this`) on the detached
    // broadcast thread. Returns false if a broadcast is already running.
    bool startBroadcast(std::function<LiteBroadcastResult()> op);

    // The bridge is shared (not just owned) so the detached, uninterruptible sync thread can
    // safely outlive the controller: it holds a ref, so the underlying bridge is destroyed
    // (and litelib_shutdown called) only once BOTH the controller and a running sync release it.
    //
    // Concurrent execute() is intentionally NOT guarded by a C++ mutex here. The design relies
    // on overlapping calls (the detached sync thread runs the long `sync` while the refresh
    // worker polls `syncstatus`, and the UI thread may call `new`). litelib serializes wallet
    // access internally: LightClient.wallet is an Arc<RwLock<LightWallet>> (do_new_address takes
    // write(), do_balance takes read(), do_sync_internal takes read()/write() and writes
    // sync_status separately), so concurrent execute() calls cannot corrupt state. A coarse
    // mutex would instead serialize sync against syncstatus polling and defeat the design.
    std::shared_ptr<LiteClientBridge> bridge_;
    std::string chainName_;  // backend chain id (for walletExists); from connection settings
    LiteConnectionSettings connectionSettings_;  // kept for the failover candidate-server list
    WalletCapabilities capabilities_;            // for thread-local lifecycle services (async path)
    LiteWalletLifecycleOptions lifecycleOptions_;  // ditto (allowBridgeCalls / rollout gate)
    LiteWalletLifecycleService lifecycle_;
    LiteWalletGateway gateway_;
    LiteSyncService sync_;
    std::function<void()> persist_;
    std::atomic<bool> walletOpen_{false};
    std::atomic<bool> syncStarted_{false};
    WalletBackendStatus status_;  // written only on the main thread (lifecycle ops)

    // Detached background sync (backend `sync` is a blocking, uninterruptible full scan).
    std::thread syncThread_;
    std::atomic<bool> syncLaunched_{false};  // startSync() guard (set on the main thread)
    std::shared_ptr<std::atomic<bool>> syncDone_ = std::make_shared<std::atomic<bool>>(false);

    // Detached background broadcast (send/shield): proving can take seconds. Mirrors the sync
    // thread's shared-lifetime pattern (shared running flag + result slot captured by the
    // thread, never `this`) so the thread can safely outlive the controller.
    std::thread broadcastThread_;
    std::shared_ptr<std::atomic<bool>> broadcastRunning_ =
        std::make_shared<std::atomic<bool>>(false);
    std::shared_ptr<std::mutex> broadcastResultMutex_ = std::make_shared<std::mutex>();
    std::shared_ptr<std::optional<LiteBroadcastResult>> broadcastResult_ =
        std::make_shared<std::optional<LiteBroadcastResult>>();

    // Detached interactive-console command runner (same shared-lifetime pattern as broadcast:
    // captures the shared bridge + running flag + result slot, never `this`).
    std::thread consoleThread_;
    std::shared_ptr<std::atomic<bool>> consoleRunning_ = std::make_shared<std::atomic<bool>>(false);
    std::shared_ptr<std::mutex> consoleResultMutex_ = std::make_shared<std::mutex>();
    std::shared_ptr<std::optional<LiteConsoleResult>> consoleResult_ =
        std::make_shared<std::optional<LiteConsoleResult>>();

    // Asynchronous open with server failover (mirrors the sync/broadcast shared-lifetime pattern:
    // the detached thread captures only shared_ptrs + value copies, never `this`, so it can
    // safely outlive the controller). pumpAsyncOpen() finalizes the result on the main thread.
    std::vector<std::string> failoverServerUrls() const;
    // Shared async open/create-with-failover core (create=true uses initialize_new).
    bool beginAsyncLifecycle(bool create);
    struct OpenOutcome { bool ok = false; bool warming = false; std::string serverUrl; std::string error; };
    std::thread openThread_;
    std::shared_ptr<std::atomic<bool>> openRunning_ = std::make_shared<std::atomic<bool>>(false);
    std::shared_ptr<std::mutex> openResultMutex_ = std::make_shared<std::mutex>();
    std::shared_ptr<std::optional<OpenOutcome>> openResult_ =
        std::make_shared<std::optional<OpenOutcome>>();
    std::string lastOpenError_;  // main-thread only
    bool lastOpenWarming_ = false;  // last failed open hit a warming-up (-28) server

    // Asynchronous FULL lifecycle (create/open/restore with passphrase + restore params). Same
    // shared-lifetime discipline as the open thread: the detached thread builds its OWN local
    // LiteWalletLifecycleService from captured value copies + the shared bridge (never `this`), so
    // it can safely outlive the controller. pumpLifecycleResult() finalizes on the main thread.
    bool beginLifecycleRequestAsync(
        const char* verb,
        std::function<LiteWalletLifecycleResult(LiteWalletLifecycleService&, const std::string&)> exec,
        std::function<void()> wipeSecrets);
    std::thread lifecycleThread_;
    std::shared_ptr<std::atomic<bool>> lifecycleRunning_ = std::make_shared<std::atomic<bool>>(false);
    std::shared_ptr<std::mutex> lifecycleResultMutex_ = std::make_shared<std::mutex>();
    std::shared_ptr<std::optional<LiteWalletLifecycleResult>> lifecycleResult_ =
        std::make_shared<std::optional<LiteWalletLifecycleResult>>();
    LiteWalletLifecycleResult lastLifecycleResult_;  // main-thread only; last finalized request

    // Passphrase to apply on the main thread once an async create/restore/open finalizes. The
    // backend wallet is not controller-"open" (walletOpen_) until pumpLifecycleResult() runs
    // onLifecycleResult(), and encryptWallet()/unlockWallet() gate on walletOpen_ — so the async
    // worker thread cannot do it. The passphrase is stashed here (main-thread only) and applied in
    // pumpLifecycleResult(), mirroring the synchronous createWallet()/restoreWallet()/openWallet()
    // wrappers. Without this the UI's async path silently discarded the passphrase, storing the
    // seed UNENCRYPTED (create/restore) or never unlocking an encrypted wallet (open) — audit C1/M1.
    enum class PendingSecurityOp { None, Encrypt, Unlock };
    std::string pendingLifecyclePassphrase_;                       // main-thread only; wiped after use
    PendingSecurityOp pendingLifecycleSecurityOp_ = PendingSecurityOp::None;
    void stashPendingLifecycleSecurity(const std::string& passphrase, PendingSecurityOp op);
    void applyPendingLifecycleSecurity();  // encrypt (create/restore) or unlock (open), then wipe
    void clearPendingLifecycleSecurity();  // wipe + reset (rejected request / failed open)

    // Joinable background refresh worker (fast iterations: syncstatus, plus data once synced).
    std::thread worker_;
    std::atomic<bool> running_{false};
    std::mutex wakeMutex_;
    std::condition_variable wakeCv_;
    std::mutex modelMutex_;
    std::optional<LiteWalletAppRefreshModel> pendingModel_;  // guarded by modelMutex_
    static constexpr int kRefreshIntervalMs = 2000;
};

} // namespace wallet
} // namespace dragonx
