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

#include "connection.h"
#include "../config/version.h"
#include "../resources/embedded_resources.h"

#include <sodium.h>

#include <fstream>
#include <sstream>
#include <cstdlib>
#include <filesystem>
#include <algorithm>
#include <cctype>

#include "../util/logger.h"

#ifdef _WIN32
#include <shlobj.h>
#else
#include <pwd.h>
#include <unistd.h>
#endif

namespace fs = std::filesystem;

namespace dragonx {
namespace rpc {

namespace {

std::string generateSecureRandomString(size_t length)
{
    static constexpr char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    static constexpr uint32_t charsetSize = static_cast<uint32_t>(sizeof(charset) - 1);

    if (sodium_init() < 0) {
        DEBUG_LOGF("Failed to initialize libsodium for RPC credential generation\n");
        return {};
    }

    std::string result;
    result.reserve(length);
    for (size_t i = 0; i < length; ++i) {
        result.push_back(charset[randombytes_uniform(charsetSize)]);
    }
    return result;
}

std::string lowercase(std::string value)
{
    std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
        return static_cast<char>(std::tolower(c));
    });
    return value;
}

bool parseBoolValue(const std::string& value)
{
    std::string lowered = lowercase(value);
    return lowered == "1" || lowered == "true" || lowered == "yes" || lowered == "on";
}

bool applyCookieAuth(ConnectionConfig& config, const std::string& dataDir)
{
    std::string cookieUser, cookiePass;
    if (!Connection::readAuthCookie(dataDir, cookieUser, cookiePass)) {
        return false;
    }

    config.rpcuser = cookieUser;
    config.rpcpassword = cookiePass;
    config.auth_source = AuthSource::Cookie;
    if (config.hush_dir.empty()) config.hush_dir = dataDir;
    return true;
}

} // namespace

Connection::Connection() = default;
Connection::~Connection() = default;

std::string Connection::getDefaultDataDir()
{
#ifdef _WIN32
    char path[MAX_PATH];
    if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path))) {
        return std::string(path) + "\\Hush\\DRAGONX";
    }
    return "";
#elif defined(__APPLE__)
    const char* home = getenv("HOME");
    if (!home) {
        struct passwd* pw = getpwuid(getuid());
        home = pw->pw_dir;
    }
    // Match SilentDragonX path: Library/Application Support/Hush/DRAGONX
    return std::string(home) + "/Library/Application Support/Hush/DRAGONX";
#else
    const char* home = getenv("HOME");
    if (!home) {
        struct passwd* pw = getpwuid(getuid());
        home = pw->pw_dir;
    }
    return std::string(home) + "/.hush/DRAGONX";
#endif
}

std::string Connection::getDefaultConfPath()
{
    return getDefaultDataDir() + "/" + DRAGONX_CONF_FILENAME;
}

std::string Connection::getSaplingParamsDir()
{
    // Sapling params are now extracted alongside the daemon binaries
    // in <ObsidianDragonDir>/dragonx/ — no longer in the legacy ZcashParams dir.
    return resources::getDaemonDirectory();
}

bool Connection::verifySaplingParams()
{
    std::string params_dir = getSaplingParamsDir();
    if (params_dir.empty()) {
        DEBUG_LOGF("verifySaplingParams: params dir is empty\n");
        return false;
    }
    
#ifdef _WIN32
    std::string spend_path = params_dir + "\\sapling-spend.params";
    std::string output_path = params_dir + "\\sapling-output.params";
#else
    std::string spend_path = params_dir + "/sapling-spend.params";
    std::string output_path = params_dir + "/sapling-output.params";
#endif
    
    bool spend_exists = fs::exists(spend_path);
    bool output_exists = fs::exists(output_path);
    
    DEBUG_LOGF("verifySaplingParams: dir=%s\n", params_dir.c_str());
    DEBUG_LOGF("  spend: %s -> %s\n", spend_path.c_str(), spend_exists ? "found" : "MISSING");
    DEBUG_LOGF("  output: %s -> %s\n", output_path.c_str(), output_exists ? "found" : "MISSING");
    
    return spend_exists && output_exists;
}

