#include "weather.h"
#include <curl/curl.h>
#include <sstream>
#include <iostream>
#include <cstdlib>
#include <fstream>

#include <nlohmann/json.hpp>
using nlohmann::json;


bool LoadWeatherCache(const std::string &path, WeatherData *out) {
  try {
    std::ifstream f(path);
    if (!f) return false;
    json j;
    f >> j;

    out->ok = j.value("ok", false);
    out->temp = j.value("temp", 0.0);
    out->hi = j.value("hi", 0.0);
    out->lo = j.value("lo", 0.0);
    out->code = j.value("code", 3);
    out->label = j.value("label", std::string("Weather"));
    out->fetched_at = (std::time_t)j.value("fetched_at", 0);
    out->tomorrow_hi = j.value("tomorrow_hi", 0.0);
    out->tomorrow_lo = j.value("tomorrow_lo", 0.0);
    out->today_code = j.value("today_code", 3);
    out->tomorrow_code = j.value("tomorrow_code", 3);
    out->today_pop = j.value("today_pop", 0);
    out->tomorrow_pop = j.value("tomorrow_pop", 0);
    return true;
  } catch (...) {
    return false;
  }
}

void SaveWeatherCache(const std::string &path, const WeatherData &w) {
  try {
    json j;
    j["ok"] = w.ok;
    j["temp"] = w.temp;
    j["hi"] = w.hi;
    j["lo"] = w.lo;
    j["code"] = w.code;
    j["label"] = w.label;
    j["fetched_at"] = (long long)w.fetched_at;
    j["tomorrow_hi"] = w.tomorrow_hi;
    j["tomorrow_lo"] = w.tomorrow_lo;
    j["today_code"] = w.today_code;
    j["tomorrow_code"] = w.tomorrow_code;
    j["today_pop"] = w.today_pop;
    j["tomorrow_pop"] = w.tomorrow_pop;


    std::ofstream f(path);
    if (f) f << j.dump(2);
  } catch (...) {
    // ignore cache write failures
  }
}

static size_t WriteCB(void *contents, size_t size, size_t nmemb, void *userp) {
  size_t total = size * nmemb;
  std::string *s = static_cast<std::string*>(userp);
  s->append(static_cast<char*>(contents), total);
  return total;
}

std::string WeatherLabelFromCode(int code) {
  switch (code) {
    case 0: return "Clear";
    case 1: return "Mst Clr";
    case 2: return "Prt Cldy";
    case 3: return "Overcast";
    case 45: case 48: return "Fog";
    case 51: case 53: case 55: return "Drizzle";
    case 61: case 63: case 65: return "Rain";
    case 71: case 73: case 75: return "Snow";
    case 80: case 81: case 82: return "Showers";
    case 95: case 96: case 99: return "T-Storm";
    default: return "Weather";
  }
}

bool FetchWeatherOpenMeteo(double lat, double lon, bool fahrenheit, int hours, WeatherData *out) {
  (void)hours;  // reserved for future hourly features
  if (!out) return false;
  *out = WeatherData{};

  std::ostringstream url;
  url << "https://api.open-meteo.com/v1/forecast"
      << "?latitude=" << lat
      << "&longitude=" << lon
      << "&current=temperature_2m,weather_code"
      << "&daily=temperature_2m_max,temperature_2m_min,weather_code,precipitation_probability_max"
      << "&hourly=temperature_2m,precipitation_probability"
      << "&forecast_days=2"
      << "&timezone=auto";
  if (fahrenheit) url << "&temperature_unit=fahrenheit";

  const char* dbg = std::getenv("WEATHERCUBE_DEBUG");
  if (dbg && *dbg == '1') {
    std::cerr << "Open-Meteo URL: " << url.str() << "\n";
  }

  CURL *curl = curl_easy_init();
  if (!curl) return false;

  std::string body;
  curl_easy_setopt(curl, CURLOPT_URL, url.str().c_str());
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCB);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
  curl_easy_setopt(curl, CURLOPT_USERAGENT, "infocube/1.0");
  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 3L);
  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
  curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 10L);
  curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 10L); // bytes/sec

  char errbuf[CURL_ERROR_SIZE];
  errbuf[0] = 0;
  curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);


  CURLcode res = curl_easy_perform(curl);

  long http_code = 0;
  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

  double namelookup = 0, connect = 0, appconnect = 0, starttransfer = 0, total = 0;
  curl_easy_getinfo(curl, CURLINFO_NAMELOOKUP_TIME, &namelookup);
  curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME, &connect);
  curl_easy_getinfo(curl, CURLINFO_APPCONNECT_TIME, &appconnect);
  curl_easy_getinfo(curl, CURLINFO_STARTTRANSFER_TIME, &starttransfer);
  curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total);

  std::cerr << "curl error: " << curl_easy_strerror(res)
          << " http=" << http_code
          << " t_dns=" << namelookup
          << " t_conn=" << connect
          << " t_tls=" << appconnect
          << " t_first=" << starttransfer
          << " t_total=" << total
          << "\n";


  if (res != CURLE_OK) {
    char *eff_url = nullptr;
    curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &eff_url);

    std::cerr << "curl error: " << curl_easy_strerror(res);
    if (errbuf[0]) std::cerr << " detail=" << errbuf;
    if (eff_url) std::cerr << " url=" << eff_url;
    std::cerr << "\n";
    curl_easy_cleanup(curl);
    return false;
  }

  curl_easy_cleanup(curl);

  if (http_code != 200) {
    std::cerr << "http status: " << http_code << "\n";
    return false;
  }

  if (body.size() < 50) {
    std::cerr << "response too small (" << body.size() << " bytes)\n";
    return false;
  }

  // Parse current temp + code
  // Parse JSON (reliable, avoids brittle string searching)
  // ---- Parse JSON ----
  json j;
  try {
    j = json::parse(body);
  } catch (const std::exception &e) {
    std::cerr << "JSON parse failed: " << e.what() << "\n";
    return false;
  }

  // ---- Current ----
  try {
    out->temp = j.at("current").at("temperature_2m").get<double>();
    out->code = j.at("current").at("weather_code").get<int>();
  } catch (const std::exception &e) {
    std::cerr << "Current parse failed: " << e.what() << "\n";
    return false;
  }

  // ---- Daily (today + tomorrow) ----
  try {
    const auto &daily = j.at("daily");

    const auto &tmax = daily.at("temperature_2m_max");
    const auto &tmin = daily.at("temperature_2m_min");
    const auto &wcod = daily.at("weather_code");
    const auto &ppmx = daily.at("precipitation_probability_max");

    out->hi = tmax.at(0).get<double>();
    out->lo = tmin.at(0).get<double>();
    out->tomorrow_hi = tmax.at(1).get<double>();
    out->tomorrow_lo = tmin.at(1).get<double>();

    out->today_code = wcod.at(0).get<int>();
    out->tomorrow_code = wcod.at(1).get<int>();

    out->today_pop = ppmx.at(0).get<int>();
    out->tomorrow_pop = ppmx.at(1).get<int>();
  } catch (const std::exception &e) {
    std::cerr << "Daily parse failed: " << e.what() << "\n";
    try {
      std::cerr << "Daily keys present: ";
      for (auto it = j.at("daily").begin(); it != j.at("daily").end(); ++it)
        std::cerr << it.key() << " ";
      std::cerr << "\n";
    } catch (...) {}
    return false;
  }

  out->label = WeatherLabelFromCode(out->code);
  out->ok = true;
  out->fetched_at = std::time(nullptr);
  return true;

}
