// ============================================================================
// ntp_timesync.cpp — see ntp_timesync.h for design notes.
// ============================================================================

#include "ntp_timesync.h"
#include "mesh_timesync.h"
#include "config.h"

#ifdef ARDUINO

#include <Arduino.h>
#include <WiFi.h>
#include <ctime>

static const char* TAG = "NtpTimesync";

// Same plausibility floor resolve_detection_ts() uses to decide whether a
// timestamp is a real epoch or boot-seconds. Kept identical on purpose: a
// value this module accepts must be one that function will also accept, or
// we'd "sync" to a clock that still produces invisible detections.
#define NTP_PLAUSIBLE_EPOCH   1600000000UL

// SNTP resolves asynchronously; poll rather than block the main loop.
#define NTP_POLL_INTERVAL_MS  1000

// If SNTP hasn't resolved in this long, restart it — covers a DHCP lease that
// came up before DNS was usable, and captive portals that swallow the first
// few packets.
#define NTP_RESTART_AFTER_MS  60000

static bool          s_sntp_started   = false;
static bool          s_have_synced    = false;
static uint32_t      s_last_poll_ms   = 0;
static uint32_t      s_started_ms     = 0;
static unsigned long s_last_epoch     = 0;

// Build the 12-byte MSG_TYPE_TIME_SYNC packet mesh_timesync expects and feed
// it TIMESYNC_MIN_PACKETS times. Feeding the same epoch repeatedly is correct
// here, not a hack: timesync_handle_packet() takes the first sample as-is and
// IIR-blends the rest, and since ts_millis() barely advances across these
// back-to-back calls, every sample yields the same offset. The alternative
// (waiting for 3 separate SNTP responses) would leave the unit clockless for
// minutes with no accuracy gain.
static void feed_timesync(unsigned long epoch_s) {
    uint64_t epoch_us = (uint64_t)epoch_s * 1000000ULL;
    uint8_t pkt[12];
    memset(pkt, 0, sizeof(pkt));
    pkt[0] = MSG_TYPE_TIME_SYNC;
    for (int i = 0; i < 8; i++) {
        pkt[1 + i] = (uint8_t)((epoch_us >> (8 * i)) & 0xFF);
    }
    for (int i = 0; i < TIMESYNC_MIN_PACKETS; i++) {
        timesync_handle_packet(pkt, sizeof(pkt));
    }
}

void ntp_timesync_tick() {
    if (s_have_synced) return;

    // The mesh beat us to it (a peer emitted 0x19, or a future T2 gateway is
    // present). Stand down — one clock source is enough, and mesh time is the
    // fleet-coherent one.
    if (timesync_is_synced()) {
        s_have_synced = true;
        return;
    }

    if (WiFi.status() != WL_CONNECTED) {
        // Dropped before resolving — re-arm so a reconnect starts cleanly.
        s_sntp_started = false;
        return;
    }

    uint32_t now_ms = millis();

    if (!s_sntp_started) {
        // Offset 0, DST 0 — everything downstream (dayKey, detection
        // timestamps, health posts) is UTC. Do NOT localize here.
        configTime(0, 0, "pool.ntp.org", "time.nist.gov");
        s_sntp_started = true;
        s_started_ms   = now_ms;
        s_last_poll_ms = now_ms;
        Serial.printf("[%s] SNTP started (UTC), waiting for resolution...\n", TAG);
        return;
    }

    if (now_ms - s_last_poll_ms < NTP_POLL_INTERVAL_MS) return;
    s_last_poll_ms = now_ms;

    time_t t = time(nullptr);
    if ((unsigned long)t < NTP_PLAUSIBLE_EPOCH) {
        if (now_ms - s_started_ms > NTP_RESTART_AFTER_MS) {
            Serial.printf("[%s] No SNTP resolution after %lu s — restarting.\n",
                          TAG, (unsigned long)(NTP_RESTART_AFTER_MS / 1000));
            s_sntp_started = false;
        }
        return;
    }

    s_last_epoch = (unsigned long)t;
    feed_timesync(s_last_epoch);
    s_have_synced = timesync_is_synced();

    Serial.printf("[%s] Clock acquired from NTP: epoch=%lu, timesync_is_synced=%s\n",
                  TAG, s_last_epoch, s_have_synced ? "true" : "false");
}

bool ntp_timesync_have_synced() { return s_have_synced; }

unsigned long ntp_timesync_last_epoch() { return s_last_epoch; }

#else  // !ARDUINO — native/test stub

void ntp_timesync_tick() {}
bool ntp_timesync_have_synced() { return false; }
unsigned long ntp_timesync_last_epoch() { return 0; }

#endif
