/*
 * 薯芯云授权中心 - C 语言 SDK 实现
 * 依赖 libcurl
 */
#include "auth_client.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <curl/curl.h>

/* ---------- MD5 实现（公共领域精简版） ---------- */
typedef struct { unsigned int state[4]; unsigned int count[2]; unsigned char buffer[64]; } MD5_CTX;

#define F(x,y,z) (((x) & (y)) | ((~x) & (z)))
#define G(x,y,z) (((x) & (z)) | ((y) & (~z)))
#define H(x,y,z) ((x) ^ (y) ^ (z))
#define I(x,y,z) ((y) ^ ((x) | (~z)))
#define ROTATE_LEFT(x,n) (((x) << (n)) | ((x) >> (32-(n))))

static void MD5Transform(unsigned int state[4], const unsigned char block[64]);
static void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len);
static void MD5Decode(unsigned int *output, const unsigned char *input, unsigned int len);

static unsigned char PADDING[64] = { 0x80 };

#define FF(a,b,c,d,x,s,ac) { (a) += F((b),(c),(d)) + (x) + (unsigned int)(ac); (a) = ROTATE_LEFT((a),(s)); (a) += (b); }
#define GG(a,b,c,d,x,s,ac) { (a) += G((b),(c),(d)) + (x) + (unsigned int)(ac); (a) = ROTATE_LEFT((a),(s)); (a) += (b); }
#define HH(a,b,c,d,x,s,ac) { (a) += H((b),(c),(d)) + (x) + (unsigned int)(ac); (a) = ROTATE_LEFT((a),(s)); (a) += (b); }
#define II(a,b,c,d,x,s,ac) { (a) += I((b),(c),(d)) + (x) + (unsigned int)(ac); (a) = ROTATE_LEFT((a),(s)); (a) += (b); }

static void MD5Init(MD5_CTX *context) {
    context->count[0] = context->count[1] = 0;
    context->state[0] = 0x67452301;
    context->state[1] = 0xefcdab89;
    context->state[2] = 0x98badcfe;
    context->state[3] = 0x10325476;
}

static void MD5Update(MD5_CTX *context, const unsigned char *input, unsigned int inputLen) {
    unsigned int i, index, partLen;
    index = (unsigned int)((context->count[0] >> 3) & 0x3F);
    if ((context->count[0] += ((unsigned int)inputLen << 3)) < ((unsigned int)inputLen << 3))
        context->count[1]++;
    context->count[1] += ((unsigned int)inputLen >> 29);
    partLen = 64 - index;
    if (inputLen >= partLen) {
        memcpy(&context->buffer[index], input, partLen);
        MD5Transform(context->state, context->buffer);
        for (i = partLen; i + 63 < inputLen; i += 64)
            MD5Transform(context->state, &input[i]);
        index = 0;
    } else {
        i = 0;
    }
    memcpy(&context->buffer[index], &input[i], inputLen - i);
}

static void MD5Final(unsigned char digest[16], MD5_CTX *context) {
    unsigned char bits[8];
    unsigned int index, padLen;
    MD5Encode(bits, context->count, 8);
    index = (unsigned int)((context->count[0] >> 3) & 0x3f);
    padLen = (index < 56) ? (56 - index) : (120 - index);
    MD5Update(context, PADDING, padLen);
    MD5Update(context, bits, 8);
    MD5Encode(digest, context->state, 16);
    memset(context, 0, sizeof(*context));
}

