/*
 * time_sync.c — SNTP 客户端实现 (纯 C, POSIX socket)
 *
 * 流程 (RFC 4330 单次请求/响应):
 *   T1 = 发包前本地时间; T2/T3 = 服务器 收到/发出 时间 (回包字段);
 *   T4 = 收包后本地时间。
 *   时钟偏移 offset = ((T2-T1) + (T3-T4)) / 2
 *   校正后时间     = T4 + offset   (等价于 T3 + (T4-T1)/2, 取后者实现简单)
 *
 * 时间均换算为微秒 (uint64_t) 参与运算, NTP 小数部分 2^32 -> 1e6。
 */
#include "time_sync.h"

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#define NTP_PACKET_SIZE   48
#define NTP_ERA_DELTA     2208988800ULL   /* 1900 纪元 -> 1970 Unix 纪元 (秒) */
#define UNIX_MIN_VALID    1735689600ULL   /* 2025-01-01 (同步结果下限) */
#define UNIX_MAX_VALID    4102444800ULL   /* 2100-01-01 (同步结果上限) */

/* 国内源优先; 每台独立超时, 全部失败才算失败 */
static const char *k_servers[] = {
    "ntp.aliyun.com",
    "ntp1.aliyun.com",
    "ntp.tencent.com",
    "cn.pool.ntp.org",
    "time.windows.com",
};

static uint64_t tv_to_us(const struct timeval *tv){
    return (uint64_t)tv->tv_sec * 1000000ULL + (uint64_t)tv->tv_usec;
}

static void us_to_tv(uint64_t us, struct timeval *tv){
    tv->tv_sec  = (time_t)(us / 1000000ULL);
    tv->tv_usec = (suseconds_t)(us % 1000000ULL);
}

bool time_sync_time_valid(void){
    time_t t = time(NULL);
    struct tm tmv;
    if(!gmtime_r(&t, &tmv)) return false;
    const int year = tmv.tm_year + 1900;
    return year >= 2025 && year <= 2100;
}

/* 解析 NTP 64bit 时间戳 (偏移 bytes 起, 高 32bit 秒 + 低 32bit 小数) -> 微秒 */
static uint64_t ntp_field_to_us(const unsigned char *p){
    uint64_t sec  = ((uint64_t)p[0] << 24) | ((uint64_t)p[1] << 16)
                  | ((uint64_t)p[2] << 8)  |  (uint64_t)p[3];
    uint64_t frac = ((uint64_t)p[4] << 24) | ((uint64_t)p[5] << 16)
                  | ((uint64_t)p[6] << 8)  |  (uint64_t)p[7];
    return sec * 1000000ULL + frac * 1000000ULL / 4294967296ULL;
}

/*
 * 向单台服务器发起 SNTP 查询。
 * 成功: *out_us = 校正后的 Unix 时间 (微秒), 返回 0; 失败返回 -1。
 */
