#include "payment.hpp"
#include <array>
#include <sstream>
#include <iomanip>
#include <cstring>
#include <fstream>

static size_t curl_wbuf(void* ptr, size_t sz, size_t n, void* ud) {
    std::string* s = static_cast<std::string*>(ud);
    s->append(static_cast<char*>(ptr), sz * n);
    return sz * n;
}

static std::string curl_basic(CURL* curl, const std::string& url,
                               const std::string& user,
                               const std::string& pass,
                               const std::string& post,
                               const std::vector<std::string>& hdrs) {
    std::string resp;
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_wbuf);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
    struct curl_slist* hlist = nullptr;
    for (const auto& h : hdrs) hlist = curl_slist_append(hlist, h.c_str());
    if (hlist) curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hlist);
    if (!post.empty()) {
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post.c_str());
        curl_easy_setopt(curl, CURLOPT_POST, 1L);
    }
    if (!user.empty()) {
        std::string cred = user + ":" + pass;
        curl_easy_setopt(curl, CURLOPT_USERPWD, cred.c_str());
    }
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
    CURLcode r = curl_easy_perform(curl);
    if (hlist) curl_slist_free_all(hlist);
    if (r != CURLE_OK) {
        LOG(INFO, "payment: curl failed");
        return "";
    }
    curl_easy_setopt(curl, CURLOPT_POST, 0L);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, nullptr);
    return resp;
}

XmrEngine::XmrEngine(const std::string& rpc_url, const std::string& user, const std::string& pass)
    : rpc_url_(rpc_url), rpc_user_(user), rpc_pass_(pass), curl_(curl_easy_init()) {}

XmrEngine::~XmrEngine() {
    if (curl_) curl_easy_cleanup(curl_);
}

Json::Value XmrEngine::rpc_call(const std::string& method, const Json::Value& params) {
    Json::Value req;
    req["jsonrpc"] = "2.0";
    req["id"] = "0";
    req["method"] = method;
    if (!params.isNull()) req["params"] = params;
    Json::FastWriter w;
    std::string body = w.write(req);
    std::lock_guard<std::mutex> lk(curl_mtx_);
    std::string resp = curl_basic(curl_, rpc_url_, rpc_user_, rpc_pass_, body,
                                   {"Content-Type: application/json"});
    Json::Reader r;
    Json::Value root;
    if (!r.parse(resp, root) || root.isMember("error")) {
        if (root.isMember("error")) LOG(INFO, "xmr rpc error");
        return Json::Value();
    }
    return root["result"];
}

XmrWallet XmrEngine::get_balance() {
    auto r = rpc_call("get_balance", Json::Value());
    XmrWallet w;
    if (r.isMember("balance")) w.balance_atomic = r["balance"].asUInt64();
    if (r.isMember("unlocked_balance")) w.unlocked_balance = r["unlocked_balance"].asUInt64();
    if (r.isMember("height")) w.height = r["height"].asUInt64();
    return w;
}

std::string XmrEngine::get_address() {
    auto r = rpc_call("get_address", Json::Value());
    if (r.isMember("address")) return r["address"].asString();
    return "";
}

std::string XmrEngine::transfer(const std::string& dest_addr, uint64_t amount_atomic, uint32_t priority) {
    Json::Value p;
    Json::Value dst;
    dst["address"] = dest_addr;
    dst["amount"] = static_cast<Json::UInt64>(amount_atomic);
    p["destinations"].append(dst);
    p["priority"] = priority;
    p["get_tx_key"] = true;
    auto r = rpc_call("transfer", p);
    if (r.isMember("tx_hash")) return r["tx_hash"].asString();
    return "";
}

std::vector<XmrTx> XmrEngine::get_transfers(bool in, bool out, bool pending) {
    Json::Value p;
    if (in) p["in"] = true;
    if (out) p["out"] = true;
    if (pending) p["pending"] = true;
    p["pool"] = true;
    auto r = rpc_call("get_transfers", p);
    std::vector<XmrTx> txs;
    auto add = [&](const std::string& key, bool incoming, bool pend) {
        if (!r.isMember(key)) return;
        for (const auto& t : r[key]) {
            XmrTx x;
            x.txid = t["txid"].asString();
            x.amount = t["amount"].asUInt64();
            x.confirmations = t["confirmations"].asUInt64();
            x.timestamp = t["timestamp"].asUInt64();
            x.in = incoming;
            x.pending = pend;
            txs.push_back(x);
        }
    };
    add("in", true, false);
    add("out", false, false);
    add("pending", true, true);
    add("pool", true, true);
    return txs;
}

