#include "time_manager.h"
#include "config.h"
#include <time.h>
#ifdef ARDUINO_ARCH_ESP32
#include <esp_sntp.h>
#endif

static bool g_time_synced = false;
static bool g_clock_plausible = false;
static bool g_tz_set = false;
static uint32_t g_last_sync_utc = 0;
static constexpr uint32_t MIN_PLAUSIBLE_EPOCH_UTC = 1672531200UL; // 2023-01-01 00:00:00 UTC

static void note_last_sync(uint32_t epoch) {
  if (epoch == 0) {
    return;
  }
  g_last_sync_utc = epoch;
}

static bool epoch_is_plausible(time_t epoch) {
  return epoch >= static_cast<time_t>(MIN_PLAUSIBLE_EPOCH_UTC);
}

static void mark_synced(uint32_t epoch) {
  if (epoch == 0) {
    return;
  }
  g_time_synced = true;
  g_clock_plausible = true;
  note_last_sync(epoch);
}

#ifdef ARDUINO_ARCH_ESP32
static void ntp_sync_notification_cb(struct timeval *tv) {
  time_t epoch = tv ? tv->tv_sec : time(nullptr);
  if (!epoch_is_plausible(epoch)) {
    return;
  }
  if (epoch > static_cast<time_t>(UINT32_MAX)) {
    return;
  }
  mark_synced(static_cast<uint32_t>(epoch));
}
#endif

static void ensure_timezone_set() {
  if (g_tz_set) {
    return;
  }
  setenv("TZ", TIMEZONE_TZ, 1);
  tzset();
  g_tz_set = true;
}

void time_receiver_init(const char *ntp_server_1, const char *ntp_server_2) {
  const char *server1 = (ntp_server_1 && ntp_server_1[0] != '\0') ? ntp_server_1 : "pool.ntp.org";
  const char *server2 = (ntp_server_2 && ntp_server_2[0] != '\0') ? ntp_server_2 : "time.nist.gov";
#ifdef ARDUINO_ARCH_ESP32
  sntp_set_time_sync_notification_cb(ntp_sync_notification_cb);
#endif
  configTime(0, 0, server1, server2);
  ensure_timezone_set();
}

uint32_t time_get_utc() {
  time_t now = time(nullptr);
  if (!epoch_is_plausible(now)) {
    g_clock_plausible = false;
    return 0;
  }
  g_clock_plausible = true;
#ifdef ARDUINO_ARCH_ESP32
  if (!g_time_synced && sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
    mark_synced(static_cast<uint32_t>(now));
  }
#endif
  return static_cast<uint32_t>(now);
}

bool time_is_synced() {
  (void)time_get_utc();
  return g_time_synced && g_clock_plausible;
}

void time_set_utc(uint32_t epoch) {
  ensure_timezone_set();
  struct timeval tv;
  tv.tv_sec = epoch;
  tv.tv_usec = 0;
  settimeofday(&tv, nullptr);
  if (epoch_is_plausible(static_cast<time_t>(epoch))) {
    mark_synced(epoch);
  } else {
    g_clock_plausible = false;
    g_time_synced = false;
  }
}

void time_get_local_hhmm(char *out, size_t out_len) {
  if (!time_is_synced()) {
    snprintf(out, out_len, "--:--");
    return;
  }
  time_t now = time(nullptr);
  struct tm timeinfo;
  localtime_r(&now, &timeinfo);
  snprintf(out, out_len, "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
}

uint32_t time_get_last_sync_utc() {
  return g_last_sync_utc;
}

uint32_t time_get_last_sync_age_sec() {
  if (!time_is_synced()) {
    return 0;
  }
  if (g_last_sync_utc == 0) {
    return 0;
  }
  uint32_t now = time_get_utc();
  return now > g_last_sync_utc ? now - g_last_sync_utc : 0;
}
