#include "CurrencyConversion.h"
#include "../Cache/Cache.h"
#include "../Utility/HttpClient.h"
#include "../Utility/JsonHelper.h"
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  const char *code;
  const char *name;
  const char *symbol;
  int is_crypto;
} CurrencyDef;

static const CurrencyDef CURRENCIES[] = {
    {"USD", "US Dollar", "$", 0},
    {"EUR", "Euro", "€", 0},
    {"GBP", "British Pound", "£", 0},
    {"JPY", "Japanese Yen", "¥", 0},
    {"AUD", "Australian Dollar", "A$", 0},
    {"CAD", "Canadian Dollar", "C$", 0},
    {"CHF", "Swiss Franc", "CHF", 0},
    {"CNY", "Chinese Yuan", "¥", 0},
    {"INR", "Indian Rupee", "₹", 0},
    {"KRW", "South Korean Won", "₩", 0},
    {"BRL", "Brazilian Real", "R$", 0},
    {"RUB", "Russian Ruble", "₽", 0},
    {"ZAR", "South African Rand", "R", 0},
    {"MXN", "Mexican Peso", "MX$", 0},
    {"SGD", "Singapore Dollar", "S$", 0},
    {"HKD", "Hong Kong Dollar", "HK$", 0},
    {"NOK", "Norwegian Krone", "kr", 0},
    {"SEK", "Swedish Krona", "kr", 0},
    {"DKK", "Danish Krone", "kr", 0},
    {"NZD", "New Zealand Dollar", "NZ$", 0},
    {"TRY", "Turkish Lira", "₺", 0},
    {"PLN", "Polish Zloty", "zł", 0},
    {"THB", "Thai Baht", "฿", 0},
    {"IDR", "Indonesian Rupiah", "Rp", 0},
    {"HUF", "Hungarian Forint", "Ft", 0},
    {"CZK", "Czech Koruna", "Kč", 0},
    {"ILS", "Israeli Shekel", "₪", 0},
    {"CLP", "Chilean Peso", "CLP$", 0},
    {"PHP", "Philippine Peso", "₱", 0},
    {"AED", "UAE Dirham", "د.إ", 0},
    {"COP", "Colombian Peso", "COP$", 0},
    {"SAR", "Saudi Riyal", "﷼", 0},
    {"MYR", "Malaysian Ringgit", "RM", 0},
    {"RON", "Romanian Leu", "lei", 0},
    {"ARS", "Argentine Peso", "ARS$", 0},
    {"PKR", "Pakistani Rupee", "₨", 0},
    {"EGP", "Egyptian Pound", "£", 0},
    {"VND", "Vietnamese Dong", "₫", 0},
    {"NGN", "Nigerian Naira", "₦", 0},
    {"BDT", "Bangladeshi Taka", "৳", 0},
    {"UAH", "Ukrainian Hryvnia", "₴", 0},
    {"PEN", "Peruvian Sol", "S/", 0},
    {"BGN", "Bulgarian Lev", "лв", 0},
    {"HRK", "Croatian Kuna", "kn", 0},
    {"ISK", "Icelandic Krona", "kr", 0},
    {"MAD", "Moroccan Dirham", "د.م.", 0},
    {"KES", "Kenyan Shilling", "KSh", 0},
    {"QAR", "Qatari Riyal", "﷼", 0},
    {"KWD", "Kuwaiti Dinar", "د.ك", 0},
    {"BHD", "Bahraini Dinar", ".د.ب", 0},
    {"OMR", "Omani Rial", "﷼", 0},
    {"JOD", "Jordanian Dinar", "د.ا", 0},
    {"TWD", "Taiwan Dollar", "NT$", 0},

    {"BTC", "Bitcoin", "₿", 1},
    {"ETH", "Ethereum", "Ξ", 1},
    {"USDT", "Tether", "₮", 1},
    {"BNB", "Binance Coin", "BNB", 1},
    {"XRP", "Ripple", "XRP", 1},
    {"USDC", "USD Coin", "$", 1},
    {"ADA", "Cardano", "ADA", 1},
    {"DOGE", "Dogecoin", "Ð", 1},
    {"SOL", "Solana", "SOL", 1},
    {"TRX", "Tron", "TRX", 1},
    {"DOT", "Polkadot", "DOT", 1},
    {"MATIC", "Polygon", "MATIC", 1},
    {"LTC", "Litecoin", "Ł", 1},
    {"SHIB", "Shiba Inu", "SHIB", 1},
    {"AVAX", "Avalanche", "AVAX", 1},
    {"LINK", "Chainlink", "LINK", 1},
    {"ATOM", "Cosmos", "ATOM", 1},
    {"XMR", "Monero", "XMR", 1},
    {"ETC", "Ethereum Classic", "ETC", 1},
    {"XLM", "Stellar", "XLM", 1},
    {"BCH", "Bitcoin Cash", "BCH", 1},
    {"ALGO", "Algorand", "ALGO", 1},
    {"VET", "VeChain", "VET", 1},
    {"FIL", "Filecoin", "FIL", 1},
    {"NEAR", "NEAR Protocol", "NEAR", 1},
    {"APT", "Aptos", "APT", 1},
    {"ARB", "Arbitrum", "ARB", 1},
    {"OP", "Optimism", "OP", 1},
    {"SAND", "The Sandbox", "SAND", 1},
    {"MANA", "Decentraland", "MANA", 1},
    {"AXS", "Axie Infinity", "AXS", 1},
    {"AAVE", "Aave", "AAVE", 1},
    {"MKR", "Maker", "MKR", 1},
    {"GRT", "The Graph", "GRT", 1},
    {"FTM", "Fantom", "FTM", 1},
    {"CRO", "Cronos", "CRO", 1},
    {"NEAR", "NEAR Protocol", "NEAR", 1},
    {"INJ", "Injective", "INJ", 1},
    {"RUNE", "THORChain", "RUNE", 1},
    {"LDO", "Lido DAO", "LDO", 1},
    {"QNT", "Quant", "QNT", 1},
    {"RNDR", "Render", "RNDR", 1},
    {"STX", "Stacks", "STX", 1},
    {"IMX", "Immutable X", "IMX", 1},
    {"SNX", "Synthetix", "SNX", 1},
    {"THETA", "Theta", "THETA", 1},
    {"XTZ", "Tezos", "XTZ", 1},
    {"EOS", "EOS", "EOS", 1},
    {"FLOW", "Flow", "FLOW", 1},
    {"CHZ", "Chiliz", "CHZ", 1},
    {"CRV", "Curve DAO", "CRV", 1},
    {"APE", "ApeCoin", "APE", 1},
    {"PEPE", "Pepe", "PEPE", 1},
    {"WIF", "dogwifhat", "WIF", 1},
    {"SUI", "Sui", "SUI", 1},
    {"SEI", "Sei", "SEI", 1},
    {"TIA", "Celestia", "TIA", 1},
    {"PYTH", "Pyth Network", "PYTH", 1},
    {"BONK", "Bonk", "BONK", 1},
    {"FET", "Fetch.ai", "FET", 1},
    {"AGIX", "SingularityNET", "AGIX", 1},
    {"RNDR", "Render Token", "RNDR", 1},
};

