/* =============================================================================
 * jsonbody.c - 요청 본문 생성 (경계 검사 포함)
 * ===========================================================================*/
#include "jsonbody.h"
#include <string.h>

/* ── 경계 검사 어펜더 ───────────────────────────────────────────────────── */
typedef struct {
    char  *p;
    size_t cap;   /* NUL 자리 포함 전체 버퍼 크기 */
    size_t len;   /* 현재 길이(NUL 제외) */
    int    ok;    /* 0 이면 오버플로 발생 */
} jb_buf;

static void jb_init(jb_buf *b, char *out, size_t cap)
{
    b->p = out;
    b->cap = cap;
    b->len = 0;
    b->ok = (cap > 0);
    if (b->ok) b->p[0] = '\0';
}

static void jb_putc(jb_buf *b, char c)
{
    if (!b->ok) return;
    if (b->len + 1 >= b->cap) { b->ok = 0; return; }   /* +1: NUL 자리 보존 */
    b->p[b->len++] = c;
    b->p[b->len] = '\0';
}

static void jb_raw(jb_buf *b, const char *s)
{
    while (*s) jb_putc(b, *s++);
}

/* JSON 문자열 값(따옴표 없이) 이스케이프하여 추가.
 * ASCII 제어문자(<0x20)는 \u00XX, " 와 \ 는 백슬래시 이스케이프,
 * 그 외(멀티바이트 UTF-8 포함)는 그대로. '/' 는 이스케이프하지 않는다. */
static void jb_estr(jb_buf *b, const char *s)
{
    static const char hexd[] = "0123456789abcdef";
    const unsigned char *u = (const unsigned char *)s;
    while (*u) {
        unsigned char c = *u++;
        if (c == '"') { jb_putc(b, '\\'); jb_putc(b, '"'); }
        else if (c == '\\') { jb_putc(b, '\\'); jb_putc(b, '\\'); }
        else if (c < 0x20) {
            jb_putc(b, '\\'); jb_putc(b, 'u');
            jb_putc(b, '0'); jb_putc(b, '0');
            jb_putc(b, hexd[(c >> 4) & 0x0F]);
            jb_putc(b, hexd[c & 0x0F]);
        } else {
            jb_putc(b, (char)c);
        }
    }
}

/* 부호 없는 정수 추가 */
static void jb_u32(jb_buf *b, uint32_t v)
{
    char tmp[10];
    int n = 0;
    if (v == 0) { jb_putc(b, '0'); return; }
    while (v > 0 && n < (int)sizeof(tmp)) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; }
    while (n > 0) jb_putc(b, tmp[--n]);
}

/* 부호 있는 정수 추가 */
static void jb_i32(jb_buf *b, int v)
{
    if (v < 0) { jb_putc(b, '-'); jb_u32(b, (uint32_t)(-(long)v)); }
    else jb_u32(b, (uint32_t)v);
}

/* 소수 2자리 고정 포맷 (locale·newlib float-printf 비의존, 결정적 반올림).
 * 예: 24.0 -> "24.00", 48.5 -> "48.50", -3.245 -> "-3.25"
 * Python 레퍼런스(reference.py)와 바이트 동일해야 한다. */
static void jb_fixed2(jb_buf *b, double v)
{
    int neg = 0;
    if (v < 0) { neg = 1; v = -v; }

    /* 반올림하여 1/100 단위 정수로 (round half up) */
    long long scaled = (long long)(v * 100.0 + 0.5);
    long long ip = scaled / 100;
    int fp = (int)(scaled % 100);

    if (neg && scaled != 0) jb_putc(b, '-');

    /* 정수부 */
    if (ip == 0) {
        jb_putc(b, '0');
    } else {
        char tmp[20];
        int n = 0;
        while (ip > 0 && n < (int)sizeof(tmp)) { tmp[n++] = (char)('0' + (ip % 10)); ip /= 10; }
        while (n > 0) jb_putc(b, tmp[--n]);
    }
    /* 소수부 (항상 2자리) */
    jb_putc(b, '.');
    jb_putc(b, (char)('0' + (fp / 10)));
    jb_putc(b, (char)('0' + (fp % 10)));
}

/* 키 헬퍼: ,"key": */
static void jb_key(jb_buf *b, int first, const char *key)
{
    if (!first) jb_putc(b, ',');
    jb_putc(b, '"');
    jb_raw(b, key);
    jb_putc(b, '"');
    jb_putc(b, ':');
}

static void jb_str_field(jb_buf *b, int first, const char *key, const char *val)
{
    jb_key(b, first, key);
    jb_putc(b, '"');
    jb_estr(b, val);
    jb_putc(b, '"');
}

int jb_sht30_event(char *out, size_t cap,
                   const char *device_id, const char *device_location,
                   int sensor_id, const char *sensor_name,
                   const char *event_type, uint32_t timestamp,
                   double temperature_c, double humidity_percent,
                   const char *metric_status, const char *app_version)
{
    jb_buf b;
    jb_init(&b, out, cap);

    jb_putc(&b, '{');
    jb_str_field(&b, 1, "device_id", device_id);
    jb_str_field(&b, 0, "device_location", device_location);
    jb_key(&b, 0, "sensor_id");   jb_i32(&b, sensor_id);
    jb_str_field(&b, 0, "sensor_name", sensor_name);
    jb_str_field(&b, 0, "event_type", event_type);
    jb_key(&b, 0, "timestamp");   jb_u32(&b, timestamp);
    jb_str_field(&b, 0, "metric_type", "sht30");
    jb_key(&b, 0, "temperature_c");   jb_fixed2(&b, temperature_c);
    jb_key(&b, 0, "humidity_percent"); jb_fixed2(&b, humidity_percent);
    jb_str_field(&b, 0, "metric_status", metric_status);
    jb_str_field(&b, 0, "app_version", app_version);
    jb_putc(&b, '}');

    return b.ok ? (int)b.len : -1;
}