static void MD5Transform(unsigned int state[4], const unsigned char block[64]) {
    unsigned int a = state[0], b = state[1], c = state[2], d = state[3], x[16];
    MD5Decode(x, block, 64);
    FF(a,b,c,d,x[0],7,0xd76aa478); FF(d,a,b,c,x[1],12,0xe8c7b756);
    FF(c,d,a,b,x[2],17,0x242070db); FF(b,c,d,a,x[3],22,0xc1bdceee);
    FF(a,b,c,d,x[4],7,0xf57c0faf); FF(d,a,b,c,x[5],12,0x4787c62a);
    FF(c,d,a,b,x[6],17,0xa8304613); FF(b,c,d,a,x[7],22,0xfd469501);
    FF(a,b,c,d,x[8],7,0x698098d8); FF(d,a,b,c,x[9],12,0x8b44f7af);
    FF(c,d,a,b,x[10],17,0xffff5bb1); FF(b,c,d,a,x[11],22,0x895cd7be);
    FF(a,b,c,d,x[12],7,0x6b901122); FF(d,a,b,c,x[13],12,0xfd987193);
    FF(c,d,a,b,x[14],17,0xa679438e); FF(b,c,d,a,x[15],22,0x49b40821);
    GG(a,b,c,d,x[1],5,0xf61e2562); GG(d,a,b,c,x[6],9,0xc040b340);
    GG(c,d,a,b,x[11],14,0x265e5a51); GG(b,c,d,a,x[0],20,0xe9b6c7aa);
    GG(a,b,c,d,x[5],5,0xd62f105d); GG(d,a,b,c,x[10],9,0x02441453);
    GG(c,d,a,b,x[15],14,0xd8a1e681); GG(b,c,d,a,x[4],20,0xe7d3fbc8);
    GG(a,b,c,d,x[9],5,0x21e1cde6); GG(d,a,b,c,x[14],9,0xc33707d6);
    GG(c,d,a,b,x[3],14,0xf4d50d87); GG(b,c,d,a,x[8],20,0x455a14ed);
    GG(a,b,c,d,x[13],5,0xa9e3e905); GG(d,a,b,c,x[2],9,0xfcefa3f8);
    GG(c,d,a,b,x[7],14,0x676f02d9); GG(b,c,d,a,x[12],20,0x8d2a4c8a);
    HH(a,b,c,d,x[5],4,0xfffa3942); HH(d,a,b,c,x[8],11,0x8771f681);
    HH(c,d,a,b,x[11],16,0x6d9d6122); HH(b,c,d,a,x[14],23,0xfde5380c);
    HH(a,b,c,d,x[1],4,0xa4beea44); HH(d,a,b,c,x[4],11,0x4bdecfa9);
    HH(c,d,a,b,x[7],16,0xf6bb4b60); HH(b,c,d,a,x[10],23,0xbebfbc70);
    HH(a,b,c,d,x[13],4,0x289b7ec6); HH(d,a,b,c,x[0],11,0xeaa127fa);
    HH(c,d,a,b,x[3],16,0xd4ef3085); HH(b,c,d,a,x[6],23,0x04881d05);
    HH(a,b,c,d,x[9],4,0xd9d4d039); HH(d,a,b,c,x[12],11,0xe6db99e5);
    HH(c,d,a,b,x[15],16,0x1fa27cf8); HH(b,c,d,a,x[2],23,0xc4ac5665);
    II(a,b,c,d,x[0],6,0xf4292244); II(d,a,b,c,x[7],10,0x432aff97);
    II(c,d,a,b,x[14],15,0xab9423a7); II(b,c,d,a,x[5],21,0xfc93a039);
    II(a,b,c,d,x[12],6,0x655b59c3); II(d,a,b,c,x[3],10,0x8f0ccc92);
    II(c,d,a,b,x[10],15,0xffeff47d); II(b,c,d,a,x[1],21,0x85845dd1);
    II(a,b,c,d,x[8],6,0x6fa87e4f); II(d,a,b,c,x[15],10,0xfe2ce6e0);
    II(c,d,a,b,x[6],15,0xa3014314); II(b,c,d,a,x[13],21,0x4e0811a1);
    II(a,b,c,d,x[4],6,0xf7537e82); II(d,a,b,c,x[11],10,0xbd3af235);
    II(c,d,a,b,x[2],15,0x2ad7d2bb); II(b,c,d,a,x[9],21,0xeb86d391);
    state[0] += a; state[1] += b; state[2] += c; state[3] += d;
}

static void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len) {
    unsigned int i, j;
    for (i = 0, j = 0; j < len; i++, j += 4) {
        output[j]   = (unsigned char)(input[i] & 0xff);
        output[j+1] = (unsigned char)((input[i] >> 8) & 0xff);
        output[j+2] = (unsigned char)((input[i] >> 16) & 0xff);
        output[j+3] = (unsigned char)((input[i] >> 24) & 0xff);
    }
}

