// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#pragma once

#include <string>
#include <vector>
#include <cstdint>
#include <chrono>
#include <ctime>
#include <utility>

#include "exchange_info.h"
#include "candle.h"

namespace dragonx {

/**
 * @brief Represents an address with its balance
 */
struct AddressInfo {
    std::string address;
    double balance = 0.0;            // DISPLAY total incl. pending 0-conf change (minconf=0)
    std::string type;  // "shielded" or "transparent"
    bool has_spending_key = true;  // false for view-only (imported via z_importviewingkey)
    
    // For display
    std::string label;

    // CONFIRMED balance (minconf>=1) — what z_sendmany can actually spend now. Kept last so positional
    // brace-init of the leading fields (used in tests) still compiles.
    double spendableBalance = 0.0;

    // Derived
    bool isZAddr() const { return !address.empty() && address[0] == 'z'; }
    bool isShielded() const { return type == "shielded"; }
    bool isSpendable() const { return has_spending_key; }
};

std::vector<size_t> sortedSpendableAddressIndices(const std::vector<AddressInfo>& addresses,
                                                  bool requirePositiveBalance = true);
int bestSpendableAddressIndex(const std::vector<AddressInfo>& addresses);

/**
 * @brief Represents a wallet transaction
 */
struct TransactionInfo {
    std::string txid;
    std::string type;        // "send", "receive", "mined"
    double amount = 0.0;
    int64_t timestamp = 0;   // Unix timestamp
    int confirmations = 0;
    std::string address;     // destination (send) or source (receive)
    std::string from_address; // source address for sends
    std::string memo;
    
    // Computed fields
    std::string getTimeString() const;
    std::string getTypeDisplay() const;
    bool isConfirmed() const { return confirmations >= 1; }
    bool isMature() const { return confirmations >= 100; }
};

/**
 * @brief Represents a connected peer
 */
struct PeerInfo {
    int id = 0;
    std::string addr;
    std::string subver;
    std::string services;
    int version = 0;
    int64_t conntime = 0;
    int banscore = 0;
    double pingtime = 0.0;
    int64_t bytessent = 0;
    int64_t bytesrecv = 0;
    int startingheight = 0;
    int synced_headers = 0;
    int synced_blocks = 0;
    bool inbound = false;
    
    // TLS info
    std::string tls_cipher;
    bool tls_verified = false;
    
    std::string getConnectionTime() const;
};

/**
 * @brief Represents a banned peer
 */
struct BannedPeer {
    std::string address;
    std::string subnet;
    int64_t banned_until = 0;
    
    std::string getBannedUntilString() const;
};

/**
 * @brief Mining statistics
 */
struct MiningInfo {
    bool generate = false;
    int genproclimit = 0;       // -1 means max CPUs
    double localHashrate = 0.0;    // Local hashrate (H/s) from getlocalsolps RPC (RandomX)
    double networkHashrate = 0.0;  // Network hashrate (H/s)
    int blocks = 0;
    double difficulty = 0.0;
    std::string chain;
    
    double daemon_memory_mb = 0.0; // Daemon process RSS in MB
    
    // History for chart
    std::vector<double> hashrate_history;  // Last N samples
    static constexpr int MAX_HISTORY = 300; // 5 minutes at 1s intervals

    // Recent daemon log lines for the mining log panel
    std::vector<std::string> log_lines;
};

/**
 * @brief Blockchain synchronization info
 */
struct SyncInfo {
    int blocks = 0;
    int headers = 0;
    double verification_progress = 0.0;
    bool syncing = false;
    std::string best_blockhash;
    
    // Rescan state (detected from daemon output)
    bool rescanning = false;
    float rescan_progress = 0.0f;  // 0.0 - 1.0
    std::string rescan_status;     // e.g. "Rescanning... 25%"

    // Sapling note witness rebuild — a distinct, often-long phase after a rescan/zap. The daemon
    // reports it in TWO sub-phases with different signals, so we track which is active:
    //   1 = initial pass ("Setting Initial Sapling Witness for tx <hash>, <i> of <N>") — progress
    //       is distinct-txs-witnessed / N (the <i> bounces, so it can't be used directly).
    //   2 = witness-cache walk ("Building Witnesses for block <h> <frac> complete, <n> remaining")
    //       — progress derived from how far "remaining" has fallen from its per-phase peak.
    // The two are sequential with different scales, so progress is NOT carried across the boundary
    // (that would pin the bar at the initial pass's ~100% through the whole cache walk).
    bool building_witnesses = false;
    int witness_phase = 0;          // 0 none, 1 initial-witness pass, 2 witness-cache walk
    float witness_progress = 0.0f;  // 0.0 - 1.0, within the current sub-phase
    int witness_remaining = 0;      // blocks left in the cache walk (0 if unknown / phase 1)

