#include "time_manager.h"
#include "compressor.h"
#include "config.h"
#include "rtc_ds3231.h"
#include <time.h>

static bool g_time_synced = false;
static bool g_tz_set = false;
static bool g_rtc_present = false;
static uint32_t g_last_sync_utc = 0;
static constexpr uint32_t kMinValidEpoch = 1672531200UL; // 2023-01-01
static constexpr uint32_t kMaxValidEpoch = 4102444800UL; // 2100-01-01

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

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";
  configTime(0, 0, server1, server2);
  if (!g_tz_set) {
    setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
    tzset();
    g_tz_set = true;
  }
}

uint32_t time_get_utc() {
  time_t now = time(nullptr);
  if (now < 1672531200) {
    return 0;
  }
  if (!g_time_synced) {
    g_time_synced = true;
    note_last_sync(static_cast<uint32_t>(now));
  }
  return static_cast<uint32_t>(now);
}

bool time_is_synced() {
  return g_time_synced || time_get_utc() > 0;
}

void time_set_utc(uint32_t epoch) {
  if (!g_tz_set) {
    setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
    tzset();
    g_tz_set = true;
  }
  struct timeval tv;
  tv.tv_sec = epoch;
  tv.tv_usec = 0;
  settimeofday(&tv, nullptr);
  g_time_synced = true;
  note_last_sync(epoch);

  if (g_rtc_present) {
    rtc_ds3231_set_epoch(epoch);
  }
}

bool time_send_timesync(uint16_t device_id_short) {
  uint32_t epoch = time_get_utc();
  if (epoch == 0) {
    return false;
  }

  char payload_str[32];
  snprintf(payload_str, sizeof(payload_str), "T:%lu", static_cast<unsigned long>(epoch));

  uint8_t compressed[LORA_MAX_PAYLOAD];
  size_t compressed_len = 0;
  if (!compressBuffer(reinterpret_cast<const uint8_t *>(payload_str), strlen(payload_str), compressed, sizeof(compressed), compressed_len)) {
    return false;
  }

  LoraPacket pkt = {};
  pkt.protocol_version = PROTOCOL_VERSION;
  pkt.role = DeviceRole::Receiver;
  pkt.device_id_short = device_id_short;
  pkt.payload_type = PayloadType::TimeSync;
  pkt.payload_len = compressed_len;
  memcpy(pkt.payload, compressed, compressed_len);
  bool ok = lora_send(pkt);
  if (ok) {
    lora_receive_continuous();
  }
  return ok;
}

bool time_handle_timesync_payload(const uint8_t *payload, size_t len) {
  uint8_t decompressed[64];
  size_t decompressed_len = 0;
  if (!decompressBuffer(payload, len, decompressed, sizeof(decompressed), decompressed_len)) {
    return false;
  }
  if (decompressed_len >= sizeof(decompressed)) {
    return false;
  }
  decompressed[decompressed_len] = '\0';

  if (decompressed_len < 3 || decompressed[0] != 'T' || decompressed[1] != ':') {
    return false;
  }

  uint32_t epoch = static_cast<uint32_t>(strtoul(reinterpret_cast<const char *>(decompressed + 2), nullptr, 10));
  if (epoch == 0) {
    return false;
  }

  time_set_utc(epoch);
  return true;
}

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);
}

void time_rtc_init() {
  if (!ENABLE_DS3231) {
    g_rtc_present = false;
    return;
  }
  g_rtc_present = rtc_ds3231_init();
}

bool time_try_load_from_rtc() {
  if (!g_rtc_present) {
    return false;
  }
  if (time_is_synced()) {
    return true;
  }
  uint32_t epoch = 0;
  if (!rtc_ds3231_read_epoch(epoch) || epoch == 0) {
    if (SERIAL_DEBUG_MODE) {
      Serial.println("rtc: read failed");
    }
    return false;
  }
  bool valid = epoch >= kMinValidEpoch && epoch <= kMaxValidEpoch;
  if (SERIAL_DEBUG_MODE) {
    Serial.printf("rtc: epoch=%lu %s\n", static_cast<unsigned long>(epoch), valid ? "accepted" : "rejected");
  }
  if (!valid) {
    return false;
  }
  time_set_utc(epoch);
  return true;
}

bool time_rtc_present() {
  return g_rtc_present;
}

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;
}