static void MD5Decode(unsigned int *output, const unsigned char *input, unsigned int len) {
    unsigned int i, j;
    for (i = 0, j = 0; j < len; i++, j += 4)
        output[i] = ((unsigned int)input[j]) | (((unsigned int)input[j+1]) << 8) |
                    (((unsigned int)input[j+2]) << 16) | (((unsigned int)input[j+3]) << 24);
}

void czx_md5_upper(const char *input, char *output) {
    MD5_CTX ctx;
    unsigned char digest[16];
    int i;
    MD5Init(&ctx);
    MD5Update(&ctx, (const unsigned char *)input, (unsigned int)strlen(input));
    MD5Final(digest, &ctx);
    for (i = 0; i < 16; i++) sprintf(output + i * 2, "%02X", digest[i]);
    output[32] = '\0';
}

/* ---------- libcurl 响应收集 ---------- */
typedef struct { char *data; size_t size; } Buffer;

static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *userdata) {
    Buffer *buf = (Buffer *)userdata;
    size_t real = size * nmemb;
    char *p = (char *)realloc(buf->data, buf->size + real + 1);
    if (!p) return 0;
    buf->data = p;
    memcpy(&(buf->data[buf->size]), ptr, real);
    buf->size += real;
    buf->data[buf->size] = '\0';
    return real;
}

/* ---------- URL 编码 ---------- */
static void url_encode(const char *src, char *dst, size_t dstLen) {
    static const char *hex = "0123456789ABCDEF";
    size_t j = 0;
    for (size_t i = 0; src[i] && j + 4 < dstLen; i++) {
        unsigned char c = (unsigned char)src[i];
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
            (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') {
            dst[j++] = (char)c;
        } else {
            dst[j++] = '%';
            dst[j++] = hex[c >> 4];
            dst[j++] = hex[c & 0x0F];
        }
    }
    dst[j] = '\0';
}

/* ---------- 简易 JSON 字段提取 ---------- */
static int json_get_string(const char *json, const char *key, char *out, size_t outLen) {
    char pat[128];
    snprintf(pat, sizeof(pat), "\"%s\"", key);
    const char *p = strstr(json, pat);
    if (!p) { out[0] = '\0'; return 0; }
    p += strlen(pat);
    while (*p && (*p == ' ' || *p == ':' || *p == '\t')) p++;
    if (*p == '"') {
        p++;
        size_t i = 0;
        while (*p && *p != '"' && i + 1 < outLen) {
            if (*p == '\\' && *(p + 1)) {
                char nx = *(p + 1);
                if (nx == 'n')      { out[i++] = '\n'; p += 2; continue; }
                if (nx == 't')      { out[i++] = '\t'; p += 2; continue; }
                if (nx == 'r')      { p += 2; continue; }
                if (nx == '"' || nx == '\\' || nx == '/') { out[i++] = nx; p += 2; continue; }
                p++; /* 其它转义序列：跳过反斜杠 */
                continue;
            }
            out[i++] = *p++;
        }
        out[i] = '\0';
        return 1;
    } else {
        size_t i = 0;
        while (*p && *p != ',' && *p != '}' && i + 1 < outLen) out[i++] = *p++;
        out[i] = '\0';
        /* 去尾部空格 */
        while (i > 0 && (out[i-1] == ' ' || out[i-1] == '\n' || out[i-1] == '\r')) out[--i] = '\0';
        return 1;
    }
}

/* ---------- 提取嵌套在 data 中的字段 ---------- */
static int json_get_in_data(const char *json, const char *key, char *out, size_t outLen) {
    const char *d = strstr(json, "\"data\"");
    if (!d) { out[0] = '\0'; return 0; }
    return json_get_string(d, key, out, outLen);
}

/* ============================================================
 *  公共 API
 * ============================================================ */

void czx_init(CzxClient *client, const char *license_key,
              const char *app_key, const char *api_url, int timeout) {
    memset(client, 0, sizeof(*client));
    snprintf(client->license_key, sizeof(client->license_key), "%s", license_key ? license_key : "");
    snprintf(client->app_key, sizeof(client->app_key), "%s", app_key ? app_key : "");
    snprintf(client->api_url, sizeof(client->api_url), "%s",
             (api_url && *api_url) ? api_url : "https://auth.chipzx.com/api/license.php");
    snprintf(client->update_url, sizeof(client->update_url), "%s",
             "https://auth.chipzx.com/api/update.php");
    client->timeout = timeout > 0 ? timeout : 10;
}