    // True only when the daemon reports a positive peer-derived tip (longestchain > 0). A node with
    // no peers reports longestchain == 0, so `blocks` is then only the LOCAL best height and cannot be
    // trusted as "at the network tip". isSynced() gates on this so a peerless / cold-started node is
    // never reported synced (which would enable Send against a stale balance). (audit: peerless-synced)
    bool tip_known = false;

    bool isSynced() const { return tip_known && !syncing && blocks > 0 && blocks >= headers - 2; }
};

/**
 * @brief Market/price information
 */
struct MarketInfo {
    double price_usd = 0.0;
    double price_btc = 0.0;
    double volume_24h = 0.0;
    double change_24h = 0.0;
    double market_cap = 0.0;
    std::string last_updated;
    std::chrono::steady_clock::time_point last_fetch_time{};
    bool price_loading = false;
    std::string price_error;
    
    // Live in-session price history: ~1 sample/minute, capped at MAX_HISTORY samples.
    // Backs the main chart and the "minute" portfolio-sparkline interval.
    std::vector<double> price_history;
    static constexpr int MAX_HISTORY = 24; // 24 samples (~24 minutes at the 60s refresh)

    // Historical USD price series fetched from CoinGecko market_chart, so the portfolio-group
    // sparklines can show real hour/day/week/month trends instead of resampling the ~24-minute
    // in-session buffer. Timestamped (unix seconds), oldest->newest; empty until the first fetch.
    // Refreshed on a slow cadence (~30 min) since historical data moves slowly.
    std::vector<std::pair<std::time_t, double>> price_chart_intraday; // ~24h @ 5-minute granularity
    std::vector<std::pair<std::time_t, double>> price_chart_daily;    // ~1yr @ daily granularity
    std::chrono::steady_clock::time_point chart_last_fetch_time{};
    bool chart_loaded = false;

    // Per-EXCHANGE candle series for the SELECTED pair, fetched from that venue's own API (see
    // data/exchange_candles.h). When exchange_chart_active is true the Market chart draws these instead
    // of the CoinGecko cross-exchange aggregate above; it's set false while switching pairs or when the
    // selected venue has no adapter / its fetch failed, so the chart gracefully falls back to aggregate.
    std::vector<std::pair<std::time_t, double>> exchange_chart_intraday;
    std::vector<std::pair<std::time_t, double>> exchange_chart_daily;
    bool exchange_chart_active = false;
    // Full OHLC for the same per-exchange candles, backing the candlestick rendering (the aggregate
    // CoinGecko series is close-only, so it stays a line). Parallel to exchange_chart_* above.
    std::vector<data::Candle> exchange_ohlc_intraday;
    std::vector<data::Candle> exchange_ohlc_daily;

    // Exchanges/pairs fetched live from CoinGecko (empty until fetched; the Market tab
    // falls back to data::getExchangeRegistry() while empty).
    std::vector<data::ExchangeInfo> exchanges;
};

/**
 * @brief Pool mining state (from xmrig HTTP API)
 */
struct PoolMiningState {
    bool   pool_mode     = false;   // UI toggle: solo vs pool
    bool   xmrig_running = false;
    std::string pool_url;
    std::string algo;
    std::string version;            // running miner's version (from its API)

    double hashrate_10s  = 0;
    double hashrate_60s  = 0;
    double hashrate_15m  = 0;
    int64_t accepted     = 0;
    int64_t rejected     = 0;
    int64_t uptime_sec   = 0;
    double  pool_diff    = 0;
    bool    connected    = false;

    // Memory/thread usage (bytes for memory)
    int64_t memory_used  = 0;
    int     threads_active = 0;

    // Pool-side hashrate (from pool stats API)
    double pool_hashrate = 0;

    // Hashrate history for chart (mirrors MiningInfo::hashrate_history)
    std::vector<double> hashrate_history;
    static constexpr int MAX_HISTORY = 60; // 5 minutes at ~5s intervals

    // Recent log lines for the log panel
    std::vector<std::string> log_lines;
};

/**
 * @brief Complete wallet state - all data fetched from daemon
 */
struct WalletState {
    // Connection
    bool connected = false;
    bool warming_up = false;         // daemon reachable but in RPC warmup (error -28)
    // True when the daemon is up/launching but not yet answering RPC (e.g. the connect probe
    // times out because the node is loading the block index). Distinct from warming_up, which
    // needs a JSON-RPC -28 reply; here getinfo never returns, so we infer the state from the
    // daemon's launch state + its own console output. Drives the same loading overlay so the
    // user sees WHAT the node is doing instead of a bare "Connection failed".
    bool daemon_initializing = false;
    std::string warmup_status;       // user-friendly title, e.g. "Processing blocks..."
    std::string warmup_description;  // subtitle explaining the stage
    int daemon_version = 0;
    std::string daemon_subversion;
    int protocol_version = 0;
    int p2p_port = 0;
    int longestchain = 0;
    int notarized = 0;
    
