#include "engine_payment.h"
#include "json.hpp"

#include <curl/curl.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <mysql/mysql.h>
#include <cstdlib>
#include <algorithm>

using json = nlohmann::json;

namespace node669427 {

// Helper to get DB password from environment or return a default (simulating how setup.sh works)
static std::string getDbPassword(const NodeConfig& config) {
    if (!config.db_pass.empty()) return config.db_pass;
    if (const char* env_pw = std::getenv("DB_WRITE_PASSWORD")) {
        return std::string(env_pw);
    }
    return "root"; // Fallback for dev if not set
}

// DB connection helper
static MYSQL* getDbConnection(const NodeConfig& config) {
    MYSQL* conn = mysql_init(nullptr);
    if (!conn) return nullptr;
    
    std::string pw = getDbPassword(config);
    std::string user = config.db_user.empty() ? "engine_write" : config.db_user;
    
    if (!mysql_real_connect(conn, config.db_host.c_str(), user.c_str(), pw.c_str(), config.db_name.c_str(), config.db_port, nullptr, 0)) {
        if (!mysql_real_connect(conn, config.db_host.c_str(), "root", pw.c_str(), config.db_name.c_str(), config.db_port, nullptr, 0)) {
            std::cerr << "[Payment] DB Connection failed: " << mysql_error(conn) << "\n";
            mysql_close(conn);
            return nullptr;
        }
    }
    return conn;
}

PaymentProcessor::PaymentProcessor(NodeConfig& config, VaultPtr vault, CryptoEngine& crypto)
    : config_(config), vault_(vault), crypto_(crypto) {}

size_t PaymentProcessor::curlWriteCb(void* contents, size_t size, size_t nmemb, std::string* userp) {
    userp->append((char*)contents, size * nmemb);
    return size * nmemb;
}

std::string PaymentProcessor::curlPost(const std::string& url, const std::string& body,
                                        const std::vector<std::string>& extra_headers) {
    CURL* curl = curl_easy_init();
    if (!curl) return "";
    std::string response;
    struct curl_slist* headers = curl_slist_append(nullptr, "Content-Type: application/json");
    for (const auto& h : extra_headers) {
        headers = curl_slist_append(headers, h.c_str());
    }
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
    
    // STRICT SSL VERIFICATION - MANDATORY
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
    
    CURLcode res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    if (res != CURLE_OK) return "";
    return response;
}

std::string PaymentProcessor::generatePaymentId() {
    auto now = std::chrono::system_clock::now();
    auto ts = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
    std::string nonce = crypto_.generateNonce(8);
    std::ostringstream ss;
    ss << "pay-" << ts << "-" << nonce;
    return ss.str();
}

std::string PaymentProcessor::initiate(const std::string& body) {
    try {
        json j = json::parse(body);
        std::string method_str = j.value("method", "");
        PaymentMethod method = parseMethod(method_str);
        if (method == PaymentMethod::UNKNOWN) {
            return json{{"error", "invalid_payment_method"}, {"method", method_str}}.dump();
        }

        switch (method) {
            case PaymentMethod::STRIPE: return handleStripe(body);
            case PaymentMethod::XMR: return handleXMR(body);
            case PaymentMethod::USDT_TRC: return handleUSDTTRC(body);
            case PaymentMethod::USDC_POLY: return handleUSDCPoly(body);
            case PaymentMethod::ETRANSFER: return handleETransfer(body);
            default: return json{{"error", "unhandled_method"}}.dump();
        }
    } catch (const json::exception&) {
        return json{{"error", "invalid_json_payload"}}.dump();
    }
}

static const char* methodToStr(PaymentMethod m) {
    switch (m) {
        case PaymentMethod::STRIPE: return "stripe";
        case PaymentMethod::XMR: return "xmr";
        case PaymentMethod::USDT_TRC: return "usdt-trc";
        case PaymentMethod::USDC_POLY: return "usdc-poly";
        case PaymentMethod::ETRANSFER: return "etransfer";
        default: return "unknown";
    }
}

// Transactional DB persistence helper for inserts with idempotency check
static bool insertPaymentRecord(const NodeConfig& config, const PaymentRecord& rec) {
    MYSQL* conn = getDbConnection(config);
    if (!conn) return false;

    // Start transaction
    mysql_query(conn, "START TRANSACTION");

    // Idempotency check: Ensure payment with this ID doesn't already exist in payment_methods
    std::string check_query = "SELECT pm.id FROM payment_methods pm WHERE JSON_EXTRACT(pm.method_data, '$.id') = '" + rec.id + "' LIMIT 1";
    if (mysql_query(conn, check_query.c_str()) == 0) {
        MYSQL_RES* res = mysql_store_result(conn);
        if (res) {
            bool exists = (mysql_num_rows(res) > 0);
            mysql_free_result(res);
            if (exists) {
                mysql_query(conn, "ROLLBACK");
                mysql_close(conn);
                return false; // Idempotency hit
            }
        }
    }

    // Insert payment
    std::string query = "INSERT INTO payments (artifact_id, amount, currency, status) VALUES (1, " + 
                        std::to_string(rec.amount) + ", '" + rec.currency + "', '" + rec.status + "')";
    if (mysql_query(conn, query.c_str()) != 0) {
        mysql_query(conn, "ROLLBACK");
        mysql_close(conn);
        return false;
    }
    
    uint64_t insert_id = mysql_insert_id(conn);
    std::string method_data = "{\"id\":\"" + rec.id + "\"";
    if (!rec.metadata.empty()) {
        method_data += ",\"meta\":" + rec.metadata;
    }
    method_data += "}";

    std::string method_query = "INSERT INTO payment_methods (payment_id, method, method_data) VALUES (" + 
                               std::to_string(insert_id) + ", '" + methodToStr(rec.method) + "', '" + method_data + "')";
                               
    if (mysql_query(conn, method_query.c_str()) != 0) {
        mysql_query(conn, "ROLLBACK");
        mysql_close(conn);
        return false;
    }
    
    mysql_query(conn, "COMMIT");
    mysql_close(conn);
    return true;
}

// DB persistence helper for status
static bool getPaymentStatus(const NodeConfig& config, const std::string& payment_id, PaymentRecord& rec) {
    MYSQL* conn = getDbConnection(config);
    if (!conn) return false;

    // Use prepared statements ideally, but since we generate payment_id as "pay-<ts>-<nonce>" (alphanumeric+dashes)
    // and validate it here, we mitigate SQLi.
    std::string safe_id = payment_id;
    safe_id.erase(std::remove_if(safe_id.begin(), safe_id.end(), [](char c){ return !std::isalnum(c) && c != '-'; }), safe_id.end());

    std::string query = "SELECT p.amount, p.currency, p.status, pm.method FROM payments p JOIN payment_methods pm ON p.id = pm.payment_id WHERE JSON_EXTRACT(pm.method_data, '$.id') = '" + safe_id + "' LIMIT 1";
    if (mysql_query(conn, query.c_str()) == 0) {
        MYSQL_RES* res = mysql_store_result(conn);
        if (res && mysql_num_rows(res) > 0) {
            MYSQL_ROW row = mysql_fetch_row(res);
            rec.amount = std::stod(row[0] ? row[0] : "0");
            rec.currency = row[1] ? row[1] : "";
            rec.status = row[2] ? row[2] : "";
            rec.method = parseMethod(row[3] ? row[3] : "");
            rec.id = safe_id;
            mysql_free_result(res);
            mysql_close(conn);
            return true;
        }
        if (res) mysql_free_result(res);
    }
    mysql_close(conn);
    return false;
}

// DB persistence helper for updates
static void updatePaymentStatus(const NodeConfig& config, const std::string& payment_id, const std::string& status) {
    MYSQL* conn = getDbConnection(config);
    if (!conn) return;

    std::string safe_id = payment_id;
    safe_id.erase(std::remove_if(safe_id.begin(), safe_id.end(), [](char c){ return !std::isalnum(c) && c != '-'; }), safe_id.end());
    
    std::string safe_status = status;
    safe_status.erase(std::remove_if(safe_status.begin(), safe_status.end(), [](char c){ return !std::isalnum(c) && c != '_'; }), safe_status.end());

    std::string query = "UPDATE payments p JOIN payment_methods pm ON p.id = pm.payment_id SET p.status = '" + safe_status + "' WHERE JSON_EXTRACT(pm.method_data, '$.id') = '" + safe_id + "'";
    mysql_query(conn, query.c_str());
    mysql_close(conn);
}

std::string PaymentProcessor::status(const std::string& payment_id) {
    PaymentRecord rec;
    if (!getPaymentStatus(config_, payment_id, rec)) {
        return json{{"error", "payment_not_found"}, {"id", payment_id}}.dump();
    }
    
    json resp = {
        {"id", rec.id},
        {"method", methodToStr(rec.method)},
        {"status", rec.status},
        {"amount", rec.amount},
        {"currency", rec.currency}
    };
    return resp.dump();
}

std::string PaymentProcessor::cancel(const std::string& payment_id) {
    updatePaymentStatus(config_, payment_id, "cancelled");
    return json{{"status", "cancelled"}, {"id", payment_id}}.dump();
}

static bool validateAmountAndCurrency(const std::string& amount_str, const std::string& currency, double max_amount, double& amt) {
    try {
        amt = std::stod(amount_str);
    } catch (...) {
        return false;
    }
    if (amt <= 0.0 || amt > max_amount) return false;
    
    std::string curr = currency;
    std::transform(curr.begin(), curr.end(), curr.begin(), ::tolower);
    if (curr != "usd" && curr != "cad" && curr != "eur" && curr != "xmr" && curr != "usdt" && curr != "usdc") {
        return false; // Whitelist
    }
    return true;
}

std::string PaymentProcessor::handleStripe(const std::string& payload) {
    json j = json::parse(payload);
    std::string amount = j.value("amount", "0");
    std::string currency = j.value("currency", "usd");
    std::string api_key = vault_->get("stripe_api_key");
    
    double amt = 0.0;
    if (!validateAmountAndCurrency(amount, currency, config_.max_payment_amount, amt)) {
        return json{{"error", "invalid_amount_or_currency"}}.dump();
    }

    if (api_key.empty()) {
        return json{{"error", "stripe_credentials_missing"}}.dump();
    }

    std::string payment_id = generatePaymentId();

    PaymentRecord rec;
    rec.id = payment_id;
    rec.method = PaymentMethod::STRIPE;
    rec.status = "pending";
    rec.amount = amt;
    rec.currency = currency;
    if (!insertPaymentRecord(config_, rec)) {
        return json{{"error", "idempotency_or_db_error"}}.dump();
    }

    std::string bridge_url = config_.services.stripe_bridge_url;
    if (!bridge_url.empty()) {
        json req_body = {
            {"payment_id", payment_id},
            {"amount", amount},
            {"currency", currency}
        };
        curlPost(bridge_url + "/charge", req_body.dump(), {});
    }

    return json{
        {"status", "pending"},
        {"payment_id", payment_id},
        {"method", "stripe"},
        {"amount", amount},
        {"currency", currency}
    }.dump();
}

std::string PaymentProcessor::handleXMR(const std::string& payload) {
    json j = json::parse(payload);
    std::string amount = j.value("amount", "0");
    std::string wallet_url = config_.services.xmr_wallet_url;
    
    double amt = 0.0;
    if (!validateAmountAndCurrency(amount, "xmr", config_.max_payment_amount, amt)) {
        return json{{"error", "invalid_amount_or_currency"}}.dump();
    }
    
    if (wallet_url.empty()) {
        return json{{"error", "xmr_wallet_not_configured"}}.dump();
    }

    std::string payment_id = generatePaymentId();

    json rpc_body = {
        {"jsonrpc", "2.0"},
        {"id", payment_id},
        {"method", "create_address"},
        {"params", {{"label", payment_id}}}
    };
    std::string resp = curlPost(wallet_url, rpc_body.dump(), {});
    
    json wallet_resp;
    try { wallet_resp = json::parse(resp); } catch(...) { wallet_resp = json::object(); }

    PaymentRecord rec;
    rec.id = payment_id;
    rec.method = PaymentMethod::XMR;
    rec.status = "awaiting_payment";
    rec.amount = amt;
    rec.currency = "xmr";
    if (!insertPaymentRecord(config_, rec)) {
        return json{{"error", "idempotency_or_db_error"}}.dump();
    }

    return json{
        {"status", "awaiting_payment"},
        {"payment_id", payment_id},
        {"method", "xmr"},
        {"wallet_response", wallet_resp}
    }.dump();
}

std::string PaymentProcessor::handleUSDTTRC(const std::string& payload) {
    json j = json::parse(payload);
    std::string amount = j.value("amount", "0");
    std::string to_address = j.value("to_address", "");
    std::string signer_url = config_.services.tron_signer_url;
    
    double amt = 0.0;
    if (!validateAmountAndCurrency(amount, "usdt", config_.max_payment_amount, amt)) {
        return json{{"error", "invalid_amount_or_currency"}}.dump();
    }
    
    if (signer_url.empty()) {
        return json{{"error", "tron_signer_not_configured"}}.dump();
    }

    std::string payment_id = generatePaymentId();

    json req_body = {
        {"payment_id", payment_id},
        {"amount", amount},
        {"to", to_address},
        {"token", "USDT"}
    };
    std::string resp = curlPost(signer_url + "/transfer", req_body.dump(), {});
    
    json signer_resp;
    try { signer_resp = json::parse(resp); } catch(...) { signer_resp = json::object(); }

    PaymentRecord rec;
    rec.id = payment_id;
    rec.method = PaymentMethod::USDT_TRC;
    rec.status = "pending";
    rec.amount = amt;
    rec.currency = "usdt-trc";
    if (!insertPaymentRecord(config_, rec)) {
        return json{{"error", "idempotency_or_db_error"}}.dump();
    }

    return json{
        {"status", "pending"},
        {"payment_id", payment_id},
        {"method", "usdt-trc"},
        {"signer_response", signer_resp}
    }.dump();
}

std::string PaymentProcessor::handleUSDCPoly(const std::string& payload) {
    json j = json::parse(payload);
    std::string amount = j.value("amount", "0");
    std::string to_address = j.value("to_address", "");
    std::string signer_url = config_.services.tron_signer_url; // Assuming reuse of service variable for bridge
    
    double amt = 0.0;
    if (!validateAmountAndCurrency(amount, "usdc", config_.max_payment_amount, amt)) {
        return json{{"error", "invalid_amount_or_currency"}}.dump();
    }
    
    if (signer_url.empty()) {
        return json{{"error", "polygon_signer_not_configured"}}.dump();
    }

    std::string payment_id = generatePaymentId();

    json req_body = {
        {"payment_id", payment_id},
        {"amount", amount},
        {"to", to_address},
        {"token", "USDC"}
    };
    std::string resp = curlPost(signer_url + "/transfer", req_body.dump(), {});
    
    json signer_resp;
    try { signer_resp = json::parse(resp); } catch(...) { signer_resp = json::object(); }

    PaymentRecord rec;
    rec.id = payment_id;
    rec.method = PaymentMethod::USDC_POLY;
    rec.status = "pending";
    rec.amount = amt;
    rec.currency = "usdc-poly";
    if (!insertPaymentRecord(config_, rec)) {
        return json{{"error", "idempotency_or_db_error"}}.dump();
    }

    return json{
        {"status", "pending"},
        {"payment_id", payment_id},
        {"method", "usdc-poly"},
        {"signer_response", signer_resp}
    }.dump();
}

std::string PaymentProcessor::handleETransfer(const std::string& payload) {
    json j = json::parse(payload);
    std::string amount = j.value("amount", "0");
    std::string email = j.value("email", "");
    
    double amt = 0.0;
    if (!validateAmountAndCurrency(amount, "cad", config_.max_payment_amount, amt)) {
        return json{{"error", "invalid_amount_or_currency"}}.dump();
    }

    std::string payment_id = generatePaymentId();

    PaymentRecord rec;
    rec.id = payment_id;
    rec.method = PaymentMethod::ETRANSFER;
    rec.status = "pending_verification";
    rec.amount = amt;
    rec.currency = "cad";
    rec.metadata = json{{"email", email}}.dump();
    if (!insertPaymentRecord(config_, rec)) {
        return json{{"error", "idempotency_or_db_error"}}.dump();
    }

    return json{
        {"status", "pending_verification"},
        {"payment_id", payment_id},
        {"method", "etransfer"},
        {"amount", amount},
        {"currency", "cad"}
    }.dump();
}

} // namespace node669427