void czx_set_app_id(CzxClient *client, const char *app_id) {
    if (!client) return;
    snprintf(client->app_id, sizeof(client->app_id), "%s", app_id ? app_id : "");
}

int czx_verify(CzxClient *client, const char *domain, CzxResult *result) {
    return czx_verify_ex(client, domain, NULL, result);
}

int czx_verify_ex(CzxClient *client, const char *domain, const char *ip, CzxResult *result) {
    if (!client || !result) return CZX_ERR_PARAM;
    memset(result, 0, sizeof(*result));

    char timestamp[24];
    snprintf(timestamp, sizeof(timestamp), "%ld", (long)time(NULL));

    /* 生成 nonce */
    char nonce[17];
    srand((unsigned)time(NULL) ^ (unsigned)rand());
    for (int i = 0; i < 16; i++) snprintf(nonce + i, 2, "%x", rand() % 16);
    nonce[16] = '\0';

    /* 规范化域名 */
    char ndomain[128] = "";
    if (domain && *domain) {
        snprintf(ndomain, sizeof(ndomain), "%s", domain);
    }

    /* 解析 IP：优先使用显式传入的 ip，其次使用 client->bind_ip */
    char nip[64] = "";
    if (ip && *ip) {
        snprintf(nip, sizeof(nip), "%s", ip);
    } else if (client->bind_ip[0]) {
        snprintf(nip, sizeof(nip), "%s", client->bind_ip);
    }

    /* 按 key 升序拼接签名串：app_id,domain,ip,license_key,nonce,timestamp
       空值字段（如未设置 app_id 或未绑定 ip）自动省略，
       与 PHP/Python SDK 及服务端保持一致 */
    char sign_src[1024] = "";
    size_t sl = 0;
    sl = strlen(sign_src);
    if (client->app_id[0]) {
        snprintf(sign_src + sl, sizeof(sign_src) - sl, "app_id=%s&", client->app_id);
        sl = strlen(sign_src);
    }
    snprintf(sign_src + sl, sizeof(sign_src) - sl, "domain=%s&", ndomain);
    sl = strlen(sign_src);
    if (nip[0]) {
        snprintf(sign_src + sl, sizeof(sign_src) - sl, "ip=%s&", nip);
        sl = strlen(sign_src);
    }
    snprintf(sign_src + sl, sizeof(sign_src) - sl,
             "license_key=%s&nonce=%s&timestamp=%s&key=%s",
             client->license_key, nonce, timestamp, client->app_key);

    char sign[33];
    czx_md5_upper(sign_src, sign);

    /* 构造 POST 数据 */
    char eappid[160], edomain[400], eip[200], ek[200], enonce[64], ets[64], esign[64];
    url_encode(client->app_id, eappid, sizeof(eappid));
    url_encode(ndomain, edomain, sizeof(edomain));
    url_encode(nip, eip, sizeof(eip));
    url_encode(client->license_key, ek, sizeof(ek));
    url_encode(nonce, enonce, sizeof(enonce));
    url_encode(timestamp, ets, sizeof(ets));
    url_encode(sign, esign, sizeof(esign));

    char post[2048] = "";
    size_t pl = 0;
    if (client->app_id[0]) {
        snprintf(post + pl, sizeof(post) - pl, "app_id=%s&", eappid);
        pl = strlen(post);
    }
    snprintf(post + pl, sizeof(post) - pl, "license_key=%s&domain=%s&", ek, edomain);
    pl = strlen(post);
    if (nip[0]) {
        snprintf(post + pl, sizeof(post) - pl, "ip=%s&", eip);
        pl = strlen(post);
    }
    snprintf(post + pl, sizeof(post) - pl, "timestamp=%s&nonce=%s&sign=%s", ets, enonce, esign);

    /* 发起请求 */
    CURL *curl = curl_easy_init();
    if (!curl) return CZX_ERR_NETWORK;

    Buffer buf;
    buf.data = (char *)malloc(1);
    buf.size = 0;
    if (buf.data) buf.data[0] = '\0';

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");

    curl_easy_setopt(curl, CURLOPT_URL, client->api_url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)client->timeout);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "ChipZX-Auth-C-SDK/2.0");

    CURLcode rc = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (rc != CURLE_OK) {
        if (buf.data) free(buf.data);
        result->code = CZX_ERR_NETWORK;
        snprintf(result->msg, sizeof(result->msg), "请求失败: %s", curl_easy_strerror(rc));
        return CZX_ERR_NETWORK;
    }
    if (!buf.data) return CZX_ERR_PARSE;

    snprintf(result->raw, sizeof(result->raw), "%s", buf.data);

    /* 解析响应 */
    char tmp[64];
    if (json_get_string(buf.data, "status", tmp, sizeof(tmp)))
        result->status = atoi(tmp);
    json_get_string(buf.data, "msg", result->msg, sizeof(result->msg));
    json_get_string(buf.data, "code", result->code_str, sizeof(result->code_str));

    if (result->status == 1) {
        json_get_in_data(buf.data, "license_key", result->license_key, sizeof(result->license_key));
        json_get_in_data(buf.data, "bind_domain", result->bind_domain, sizeof(result->bind_domain));
        json_get_in_data(buf.data, "expire_at", result->expire_at, sizeof(result->expire_at));
        json_get_in_data(buf.data, "is_forever", tmp, sizeof(tmp));
        result->is_forever = atoi(tmp);
    } else {
        snprintf(result->code_str, sizeof(result->code_str), "%s",
                 result->code_str[0] ? result->code_str : "0000");
    }

    free(buf.data);
    result->code = CZX_OK;
    return CZX_OK;
}