static int sntp_query_one(const char *server, int timeout_ms, uint64_t *out_us){
    struct addrinfo hints, *res = NULL;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_INET;        /* NTP 服务器均有 A 记录, 仅 IPv4 */
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

    int gai = getaddrinfo(server, "123", &hints, &res);
    if(gai != 0 || !res){
        fprintf(stderr, "[ts] DNS fail %s: %s\n", server, gai_strerror(gai));
        return -1;
    }

    int fd = socket(res->ai_family, SOCK_DGRAM, IPPROTO_UDP);
    if(fd < 0){
        fprintf(stderr, "[ts] socket fail: %s\n", strerror(errno));
        freeaddrinfo(res);
        return -1;
    }

    struct timeval tv;
    tv.tv_sec  = timeout_ms / 1000;
    tv.tv_usec = (timeout_ms % 1000) * 1000;
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

    /* SNTP 请求: 48 字节全 0, 仅首字节 LI=0 VN=3 Mode=3 => 0x1B,
     * 并填 Originate/Transmit Timestamp (T1) 供服务器回显 */
    unsigned char req[NTP_PACKET_SIZE];
    memset(req, 0, sizeof(req));
    req[0] = 0x1B;

    struct timeval t1, t4;
    gettimeofday(&t1, NULL);
    uint64_t t1_us = tv_to_us(&t1);
    uint64_t t1_ntp = t1_us / 1000000ULL + NTP_ERA_DELTA;
    uint32_t t1_frac = (uint32_t)((t1_us % 1000000ULL) * 4294967296ULL / 1000000ULL);
    req[40] = (unsigned char)(t1_ntp >> 24);          /* Transmit Timestamp 秒 */
    req[41] = (unsigned char)(t1_ntp >> 16);
    req[42] = (unsigned char)(t1_ntp >> 8);
    req[43] = (unsigned char)(t1_ntp);
    req[44] = (unsigned char)(t1_frac >> 24);         /* 小数部分 */
    req[45] = (unsigned char)(t1_frac >> 16);
    req[46] = (unsigned char)(t1_frac >> 8);
    req[47] = (unsigned char)(t1_frac);

    ssize_t n = sendto(fd, req, sizeof(req), 0, res->ai_addr, res->ai_addrlen);
    freeaddrinfo(res);
    if(n != (ssize_t)sizeof(req)){
        fprintf(stderr, "[ts] sendto %s fail: %s\n", server, strerror(errno));
        close(fd);
        return -1;
    }

    unsigned char rsp[NTP_PACKET_SIZE];
    n = recvfrom(fd, rsp, sizeof(rsp), 0, NULL, NULL);
    gettimeofday(&t4, NULL);
    int saved_errno = errno;
    close(fd);

    if(n < NTP_PACKET_SIZE){
        fprintf(stderr, "[ts] %s %s\n", server,
                (n < 0) ? strerror(saved_errno) : "bad response size");
        return -1;
    }
    if((rsp[0] & 0x07) != 4){                 /* Mode 必须是 4 (server) */
        fprintf(stderr, "[ts] %s not a server response (mode=%d)\n",
                server, rsp[0] & 0x07);
        return -1;
    }
    if(rsp[1] == 0){                          /* Stratum 0 = kiss-of-death */
        fprintf(stderr, "[ts] %s kiss-of-death, skip\n", server);
        return -1;
    }

    /* Transmit Timestamp T3 (bytes 40..47) + 半 RTT 补偿 */
    uint64_t t3_us = ntp_field_to_us(rsp + 40);
    uint64_t t3_unix = t3_us / 1000000ULL;
    if(t3_unix < NTP_ERA_DELTA ||
       t3_unix - NTP_ERA_DELTA < UNIX_MIN_VALID ||
       t3_unix - NTP_ERA_DELTA > UNIX_MAX_VALID){
        fprintf(stderr, "[ts] %s transmit ts unreasonable, skip\n", server);
        return -1;
    }
    t3_us -= NTP_ERA_DELTA * 1000000ULL;     /* 转为 Unix 微秒 */

    uint64_t rtt = tv_to_us(&t4) - t1_us;    /* 单向延迟近似 rtt/2 */
    uint64_t corrected = t3_us + rtt / 2;

    *out_us = corrected;
    return 0;
}

int time_sync_sntp(int timeout_ms_per_server){
    for(size_t i = 0; i < sizeof(k_servers) / sizeof(k_servers[0]); i++){
        fprintf(stderr, "[ts] trying %s ...\n", k_servers[i]);
        uint64_t us = 0;
        if(sntp_query_one(k_servers[i], timeout_ms_per_server, &us) == 0){
            struct timeval tv;
            us_to_tv(us, &tv);
            if(settimeofday(&tv, NULL) != 0){
                fprintf(stderr, "[ts] settimeofday fail: %s (need root)\n",
                        strerror(errno));
                return TIME_SYNC_ERR_SETTIME;
            }
            struct tm tmv;
            char buf[32];
            gmtime_r(&tv.tv_sec, &tmv);
            strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tmv);
            fprintf(stderr, "[ts] synced via %s -> %s.%03d UTC\n",
                    k_servers[i], buf, (int)(tv.tv_usec / 1000));
            return TIME_SYNC_OK;
        }
    }
    return TIME_SYNC_ERR_TIMEOUT;
}

bool time_sync_ensure(void){
    const bool was_valid = time_sync_time_valid();
    time_t t = time(NULL);
    struct tm tmv;
    char buf[32];
    gmtime_r(&t, &tmv);
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tmv);

    if(was_valid)
        fprintf(stderr, "[ts] system time looks valid (%s UTC), verifying via SNTP ...\n", buf);
    else
        fprintf(stderr, "[ts] system time invalid (%s UTC), SNTP syncing ...\n", buf);

    if(time_sync_sntp(1500) == TIME_SYNC_OK) return true;

    /* UDP/123 may be blocked. A plausible existing clock can still be used for TLS. */
    if(time_sync_time_valid()){
        fprintf(stderr, "[ts] warning: SNTP unavailable; keeping existing system time\n");
        return true;
    }
    fprintf(stderr, "[ts] error: SNTP failed and system time is invalid\n");
    return false;
}
