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

#include "console_command_executor.h"
#include "console_channel.h"
#include "console_command_reference.h"  // consoleCommandCategories / liteConsoleCommandCategories
#include "console_input_model.h"  // BuildConsoleRpcCall

#include "../../app.h"
#include "../../data/wallet_state.h"
#include "../../rpc/rpc_client.h"
#include "../../rpc/rpc_worker.h"
#include "../../daemon/embedded_daemon.h"
#include "../../daemon/xmrig_manager.h"
#include "../../wallet/lite_wallet_controller.h"
#include "../../wallet/lite_diagnostics.h"
#include "../../util/i18n.h"
#include "../material/colors.h"

#include <nlohmann/json.hpp>

#include <algorithm>
#include <cstdio>
#include <sstream>

namespace dragonx {
namespace ui {

using namespace material;

// Classifies a diagnostics line into a console channel (defined below in an anonymous namespace); both
// executors drain the same LiteDiagnostics ring, so declare it up front for the full-node drain.
namespace { ConsoleChannel liteLogChannel(const std::string& line); }

// ============================================================================
// FullNodeConsoleExecutor
// ============================================================================

bool FullNodeConsoleExecutor::isReady() const
{
    rpc::RPCClient* rpc = app_->consoleRpc();
    return rpc && rpc->isConnected();
}

void FullNodeConsoleExecutor::submit(const std::string& cmd)
{
    rpc::RPCClient* rpc = app_->consoleRpc();
    if (!rpc || !rpc->isConnected()) {
        std::lock_guard<std::mutex> lk(results_mutex_);
        results_.push_back({std::string(TR("console_not_connected")), true});
        return;
    }

    auto call = BuildConsoleRpcCall(cmd);
    if (!call.valid) {
        if (!call.error.empty()) {
            std::lock_guard<std::mutex> lk(results_mutex_);
            results_.push_back({call.error, true});
        }
        return;
    }
    const std::string method = call.method;
    const nlohmann::json params = call.params;

    rpc::RPCWorker* worker = app_->consoleWorker();
    if (worker) {
        in_flight_.fetch_add(1);   // gates busy() so the input is disabled until this returns
        worker->post([rpc, method, params, this]() -> rpc::RPCWorker::MainCb {
            std::string result_str;
            bool is_error = false;
            try {
                rpc::RPCClient::TraceScope trace("Console tab / User command");
                result_str = rpc->callRaw(method, params);
            } catch (const std::exception& e) {
                result_str = e.what();
                is_error = true;
            }
            return [this, result_str, is_error]() {
                in_flight_.fetch_sub(1);
                std::lock_guard<std::mutex> lk(results_mutex_);
                results_.push_back({result_str, is_error});
            };
        });
    } else {
        std::string result_str;
        bool is_error = false;
        try {
            rpc::RPCClient::TraceScope trace("Console tab / User command");
            result_str = rpc->callRaw(method, params);
        } catch (const std::exception& e) {
            result_str = e.what();
            is_error = true;
        }
        std::lock_guard<std::mutex> lk(results_mutex_);
        results_.push_back({result_str, is_error});
    }
}

bool FullNodeConsoleExecutor::pollResult(std::string& result, bool& isError)
{
    std::lock_guard<std::mutex> lk(results_mutex_);
    if (results_.empty()) return false;
    result  = std::move(results_.front().first);
    isError = results_.front().second;
    results_.pop_front();
    return true;
}

void FullNodeConsoleExecutor::pollLogLines(const ConsoleAddLineFn& add)
{
    using DState = daemon::EmbeddedDaemon::State;

    daemon::EmbeddedDaemon* d = app_->consoleDaemon();
    if (d) {
        const DState cur = d->getState();
        const int prev = last_daemon_state_;
        if (cur == DState::Starting && prev == static_cast<int>(DState::Stopped)) {
            add("", ConsoleChannel::None);
            add(TR("console_starting_node"), ConsoleChannel::Info);
            add(TR("console_capturing_output"), ConsoleChannel::Info);
            add("", ConsoleChannel::None);
        } else if (cur == DState::Running && prev != static_cast<int>(DState::Running)) {
            add(TR("console_daemon_started"), ConsoleChannel::Info);
        } else if (cur == DState::Stopped && prev == static_cast<int>(DState::Running)) {
            add("", ConsoleChannel::None);
            add(TR("console_daemon_stopped"), ConsoleChannel::Info);
        } else if (cur == DState::Error) {
            add(std::string(TR("console_daemon_error")) + d->getLastError() + " ===", ConsoleChannel::Error);
        }
        last_daemon_state_ = static_cast<int>(cur);
    }

    rpc::RPCClient* rpc = app_->consoleRpc();
    if (rpc) {
        const bool now = rpc->isConnected();
        if (now && !last_rpc_connected_)       add(TR("console_connected"), ConsoleChannel::Info);
        else if (!now && last_rpc_connected_)  add(TR("console_disconnected"), ConsoleChannel::Error);
        last_rpc_connected_ = now;
    }

    if (d) {
        std::string out = d->getOutputSince(last_daemon_output_size_);
        if (!out.empty()) {
            std::istringstream ss(out);
            std::string line;
            while (std::getline(ss, line)) {
                if (line.empty()) continue;
                ConsoleChannel c = ConsoleChannel::Daemon;
                if (line.find("[ERROR]") != std::string::npos ||
                    line.find("error:") != std::string::npos ||
                    line.find("Error:") != std::string::npos)
                    c = ConsoleChannel::Error;
                add(line, c);  // channel conveys the source; no "[daemon] " prefix
            }
        }
    }

    daemon::XmrigManager* x = app_->consoleXmrig();
    if (x && x->isRunning()) {
        std::string out = x->getOutputSince(last_xmrig_output_size_);
        if (!out.empty()) {
            std::istringstream ss(out);
            std::string line;
            while (std::getline(ss, line)) {
                if (line.empty()) continue;
                ConsoleChannel c = ConsoleChannel::Xmrig;
                if (line.find("error") != std::string::npos ||
                    line.find("ERROR") != std::string::npos ||
                    line.find("failed") != std::string::npos)
                    c = ConsoleChannel::Error;
                else if (line.find("accepted") != std::string::npos)
                    c = ConsoleChannel::Success;
                add(line, c);  // channel conveys the source; no "[xmrig] " prefix
            }
        }
    } else {
        last_xmrig_output_size_ = 0;  // reset so we get fresh output when it restarts
    }

    // Chat diagnostics ring: chat code logs via wallet::liteLog on both variants (send lifecycle, the
    // 0-conf harvest, the note buffer). Surface those lines here as App messages so the full-node console
    // shows chat activity too. Same generation-cursor drain the lite console uses.
    {
        auto& diag = wallet::LiteDiagnostics::instance();
        const std::uint64_t gen = diag.generation();
        if (gen != diag_gen_) {
            const auto snap = diag.snapshot();
            std::size_t startIdx = 0;
            if (diag_gen_ != static_cast<std::uint64_t>(-1)) {
                const std::uint64_t added = gen - diag_gen_;
                startIdx = (added >= snap.size()) ? 0 : snap.size() - static_cast<std::size_t>(added);
            }
            for (std::size_t i = startIdx; i < snap.size(); ++i)
                add(snap[i], liteLogChannel(snap[i]));
            diag_gen_ = gen;
        }
    }
}

void FullNodeConsoleExecutor::printHelp(const ConsoleAddLineFn& add)
{
    add(TR("console_available_commands"), ConsoleChannel::Info);
    add(TR("console_help_clear"), ConsoleChannel::None);
    add(TR("console_help_help"), ConsoleChannel::None);
    add("", ConsoleChannel::None);
    add(TR("console_common_rpc"), ConsoleChannel::Info);
    add(TR("console_help_getinfo"), ConsoleChannel::None);
    add(TR("console_help_getbalance"), ConsoleChannel::None);
    add(TR("console_help_gettotalbalance"), ConsoleChannel::None);
    add(TR("console_help_getblockcount"), ConsoleChannel::None);
    add(TR("console_help_getpeerinfo"), ConsoleChannel::None);
    add(TR("console_help_setgenerate"), ConsoleChannel::None);
    add(TR("console_help_getmininginfo"), ConsoleChannel::None);
    add(TR("console_help_stop"), ConsoleChannel::None);
    add("", ConsoleChannel::None);
    add(TR("console_click_commands"), ConsoleChannel::Info);
    add(TR("console_tab_completion"), ConsoleChannel::Info);
}

const std::vector<ConsoleCommandCategory>* FullNodeConsoleExecutor::commandReference() const
{
    return &consoleCommandCategories();
}

ConsoleStatusLine FullNodeConsoleExecutor::toolbarStatus() const
{
    ConsoleStatusLine s;
    daemon::EmbeddedDaemon* d = app_->consoleDaemon();
    if (!d) {
        // No wallet-managed daemon, but a daemon started externally (before the wallet) is still
        // reachable over RPC and accepts commands — report it as running rather than "no daemon".
        if (app_->isConnected()) { s.text = TR("console_status_running"); s.color = Success(); }
        return s;  // otherwise empty text -> toolbar shows the generic "no daemon" label
    }
    using DState = daemon::EmbeddedDaemon::State;
    switch (d->getState()) {
        case DState::Stopped:  s.text = TR("console_status_stopped");  s.color = IM_COL32(150,150,150,255); break;
        case DState::Starting: s.text = TR("console_status_starting"); s.color = Warning(); s.pulse = true; break;
        case DState::Running:  s.text = TR("console_status_running");  s.color = Success(); break;
        case DState::Stopping: s.text = TR("console_status_stopping"); s.color = Warning(); s.pulse = true; break;
        case DState::Error:    s.text = TR("console_status_error");    s.color = Error(); break;
    }
    return s;
}

// ============================================================================
// LiteConsoleExecutor
// ============================================================================

namespace {
// Classify a lite diagnostics line into a channel for at-a-glance scanning.
ConsoleChannel liteLogChannel(const std::string& line)
{
    const auto has = [&line](const char* s) { return line.find(s) != std::string::npos; };
    if (has("failed") || has(" unreachable") || has("blocked") || has("could not") ||
        has("Error") || has("error"))
        return ConsoleChannel::Error;
    if (has(": connected") || has("opened") || has("wallet ready") || has("Ready"))
        return ConsoleChannel::Success;
    return ConsoleChannel::App;  // lite diagnostics / wallet log
}
} // namespace

bool LiteConsoleExecutor::isReady() const
{
    wallet::LiteWalletController* lw = app_->liteWallet();
    return lw && lw->walletOpen();
}

bool LiteConsoleExecutor::busy() const
{
    wallet::LiteWalletController* lw = app_->liteWallet();
    return lw && lw->consoleCommandInProgress();
}

void LiteConsoleExecutor::submit(const std::string& cmd)
{
    wallet::LiteWalletController* lw = app_->liteWallet();
    if (!lw) return;
    lw->runConsoleCommand(cmd);  // rejection (busy) surfaces via the busy() indicator
}

bool LiteConsoleExecutor::pollResult(std::string& result, bool& isError)
{
    wallet::LiteWalletController* lw = app_->liteWallet();
    if (!lw) return false;
    wallet::LiteConsoleResult res;
    if (!lw->takeConsoleResult(res)) return false;
    result  = res.response.empty() ? std::string(TR("console_no_output")) : res.response;
    isError = !res.ok;
    return true;
}

void LiteConsoleExecutor::pollLogLines(const ConsoleAddLineFn& add)
{
    auto& diag = wallet::LiteDiagnostics::instance();
    const std::uint64_t gen = diag.generation();
    if (gen == diag_gen_) return;

    const auto snap = diag.snapshot();
    std::size_t startIdx = 0;
    if (diag_gen_ != static_cast<std::uint64_t>(-1)) {
        const std::uint64_t added = gen - diag_gen_;
        startIdx = (added >= snap.size()) ? 0 : snap.size() - static_cast<std::size_t>(added);
    }
    for (std::size_t i = startIdx; i < snap.size(); ++i)
        add(snap[i], liteLogChannel(snap[i]));
    diag_gen_ = gen;
}

void LiteConsoleExecutor::printHelp(const ConsoleAddLineFn& add)
{
    add(TR("console_available_commands"), ConsoleChannel::Info);
    add(TR("console_help_clear"), ConsoleChannel::None);
    add(TR("console_help_help"), ConsoleChannel::None);
    add(TR("lite_console_help_passthrough"), ConsoleChannel::Info);
    // The lite backend's own command verbs (literal command tokens, not RPC methods — mirrors the
    // backend's get_commands() registry). Listed for discoverability: the C++ tab intercepts `help`
    // before it can reach the backend's own HelpCommand, so its "type help" advice would dead-end.
    add(TR("lite_console_backend_commands"), ConsoleChannel::Info);
    add("  sync  syncstatus  balance  addresses  height  info  list  notes  encryptionstatus", ConsoleChannel::None);
    add("  send  shield  new  seed  import  timport  export", ConsoleChannel::None);
    add("  encrypt  decrypt  lock  unlock  rescan  save  sietch  saplingtree  coinsupply", ConsoleChannel::None);
    add(TR("console_click_commands"), ConsoleChannel::Info);
}

const std::vector<ConsoleCommandCategory>* LiteConsoleExecutor::commandReference() const
{
    return &liteConsoleCommandCategories();
}

std::vector<ConsoleStatusLine> LiteConsoleExecutor::statusLines() const
{
    std::vector<ConsoleStatusLine> out;
    wallet::LiteWalletController* lw = app_->liteWallet();
    if (lw && lw->walletOpen()) {
        const SyncInfo& sync = app_->state().sync;
        char buf[128];
        if (sync.syncing && !sync.isSynced()) {
            double vp = sync.verification_progress;
            if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0;
            std::snprintf(buf, sizeof(buf), "%s %.1f%%  (block %d / %d)",
                          TR("lite_net_syncing"), vp * 100.0, sync.blocks, sync.headers);
        } else {
            std::snprintf(buf, sizeof(buf), "%s  (block %d)", TR("lite_net_synced"), sync.blocks);
        }
        out.push_back({std::string(buf), OnSurfaceMedium(), false});
    }
    const std::string& err = app_->liteOpenError();
    if (!err.empty() && (!lw || !lw->walletOpen()))
        out.push_back({std::string(TR("console_last_error")) + " " + err, Error(), false});
    return out;
}

ConsoleStatusLine LiteConsoleExecutor::toolbarStatus() const
{
    ConsoleStatusLine s;
    wallet::LiteWalletController* lw = app_->liteWallet();
    if (!lw) { s.text = TR("console_backend_unavailable"); s.color = Error(); return s; }
    if (lw->walletOpen()) { s.text = TR("lite_net_connected"); s.color = Success(); }
    else if (lw->openInProgress()) { s.text = TR("lite_net_connecting"); s.color = Warning(); s.pulse = true; }
    else { s.text = TR("lite_net_disconnected"); s.color = Error(); }
    return s;
}

} // namespace ui
} // namespace dragonx