/* ============================================================
 *  通用 POST（供更新检查复用）
 * ============================================================ */
static int czx_post(const char *url, const char *post, char **out, int timeout) {
    CURL *curl = curl_easy_init();
    if (!curl) return CZX_ERR_NETWORK;

    Buffer buf;
    buf.data = (char *)malloc(1);
    buf.size = 0;
    if (buf.data) buf.data[0] = '\0';

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)timeout);
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "ChipZX-Auth-C-SDK/2.0");

    CURLcode rc = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);

    if (rc != CURLE_OK) {
        if (buf.data) free(buf.data);
        return CZX_ERR_NETWORK;
    }
    *out = buf.data;
    return CZX_OK;
}

/* JSON 布尔/数字取值 */
static int czx_truthy(const char *s) {
    if (!s || !*s) return 0;
    if (strcmp(s, "true") == 0) return 1;
    if (strcmp(s, "false") == 0) return 0;
    return atoi(s) != 0;
}

/* ============================================================
 *  语义化版本比较
 * ============================================================ */
int czx_version_compare(const char *a, const char *b) {
    long va[8], vb[8];
    int i, ia = 0, ib = 0;
    char *end;

    for (i = 0; i < 8; i++) { va[i] = 0; vb[i] = 0; }

    const char *p = a ? a : "";
    while (*p && ia < 8) {
        va[ia++] = strtol(p, &end, 10);
        if (end == p) break;
        p = end;
        if (*p == '.') p++;
    }
    p = b ? b : "";
    while (*p && ib < 8) {
        vb[ib++] = strtol(p, &end, 10);
        if (end == p) break;
        p = end;
        if (*p == '.') p++;
    }
    for (i = 0; i < 8; i++) {
        if (va[i] > vb[i]) return 1;
        if (va[i] < vb[i]) return -1;
    }
    return 0;
}

/* ============================================================
 *  在线更新检查
 * ============================================================ */