ConnectionConfig Connection::parseConfFile(const std::string& path)
{
    ConnectionConfig config;
    
    std::ifstream file(path);
    if (!file.is_open()) {
        return config;
    }
    
    std::string line;
    while (std::getline(file, line)) {
        // Skip empty lines and comments
        if (line.empty() || line[0] == '#') {
            continue;
        }
        
        // Parse key=value
        size_t eq_pos = line.find('=');
        if (eq_pos == std::string::npos) {
            continue;
        }
        
        std::string key = line.substr(0, eq_pos);
        std::string value = line.substr(eq_pos + 1);
        
        // Trim whitespace (including \r from Windows line endings)
        while (!key.empty() && (key.back() == ' ' || key.back() == '\t' || key.back() == '\r')) {
            key.pop_back();
        }
        while (!value.empty() && (value[0] == ' ' || value[0] == '\t')) {
            value.erase(0, 1);
        }
        while (!value.empty() && (value.back() == ' ' || value.back() == '\t' || value.back() == '\r')) {
            value.pop_back();
        }
        
        // Map to config
        if (key == "rpcuser") {
            config.rpcuser = value;
        } else if (key == "rpcpassword") {
            config.rpcpassword = value;
        } else if (key == "rpcport") {
            config.port = value;
        } else if (key == "rpchost" || key == "rpcconnect") {
            config.host = value;
        } else if (key == "proxy") {
            config.proxy = value;
        } else if (key == "rpctls" || key == "rpcssl" || key == "use_tls" || key == "rpcuse_tls") {
            config.use_tls = parseBoolValue(value);
        }
    }

    if (!config.rpcuser.empty() || !config.rpcpassword.empty()) {
        config.auth_source = AuthSource::ConfigFile;
    }
    
    return config;
}

ConnectionConfig Connection::autoDetectConfig()
{
    ConnectionConfig config;
    
    // Ensure data directory exists
    std::string data_dir = getDefaultDataDir();
    if (!fs::exists(data_dir)) {
        DEBUG_LOGF("Creating data directory: %s\n", data_dir.c_str());
        fs::create_directories(data_dir);
    }
    
    // Try to find DRAGONX.conf
    std::string conf_path = getDefaultConfPath();
    
    if (fs::exists(conf_path)) {
        // Ensure exportdir is present in existing config
        ensureExportDir(conf_path);
        // Ensure encryption flags are present
        ensureEncryptionEnabled(conf_path);
        config = parseConfFile(conf_path);
        config.hush_dir = data_dir;
    } else {
        // Create a default config file
        if (createDefaultConfig(conf_path)) {
            config = parseConfFile(conf_path);
            config.hush_dir = data_dir;
        }
    }
    
    // If rpcpassword is empty, the daemon may be using .cookie auth
    if (config.rpcpassword.empty()) {
        if (applyCookieAuth(config, data_dir)) {
            DEBUG_LOGF("Using .cookie authentication (no rpcpassword in config)\n");
        }
    }
    
    // Set defaults for missing values
    if (config.host.empty()) {
        config.host = DRAGONX_DEFAULT_RPC_HOST;
    }
    if (config.port.empty()) {
        config.port = DRAGONX_DEFAULT_RPC_PORT;
    }
    
    return config;
}

bool Connection::buildCookieAuthConfig(const ConnectionConfig& base, ConnectionConfig& cookieConfig)
{
    if (base.auth_source == AuthSource::Cookie) {
        return false;
    }

    std::string dataDir = base.hush_dir.empty() ? getDefaultDataDir() : base.hush_dir;
    ConnectionConfig fallback = base;
    if (!applyCookieAuth(fallback, dataDir)) {
        return false;
    }

    cookieConfig = std::move(fallback);
    return true;
}

bool Connection::isLocalHost(const std::string& host)
{
    std::string lowered = lowercase(host);
    if (!lowered.empty() && lowered.front() == '[' && lowered.back() == ']') {
        lowered = lowered.substr(1, lowered.size() - 2);
    }

    return lowered == "localhost" || lowered == "localhost." ||
           lowered == "::1" || lowered == "0:0:0:0:0:0:0:1" ||
           lowered == "127.0.0.1" || lowered.rfind("127.", 0) == 0;
}

bool Connection::usesPlaintextRemote(const ConnectionConfig& config)
{
    return !config.use_tls && !isLocalHost(config.host);
}

const char* Connection::authSourceName(AuthSource source)
{
    switch (source) {
        case AuthSource::ConfigFile: return "config";
        case AuthSource::Cookie:     return "cookie";
        case AuthSource::Missing:    return "missing";
    }
    return "unknown";
}