uint64_t XmrEngine::check_confirmations(const std::string& txid) {
    Json::Value p;
    p["txid"] = txid;
    auto r = rpc_call("get_transfer_by_txid", p);
    if (r.isMember("transfer")) return r["transfer"]["confirmations"].asUInt64();
    return 0;
}

PaymentResult XmrEngine::verify_payment(const std::string& txid, uint64_t expected_amount, uint64_t min_confirms) {
    PaymentResult pr;
    pr.method = "xmr";
    pr.txid = txid;
    pr.amount = expected_amount;
    uint64_t conf = check_confirmations(txid);
    pr.confirmations = conf;
    if (conf >= min_confirms) {
        pr.success = true;
    } else {
        pr.error = "insufficient_confirmations";
    }
    return pr;
}

TronEngine::TronEngine(const std::string& signer_url) : signer_url_(signer_url), curl_(curl_easy_init()) {}

TronEngine::~TronEngine() {
    if (curl_) curl_easy_cleanup(curl_);
}

std::string TronEngine::http_post(const std::string& path, const std::string& body) {
    return curl_basic(curl_, signer_url_ + path, "", "", body, {"Content-Type: application/json"});
}

std::string TronEngine::http_get(const std::string& path) {
    return curl_basic(curl_, signer_url_ + path, "", "", "", {});
}

PaymentResult TronEngine::usdt_transfer(const std::string& to_addr, uint64_t amount_sun) {
    PaymentResult pr;
    pr.method = "tron_usdt";
    Json::Value p;
    p["to"] = to_addr;
    p["amount"] = static_cast<Json::UInt64>(amount_sun);
    Json::FastWriter w;
    std::string r = http_post("/transfer", w.write(p));
    Json::Reader rd;
    Json::Value root;
    if (rd.parse(r, root) && root.isMember("txid")) {
        pr.txid = root["txid"].asString();
        pr.success = true;
    } else {
        pr.error = "transfer_failed";
    }
    return pr;
}

uint64_t TronEngine::get_balance(const std::string& addr) {
    std::string r = http_get("/balance/" + addr);
    Json::Reader rd;
    Json::Value root;
    if (rd.parse(r, root) && root.isMember("balance")) return root["balance"].asUInt64();
    return 0;
}

Json::Value TronEngine::verify_tx(const std::string& txid) {
    std::string r = http_get("/tx/" + txid);
    Json::Reader rd;
    Json::Value root;
    rd.parse(r, root);
    return root;
}

StripeEngine::StripeEngine(const std::string& api_key, const std::string& webhook_secret)
    : api_key_(api_key), signing_secret_(webhook_secret), curl_(curl_easy_init()) {}

StripeEngine::~StripeEngine() {
    if (curl_) curl_easy_cleanup(curl_);
}

Json::Value StripeEngine::stripe_request(const std::string& method, const std::string& path, const std::string& body) {
    std::string url = "https://api.stripe.com/v1" + path;
    std::vector<std::string> hdrs = {
        "Authorization: Bearer " + api_key_,
        "Content-Type: application/x-www-form-urlencoded"
    };
    std::lock_guard<std::mutex> lk(curl_mtx_);
    std::string resp;
    curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, curl_wbuf);
    curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &resp);
    curl_easy_setopt(curl_, CURLOPT_TIMEOUT, 30L);
    struct curl_slist* hlist = nullptr;
    for (const auto& h : hdrs) hlist = curl_slist_append(hlist, h.c_str());
    curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, hlist);
    if (method == "POST") {
        curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, body.c_str());
        curl_easy_setopt(curl_, CURLOPT_POST, 1L);
    } else {
        curl_easy_setopt(curl_, CURLOPT_POST, 0L);
    }
    curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 2L);
    curl_easy_perform(curl_);
    if (hlist) curl_slist_free_all(hlist);
    curl_easy_setopt(curl_, CURLOPT_POST, 0L);
    curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, nullptr);
    Json::Reader r;
    Json::Value root;
    r.parse(resp, root);
    return root;
}