int czx_check_update(CzxClient *client, const char *current_version,
                     const char *domain, CzxUpdate *result) {
    if (!client || !result) return CZX_ERR_PARAM;
    memset(result, 0, sizeof(*result));
    snprintf(result->current_version, sizeof(result->current_version), "%s",
             current_version ? current_version : "0.0.0");

    char timestamp[24];
    snprintf(timestamp, sizeof(timestamp), "%ld", (long)time(NULL));

    char nonce[17];
    srand((unsigned)time(NULL) ^ (unsigned)rand());
    for (int i = 0; i < 16; i++) snprintf(nonce + i, 2, "%x", rand() % 16);
    nonce[16] = '\0';

    char ndomain[128] = "";
    if (domain && *domain) snprintf(ndomain, sizeof(ndomain), "%s", domain);

    /* 签名串（升序）：app_id,current_version,domain,license_key,nonce,timestamp */
    char sign_src[1024] = "";
    size_t sl = 0;
    if (client->app_id[0]) {
        snprintf(sign_src + sl, sizeof(sign_src) - sl, "app_id=%s&", client->app_id);
        sl = strlen(sign_src);
    }
    snprintf(sign_src + sl, sizeof(sign_src) - sl, "current_version=%s&", result->current_version);
    sl = strlen(sign_src);
    if (ndomain[0]) {
        snprintf(sign_src + sl, sizeof(sign_src) - sl, "domain=%s&", ndomain);
        sl = strlen(sign_src);
    }
    if (client->license_key[0]) {
        snprintf(sign_src + sl, sizeof(sign_src) - sl, "license_key=%s&", client->license_key);
        sl = strlen(sign_src);
    }
    snprintf(sign_src + sl, sizeof(sign_src) - sl, "nonce=%s&timestamp=%s&key=%s",
             nonce, timestamp, client->app_key);

    char sign[33];
    czx_md5_upper(sign_src, sign);

    char eappid[160], ecur[64], edomain[400], ek[200], enonce[64], ets[64], esign[64];
    url_encode(client->app_id, eappid, sizeof(eappid));
    url_encode(result->current_version, ecur, sizeof(ecur));
    url_encode(ndomain, edomain, sizeof(edomain));
    url_encode(client->license_key, ek, sizeof(ek));
    url_encode(nonce, enonce, sizeof(enonce));
    url_encode(timestamp, ets, sizeof(ets));
    url_encode(sign, esign, sizeof(esign));

    char post[2048] = "";
    size_t pl = 0;
    if (client->app_id[0]) {
        snprintf(post + pl, sizeof(post) - pl, "app_id=%s&", eappid);
        pl = strlen(post);
    }
    snprintf(post + pl, sizeof(post) - pl, "current_version=%s&", ecur);
    pl = strlen(post);
    if (ndomain[0]) {
        snprintf(post + pl, sizeof(post) - pl, "domain=%s&", edomain);
        pl = strlen(post);
    }
    if (client->license_key[0]) {
        snprintf(post + pl, sizeof(post) - pl, "license_key=%s&", ek);
        pl = strlen(post);
    }
    snprintf(post + pl, sizeof(post) - pl, "timestamp=%s&nonce=%s&sign=%s", ets, enonce, esign);

    const char *url = client->update_url[0] ? client->update_url
                                             : "https://auth.chipzx.com/api/update.php";
    char *resp = NULL;
    int rc = czx_post(url, post, &resp, client->timeout);
    if (rc != CZX_OK) {
        snprintf(result->msg, sizeof(result->msg), "请求失败");
        return rc;
    }
    if (!resp) return CZX_ERR_PARSE;

    snprintf(result->raw, sizeof(result->raw), "%s", resp);

    char tmp[64] = "";
    json_get_string(resp, "msg", result->msg, sizeof(result->msg));
    json_get_in_data(resp, "has_update", tmp, sizeof(tmp));
    result->has_update = czx_truthy(tmp);

    if (result->has_update) {
        json_get_in_data(resp, "version", result->version, sizeof(result->version));
        json_get_in_data(resp, "title", result->title, sizeof(result->title));
        json_get_in_data(resp, "changelog", result->changelog, sizeof(result->changelog));
        json_get_in_data(resp, "download_url", result->download_url, sizeof(result->download_url));
        json_get_in_data(resp, "file_hash", result->file_hash, sizeof(result->file_hash));
        json_get_in_data(resp, "published_at", result->published_at, sizeof(result->published_at));
        json_get_in_data(resp, "force", tmp, sizeof(tmp));
        result->force = czx_truthy(tmp);
    } else {
        json_get_in_data(resp, "version", result->version, sizeof(result->version));
        json_get_in_data(resp, "published_at", result->published_at, sizeof(result->published_at));
    }

    free(resp);
    return CZX_OK;
}