bool Connection::createDefaultConfig(const std::string& path)
{
    std::string rpcuser = generateSecureRandomString(16);
    std::string rpcpassword = generateSecureRandomString(32);
    if (rpcuser.empty() || rpcpassword.empty()) {
        DEBUG_LOGF("Failed to generate secure RPC credentials for config file: %s\n", path.c_str());
        return false;
    }
    
    std::ofstream file(path);
    if (!file.is_open()) {
        DEBUG_LOGF("Failed to create config file: %s\n", path.c_str());
        return false;
    }
    
    // Get the data directory for exportdir
    std::string dataDir = getDefaultDataDir();
    
    file << "# DragonX configuration file\n";
    file << "# Auto-generated by DragonX Wallet\n";
    file << "\n";
    file << "rpcuser=" << rpcuser << "\n";
    file << "rpcpassword=" << rpcpassword << "\n";
    file << "rpcport=" << DRAGONX_DEFAULT_RPC_PORT << "\n";
    file << "server=1\n";
    file << "txindex=1\n";
    file << "exportdir=" << dataDir << "\n";
    file << "experimentalfeatures=1\n";
    file << "developerencryptwallet=1\n";
    file << "addnode=node.dragonx.is\n";
    file << "addnode=node1.dragonx.is\n";
    file << "addnode=node2.dragonx.is\n";
    file << "addnode=node3.dragonx.is\n";
    file << "addnode=node4.dragonx.is\n";
    
    file.close();
    
    DEBUG_LOGF("Created default config file: %s\n", path.c_str());
    return true;
}

bool Connection::ensureExportDir(const std::string& confPath)
{
    // Read existing config and check if exportdir is present
    std::ifstream inFile(confPath);
    if (!inFile.is_open()) {
        return false;
    }
    
    std::string contents;
    std::string line;
    bool hasExportDir = false;
    
    while (std::getline(inFile, line)) {
        contents += line + "\n";
        // Check for exportdir (case insensitive check for key)
        if (line.find("exportdir=") == 0 || line.find("exportdir =") == 0) {
            hasExportDir = true;
        }
    }
    inFile.close();
    
    if (hasExportDir) {
        return true; // Already has exportdir
    }
    
    // Append exportdir to config
    std::ofstream outFile(confPath, std::ios::app);
    if (!outFile.is_open()) {
        DEBUG_LOGF("Failed to open config for appending exportdir: %s\n", confPath.c_str());
        return false;
    }
    
    std::string dataDir = getDefaultDataDir();
    outFile << "\n# Export directory for wallet backups (required for z_exportwallet)\n";
    outFile << "exportdir=" << dataDir << "\n";
    outFile.close();
    
    DEBUG_LOGF("Added exportdir to config: %s\n", confPath.c_str());
    return true;
}

bool Connection::ensureEncryptionEnabled(const std::string& confPath)
{
    // Read existing config and check if encryption flags are present
    std::ifstream inFile(confPath);
    if (!inFile.is_open()) {
        return false;
    }
    
    std::string contents;
    std::string line;
    bool hasExperimental = false;
    bool hasEncrypt = false;
    
    while (std::getline(inFile, line)) {
        contents += line + "\n";
        if (line.find("experimentalfeatures=") == 0 || line.find("experimentalfeatures =") == 0) {
            hasExperimental = true;
        }
        if (line.find("developerencryptwallet=") == 0 || line.find("developerencryptwallet =") == 0) {
            hasEncrypt = true;
        }
    }
    inFile.close();
    
    if (hasExperimental && hasEncrypt) {
        return true; // Already has both flags
    }
    
    // Append missing flags to config
    std::ofstream outFile(confPath, std::ios::app);
    if (!outFile.is_open()) {
        DEBUG_LOGF("Failed to open config for appending encryption flags: %s\n", confPath.c_str());
        return false;
    }
    
    outFile << "\n# Enable wallet encryption support\n";
    if (!hasExperimental) {
        outFile << "experimentalfeatures=1\n";
    }
    if (!hasEncrypt) {
        outFile << "developerencryptwallet=1\n";
    }
    outFile.close();
    
    DEBUG_LOGF("Added encryption flags to config: %s\n", confPath.c_str());
    return true;
}

bool Connection::readAuthCookie(const std::string& dataDir, std::string& user, std::string& password)
{
    if (dataDir.empty()) return false;
    
#ifdef _WIN32
    std::string cookiePath = dataDir + "\\.cookie";
#else
    std::string cookiePath = dataDir + "/.cookie";
#endif
    
    std::ifstream file(cookiePath);
    if (!file.is_open()) return false;
    
    std::string cookie;
    std::getline(file, cookie);
    file.close();
    
    // Cookie format: __cookie__:base64encodedpassword
    size_t colonPos = cookie.find(':');
    if (colonPos == std::string::npos || colonPos == 0) return false;
    
    user = cookie.substr(0, colonPos);
    password = cookie.substr(colonPos + 1);
    
    // Trim \r if present (Windows line endings)
    while (!password.empty() && (password.back() == '\r' || password.back() == '\n')) {
        password.pop_back();
    }
    
    if (user.empty() || password.empty()) return false;
    
    DEBUG_LOGF("Read auth cookie from: %s (user=%s)\n", cookiePath.c_str(), user.c_str());
    return true;
}

} // namespace rpc
} // namespace dragonx