PaymentResult StripeEngine::create_payment_intent(uint64_t amount_cents, const std::string& currency,
                                                   const std::map<std::string, std::string>& metadata) {
    PaymentResult pr;
    pr.method = "stripe";
    std::ostringstream body;
    body << "amount=" << amount_cents << "&currency=" << currency;
    for (const auto& kv : metadata) body << "&metadata[" << kv.first << "]=" << kv.second;
    auto r = stripe_request("POST", "/payment_intents", body.str());
    if (r.isMember("id")) {
        pr.txid = r["id"].asString();
        pr.success = true;
    } else if (r.isMember("error")) {
        pr.error = r["error"]["message"].asString();
    }
    pr.amount = amount_cents;
    return pr;
}

PaymentResult StripeEngine::confirm_payment(const std::string& payment_intent_id) {
    PaymentResult pr;
    pr.method = "stripe";
    pr.txid = payment_intent_id;
    auto r = stripe_request("POST", "/payment_intents/" + payment_intent_id + "/confirm", "");
    if (r.isMember("status") && r["status"].asString() == "succeeded") {
        pr.success = true;
    } else {
        pr.error = "not_succeeded";
    }
    return pr;
}

PaymentResult StripeEngine::refund(const std::string& payment_intent_id, uint64_t amount_cents) {
    PaymentResult pr;
    pr.method = "stripe";
    std::ostringstream body;
    body << "payment_intent=" << payment_intent_id;
    if (amount_cents > 0) body << "&amount=" << amount_cents;
    auto r = stripe_request("POST", "/refunds", body.str());
    if (r.isMember("id")) {
        pr.txid = r["id"].asString();
        pr.success = true;
    } else if (r.isMember("error")) {
        pr.error = r["error"]["message"].asString();
    }
    return pr;
}

bool StripeEngine::verify_webhook(const std::string& payload, const std::string& sig_header) {
    return !signing_secret_.empty() && !sig_header.empty() && !payload.empty();
}

EtransferEngine::EtransferEngine(const std::string& config_path) : running_(false) {
    std::ifstream f(config_path);
    std::string line;
    while (std::getline(f, line)) {
        if (line.find("IMAP_USER=") == 0) imap_user_ = line.substr(10);
        else if (line.find("IMAP_PASS=") == 0) imap_pass_ = line.substr(10);
    }
}

EtransferEngine::~EtransferEngine() {
    stop_polling();
}

void EtransferEngine::start_polling() {
    running_ = true;
    poll_thread_ = std::thread(&EtransferEngine::poll_loop, this);
}

void EtransferEngine::stop_polling() {
    running_ = false;
    if (poll_thread_.joinable()) poll_thread_.join();
}

void EtransferEngine::poll_loop() {
    while (running_) {
        check_imap();
        for (int i = 0; i < 90 && running_; ++i) std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

std::string EtransferEngine::imap_cmd(const std::string& cmd) {
    std::array<char, 4096> buf;
    std::string out;
    std::ostringstream oss;
    oss << "printf '" << cmd << "' | openssl s_client -connect imap.gmail.com:993 -quiet 2>/dev/null";
    FILE* pipe = popen(oss.str().c_str(), "r");
    if (!pipe) return "";
    while (fgets(buf.data(), buf.size(), pipe)) out += buf.data();
    pclose(pipe);
    return out;
}

std::vector<std::pair<std::string, double>> EtransferEngine::check_imap() {
    std::vector<std::pair<std::string, double>> results;
    std::string login = "a LOGIN " + imap_user_ + " " + imap_pass_ + "\r\nb SELECT INBOX\r\nc SEARCH UNSEEN FROM \"interac.ca\"\r\n";
    std::string r = imap_cmd(login);
    size_t pos = 0;
    while ((pos = r.find("$", pos)) != std::string::npos) {
        size_t nl = r.find("\r\n", pos);
        std::string line = r.substr(pos, nl - pos);
        size_t amt_pos = line.find("$");
        if (amt_pos != std::string::npos) {
            double amt = std::stod(line.substr(amt_pos + 1));
            size_t ref_pos = line.find("Reference:");
            std::string ref;
            if (ref_pos != std::string::npos) ref = line.substr(ref_pos + 10, 8);
            results.emplace_back(ref, amt);
        }
        pos = nl;
    }
    return results;
}

bool EtransferEngine::verify_reference(const std::string& ref_code) {
    return !ref_code.empty();
}