static const int CURRENCY_COUNT = sizeof(CURRENCIES) / sizeof(CURRENCIES[0]);

static int is_whitespace(char c) {
  return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}

static const CurrencyDef *find_currency(const char *str) {
  if (!str || !*str)
    return NULL;

  size_t len = strlen(str);
  if (len < 2 || len > 10)
    return NULL;

  char normalized[16] = {0};
  size_t j = 0;
  for (size_t i = 0; i < len && j < 15; i++) {
    normalized[j++] = toupper((unsigned char)str[i]);
  }
  normalized[j] = '\0';

  for (int i = 0; i < CURRENCY_COUNT; i++) {
    if (strcmp(normalized, CURRENCIES[i].code) == 0) {
      return &CURRENCIES[i];
    }
  }
  return NULL;
}

int is_currency_query(const char *query) {
  if (!query)
    return 0;

  const char *patterns[] = {" to ", " in ", " into ", " = ", " equals ", NULL};

  int has_pattern = 0;
  for (int i = 0; patterns[i]; i++) {
    if (strstr(query, patterns[i])) {
      has_pattern = 1;
      break;
    }
  }

  if (!has_pattern) {
    const char *last_space = strrchr(query, ' ');
    if (last_space) {
      const CurrencyDef *c = find_currency(last_space + 1);
      if (c) {
        const char *before = query;
        while (*before && is_whitespace(*before))
          before++;
        const char *num_end = before;
        while (*num_end &&
               (isdigit(*num_end) || *num_end == '.' || *num_end == ',' ||
                *num_end == '-' || *num_end == '+')) {
          num_end++;
        }
        if (num_end > before)
          has_pattern = 1;
      }
    }
  }

  return has_pattern;
}

