#pragma once
// Per-exchange OHLC candle adapters. CoinGecko tells us WHICH exchanges list DRGX (via the ticker
// `market.identifier`, e.g. "ourbit" / "nonkyc_io") but NOT their API — this is the hand-maintained
// mapping from that identifier to each venue's public candle endpoint, so the market chart can show the
// SELECTED exchange's real price history instead of CoinGecko's cross-exchange aggregate.
//
// Header-only + pure (no I/O — the actual HTTP fetch happens in app_network.cpp via util::httpGetString)
// so the URL builder + parsers are unit-tested against real captured responses. An unmapped exchange
// returns an empty URL, so the caller falls back to the CoinGecko aggregate and nothing regresses.
#include <algorithm>
#include <ctime>
#include <string>
#include <utility>
#include <vector>

#include <nlohmann/json.hpp>

#include "candle.h"

namespace dragonx {
namespace data {

using PricePoint = std::pair<std::time_t, double>;   // (epoch SECONDS, close price)

enum class CandleRange {
    Intraday,   // ~2 days of 5-minute candles — backs the Live / 1H / 1D views
    Daily,      // ~1 year of daily candles     — backs the 1W / 1M views
};

// True when we have a candle adapter for this CoinGecko exchange identifier.
inline bool hasExchangeCandleAdapter(const std::string& identifier) {
    return identifier == "ourbit" || identifier == "nonkyc_io";
}

// Build the venue's candle URL for a pair + range. Returns "" when the exchange isn't mapped.
// `now` is epoch seconds, passed in (no hidden clock reads) so URLs are deterministic in tests.
inline std::string buildExchangeCandleUrl(const std::string& identifier, const std::string& base,
                                          const std::string& quote, CandleRange range, std::time_t now) {
    if (identifier == "ourbit") {
        // Ourbit = MEXC-style /api/v3/klines: symbol=BASEQUOTE, interval=5m|1d, limit.
        const char* iv    = (range == CandleRange::Intraday) ? "5m" : "1d";
        const int   limit = (range == CandleRange::Intraday) ? 576  : 365;
        return "https://api.ourbit.com/api/v3/klines?symbol=" + base + quote +
               "&interval=" + iv + "&limit=" + std::to_string(limit);
    }
    if (identifier == "nonkyc_io") {
        // NonKYC = TradingView-UDF candles: symbol=BASE_QUOTE, resolution in minutes, from/to seconds.
        const char*       res  = (range == CandleRange::Intraday) ? "5" : "1440";
        const std::time_t span = (range == CandleRange::Intraday) ? (std::time_t)2 * 24 * 3600
                                                                  : (std::time_t)365 * 24 * 3600;
        return "https://api.nonkyc.io/api/v2/market/candles?symbol=" + base + "_" + quote +
               "&resolution=" + res + "&from=" + std::to_string(now - span) + "&to=" + std::to_string(now);
    }
    return "";
}

namespace detail {
// Read a JSON value that may be a number or a numeric string (Ourbit sends prices as strings).
inline double jnum(const nlohmann::json& v) {
    if (v.is_number()) return v.get<double>();
    if (v.is_string()) { try { return std::stod(v.get<std::string>()); } catch (...) { return 0.0; } }
    return 0.0;
}
} // namespace detail

// Parse the venue's candle response into ascending OHLC candles. Empty on failure.
inline std::vector<Candle> parseExchangeOHLC(const std::string& identifier, const std::string& body) {
    std::vector<Candle> out;
    if (body.empty()) return out;
    try {
        const nlohmann::json j = nlohmann::json::parse(body);
        if (identifier == "ourbit") {
            // [[openTime_ms, open, high, low, close, volume, closeTime, quoteVol], ...]
            if (!j.is_array()) return out;
            out.reserve(j.size());
            for (const auto& k : j) {
                if (!k.is_array() || k.size() < 5) continue;
                Candle c;
                c.time  = (std::time_t)(detail::jnum(k[0]) / 1000.0);
                c.open  = detail::jnum(k[1]);
                c.high  = detail::jnum(k[2]);
                c.low   = detail::jnum(k[3]);
                c.close = detail::jnum(k[4]);
                if (c.time > 0 && c.close > 0) out.push_back(c);
            }
        } else if (identifier == "nonkyc_io") {
            // {"bars":[{"time":ms,"open":..,"high":..,"low":..,"close":..,"volume":..}, ...]}
            if (!j.contains("bars") || !j["bars"].is_array()) return out;
            out.reserve(j["bars"].size());
            for (const auto& b : j["bars"]) {
                if (!b.is_object() || !b.contains("time") || !b.contains("close")) continue;
                Candle c;
                c.time  = (std::time_t)(detail::jnum(b["time"]) / 1000.0);
                c.close = detail::jnum(b["close"]);
                c.open  = b.contains("open") ? detail::jnum(b["open"]) : c.close;
                c.high  = b.contains("high") ? detail::jnum(b["high"]) : c.close;
                c.low   = b.contains("low")  ? detail::jnum(b["low"])  : c.close;
                if (c.time > 0 && c.close > 0) out.push_back(c);
            }
        }
    } catch (...) {
        return {};
    }
    std::sort(out.begin(), out.end(), [](const Candle& a, const Candle& b) { return a.time < b.time; });
    return out;
}

// Close-only convenience over parseExchangeOHLC (backs the line chart + change-% fallback). bucketOHLC
// for candlestick resampling lives in candle.h (dependency-free, reused by market_series.h).
inline std::vector<PricePoint> parseExchangeCandles(const std::string& identifier, const std::string& body) {
    std::vector<PricePoint> out;
    for (const auto& c : parseExchangeOHLC(identifier, body)) out.emplace_back(c.time, c.close);
    return out;
}

} // namespace data
} // namespace dragonx