    // Sync status
    SyncInfo sync;
    
    // Balances (named to match UI usage). These are the DISPLAY totals — minconf=0, so they include the
    // user's own pending change and don't crater during an unconfirmed send.
    double privateBalance = 0.0;     // shielded balance (display, incl. pending change)
    double transparentBalance = 0.0;
    double totalBalance = 0.0;
    double unconfirmedBalance = 0.0; // = totalBalance - spendableTotalBalance (the pending portion)
    // CONFIRMED / spendable totals (minconf>=1) — what can actually be sent right now. z_sendmany runs at
    // minconf=1, so the Send form / Max / spend validation must size off these, never the display totals.
    double spendablePrivateBalance = 0.0;
    double spendableTransparentBalance = 0.0;
    double spendableTotalBalance = 0.0;

    // Aliases for backward compatibility
    double& shielded_balance = privateBalance;
    double& transparent_balance = transparentBalance;
    double& total_balance = totalBalance;
    double& unconfirmed_balance = unconfirmedBalance;
    
    // Addresses - combined list for UI convenience
    std::vector<AddressInfo> addresses;
    
    // Also keep separate lists for legacy code
    std::vector<AddressInfo> z_addresses;
    std::vector<AddressInfo> t_addresses;
    
    // Transactions
    std::vector<TransactionInfo> transactions;
    
    // Peers
    std::vector<PeerInfo> peers;
    std::vector<BannedPeer> bannedPeers;
    
    // Aliases for banned_peers
    std::vector<BannedPeer>& banned_peers = bannedPeers;
    
    // Mining
    MiningInfo mining;
    
    // Pool mining (xmrig)
    PoolMiningState pool_mining;
    
    // Market
    MarketInfo market;
    
    // Wallet encryption state (populated from getwalletinfo)
    bool encrypted = false;         // true if wallet has ever been encrypted
    bool locked = false;            // true if encrypted && unlocked_until <= now
    int64_t unlocked_until = 0;     // 0 = locked, >0 = unix timestamp when auto-lock fires
    bool encryption_state_known = false; // true once first getwalletinfo response processed
    
    bool isEncrypted() const { return encrypted; }
    bool isLocked() const { return encrypted && locked; }
    bool isUnlocked() const { return encrypted && !locked; }
    
    // Timestamps for refresh logic
    int64_t last_balance_update = 0;
    int64_t last_address_update = 0;   // set when an address-list refresh applies; 0 = never loaded yet
    int64_t last_tx_update = 0;
    int64_t last_peer_update = 0;
    int64_t last_mining_update = 0;
    
    // Helper methods
    int getAddressCount() const { return addresses.size(); }
    double getBalanceUSD() const { return totalBalance * market.price_usd; }
    
    void clear() {
        connected = false;
        warming_up = false;
        daemon_initializing = false;
        warmup_status.clear();
        warmup_description.clear();
        daemon_version = 0;
        daemon_subversion.clear();
        protocol_version = 0;
        p2p_port = 0;
        longestchain = 0;
        notarized = 0;
        sync = SyncInfo{};
        privateBalance = transparentBalance = totalBalance = 0.0;
        unconfirmedBalance = 0.0;
        spendablePrivateBalance = spendableTransparentBalance = spendableTotalBalance = 0.0;
        encrypted = false;
        locked = false;
        unlocked_until = 0;
        encryption_state_known = false;
        addresses.clear();
        z_addresses.clear();
        t_addresses.clear();
        transactions.clear();
        peers.clear();
        bannedPeers.clear();
        // W6-1: reset node-level mining state too — the daemon restarts on a wallet switch (mining
        // stops), so leaving the previous wallet's hashrate/blocks would show stale mining stats.
        mining = MiningInfo{};
        pool_mining = PoolMiningState{};
        // After a disconnect / wallet switch nothing is freshly known, so drop the "last successful
        // refresh" stamps. Otherwise the pre-teardown time survives and, on reconnect, the staleness
        // badge (and any "updated X ago" reader) briefly reports it as current until the first refresh
        // re-stamps it. All readers treat 0 as "never" (formatTimeAgoShort/timeAgo return "").
        last_balance_update = last_address_update = last_tx_update = last_peer_update = last_mining_update = 0;
    }
    
    // Rebuild combined addresses list from z/t lists
    void rebuildAddressList() {
        addresses.clear();
        addresses.reserve(z_addresses.size() + t_addresses.size());
        for (const auto& addr : z_addresses) {
            addresses.push_back(addr);
        }
        for (const auto& addr : t_addresses) {
            addresses.push_back(addr);
        }
    }
};

} // namespace dragonx