static double parse_value(const char **ptr) {
  const char *p = *ptr;
  while (*p && is_whitespace(*p))
    p++;

  double value = 0.0;
  int has_decimal = 0;
  double decimal_place = 1.0;

  if (*p == '-' || *p == '+')
    p++;

  while (*p >= '0' && *p <= '9') {
    value = value * 10 + (*p - '0');
    if (has_decimal)
      decimal_place *= 10;
    p++;
  }

  if (*p == '.' || *p == ',') {
    has_decimal = 1;
    p++;
    while (*p >= '0' && *p <= '9') {
      value = value * 10 + (*p - '0');
      decimal_place *= 10;
      p++;
    }
  }

  if (has_decimal)
    value /= decimal_place;

  *ptr = p;
  return value;
}

static int parse_currency_query(const char *query, double *value,
                                const CurrencyDef **from_curr,
                                const CurrencyDef **to_curr) {
  *value = 0;
  *from_curr = NULL;
  *to_curr = NULL;

  const char *value_end = query;
  *value = parse_value(&value_end);

  if (value_end == query)
    return 0;

  const char *p = value_end;
  while (*p && is_whitespace(*p))
    p++;

  size_t remaining = strlen(p);
  if (remaining < 2)
    return 0;

  const char *to_keywords[] = {" to ", " in ", " into ", " -> ",
                               " → ",  " = ",  NULL};
  const char *to_pos = NULL;
  size_t keyword_len = 0;
  for (int i = 0; to_keywords[i]; i++) {
    const char *found = strstr(p, to_keywords[i]);
    if (found) {
      to_pos = found + strlen(to_keywords[i]);
      keyword_len = strlen(to_keywords[i]);
      break;
    }
  }

  if (!to_pos) {
    const char *last_space = strrchr(p, ' ');
    if (last_space && last_space > p) {
      char from_part[32] = {0};
      size_t len = last_space - p;
      if (len < 31) {
        strncpy(from_part, p, len);
        *from_curr = find_currency(from_part);
        if (*from_curr) {
          *to_curr = find_currency(last_space + 1);
          return *to_curr ? 1 : 0;
        }
      }
    }
    return 0;
  }

  char from_part[32] = {0};
  size_t from_len = to_pos - p - keyword_len;
  if (from_len > 31)
    from_len = 31;
  strncpy(from_part, p, from_len);

  char *end_from = from_part + from_len;
  while (end_from > from_part && is_whitespace(end_from[-1]))
    end_from--;
  *end_from = '\0';

  *from_curr = find_currency(from_part);
  if (!*from_curr) {
    return 0;
  }

  while (*to_pos && is_whitespace(*to_pos))
    to_pos++;

  if (!*to_pos)
    return 0;

  char to_part[32] = {0};
  size_t to_len = 0;
  const char *tp = to_pos;
  while (*tp && !is_whitespace(*tp) && to_len < 31) {
    to_part[to_len++] = *tp++;
  }
  to_part[to_len] = '\0';

  *to_curr = find_currency(to_part);
  if (!*to_curr) {
    char try_buf[32] = {0};
    strncpy(try_buf, to_pos, 31);
    *to_curr = find_currency(try_buf);
  }

  return *to_curr ? 1 : 0;
}

static double get_rate_from_json(const char *json, const char *target_code) {
  JsonFloatMap map;
  if (json_parse_float_map(json, "rates", &map)) {
    for (size_t i = 0; i < map.count; i++) {
      if (strcmp(map.keys[i], target_code) == 0) {
        return map.values[i];
      }
    }
  }
  return 0;
}

static void format_number(double val, char *buf, size_t bufsize) {
  if (bufsize == 0)
    return;
  if (val == 0) {
    snprintf(buf, bufsize, "0");
    return;
  }
  snprintf(buf, bufsize, "%.2f", val);
}

static char *build_html(double value, const CurrencyDef *from,
                        const CurrencyDef *to, double result) {
  static char html[4096];
  char val_buf[64], res_buf[64];

  format_number(value, val_buf, sizeof(val_buf));
  format_number(result, res_buf, sizeof(res_buf));

  snprintf(html, sizeof(html),
           "<div class='currency-conv-container' style='line-height: 1.6;'>"
           "<div style='font-size: 1.3em; margin-bottom: 8px;'>"
           "<b>%s %s</b> = <b>%s %s</b></div>"
           "<div style='font-size: 0.9em; color: #666;'>"
           "1 %s = %.4f %s</div>"
           "</div>",
           val_buf, from->code, res_buf, to->code, from->code, result / value,
           to->code);

  return html;
}

InfoBox fetch_currency_data(const char *query) {
  InfoBox info = {NULL, NULL, NULL, NULL};
  if (!query)
    return info;

  double value = 0;
  const CurrencyDef *from_curr = NULL;
  const CurrencyDef *to_curr = NULL;

  if (!parse_currency_query(query, &value, &from_curr, &to_curr))
    return info;
  if (!from_curr || !to_curr)
    return info;
  if (strcmp(from_curr->code, to_curr->code) == 0)
    return info;

  char cache_key[128];
  snprintf(cache_key, sizeof(cache_key), "currency_%s_%s", from_curr->code,
           to_curr->code);

  char *cached_data = NULL;
  size_t cached_size = 0;
  double rate = 0;
  int use_cache = 0;

  int is_crypto = from_curr->is_crypto || to_curr->is_crypto;

  if (get_cache_ttl_infobox() > 0) {
    if (cache_get(cache_key, get_cache_ttl_infobox(), &cached_data,
                  &cached_size) == 0 &&
        cached_data && cached_size > 0) {
      if (is_crypto) {
        rate = json_get_float(cached_data, to_curr->code);
      } else {
        rate = get_rate_from_json(cached_data, to_curr->code);
      }
      if (rate > 0) {
        use_cache = 1;
      }
      free(cached_data);
    }
  }

  if (!use_cache) {
    char url[512];

    if (is_crypto) {
      snprintf(url, sizeof(url),
               "https://min-api.cryptocompare.com/data/price?fsym=%s&tsyms=%s",
               from_curr->code, to_curr->code);
    } else {
      snprintf(url, sizeof(url),
               "https://api.exchangerate-api.com/v4/latest/%s",
               from_curr->code);
    }

    HttpResponse resp = http_get(url, "libcurl-agent/1.0");
    if (resp.memory && resp.size > 0) {
      if (is_crypto) {
        rate = json_get_float(resp.memory, to_curr->code);
      } else {
        rate = get_rate_from_json(resp.memory, to_curr->code);
      }
      if (rate > 0 && get_cache_ttl_infobox() > 0) {
        cache_set(cache_key, resp.memory, resp.size);
      }
    }
    http_response_free(&resp);
  }

  if (rate <= 0)
    return info;

  double result = value * rate;

  info.title = strdup("Currency Conversion");
  info.extract = strdup(build_html(value, from_curr, to_curr, result));
  info.thumbnail_url = strdup("/static/calculation.svg");
  info.url = strdup("#");

  return info;
}
