/*
 * test_json_serialization.c - Unit tests for JSON serialization
 */

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <string.h>
#include <stdio.h>
#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_time.h>
#include <apr_lib.h>
#include <apr_general.h>

typedef struct {
    char *data;
    size_t len;
    size_t cap;
    apr_pool_t *pool;
} testbuf_t;

static void testbuf_init(testbuf_t *buf, apr_pool_t *pool, size_t initial_capacity)
{
    buf->pool = pool;
    buf->cap = initial_capacity;
    buf->len = 0;
    buf->data = apr_palloc(pool, initial_capacity);
    buf->data[0] = '\0';
}

static void testbuf_append(testbuf_t *buf, const char *str, size_t len)
{
    if (str == NULL) {
        return;
    }

    if (len == (size_t)-1) {
        len = strlen(str);
    }

    if (buf->len + len + 1 > buf->cap) {
        size_t new_cap = (buf->len + len + 1) * 2;
        char *new_data = apr_palloc(buf->pool, new_cap);
        memcpy(new_data, buf->data, buf->len + 1);  /* Copy including null terminator */
        buf->data = new_data;
        buf->cap = new_cap;
    }

    memcpy(buf->data + buf->len, str, len);
    buf->len += len;
    buf->data[buf->len] = '\0';
}

static void testbuf_append_char(testbuf_t *buf, char c)
{
    if (buf->len + 2 > buf->cap) {
        size_t new_cap = (buf->cap * 2);
        char *new_data = apr_palloc(buf->pool, new_cap);
        memcpy(new_data, buf->data, buf->len + 1);
        buf->data = new_data;
        buf->cap = new_cap;
    }

    buf->data[buf->len++] = c;
    buf->data[buf->len] = '\0';
}

/* Mock JSON string escaping function for testing */
static void append_json_string(testbuf_t *buf, const char *str)
{
    if (str == NULL) {
        return;
    }

    for (const char *p = str; *p; p++) {
        char c = *p;
        switch (c) {
            case '"':  testbuf_append(buf, "\\\"", 2); break;
            case '\\': testbuf_append(buf, "\\\\", 2); break;
            case '\b': testbuf_append(buf, "\\b", 2); break;
            case '\f': testbuf_append(buf, "\\f", 2); break;
            case '\n': testbuf_append(buf, "\\n", 2); break;
            case '\r': testbuf_append(buf, "\\r", 2); break;
            case '\t': testbuf_append(buf, "\\t", 2); break;
            default:
                if ((unsigned char)c < 0x20) {
                    char unicode[8];
                    apr_snprintf(unicode, sizeof(unicode), "\\u%04x", (unsigned char)c);
                    testbuf_append(buf, unicode, (size_t)-1);
                } else {
                    testbuf_append_char(buf, c);
                }
                break;
        }
    }
}

/* Test: Empty string */
static void test_json_escape_empty_string(void **state)
{
    apr_pool_t *pool;
    testbuf_t buf;
    (void)state;

    apr_pool_create(&pool, NULL);
    testbuf_init(&buf, pool, 256);

    append_json_string(&buf, "");

    assert_string_equal(buf.data, "");

    apr_pool_destroy(pool);
}

/* Test: Simple string without special characters */
static void test_json_escape_simple_string(void **state)
{
    apr_pool_t *pool;
    testbuf_t buf;
    (void)state;

    apr_pool_create(&pool, NULL);
    testbuf_init(&buf, pool, 256);

    append_json_string(&buf, "hello world");

    assert_string_equal(buf.data, "hello world");

    apr_pool_destroy(pool);
}

/* Test: String with double quotes */
static void test_json_escape_quotes(void **state)
{
    apr_pool_t *pool;
    testbuf_t buf;
    (void)state;

    apr_pool_create(&pool, NULL);
    testbuf_init(&buf, pool, 256);

    append_json_string(&buf, "hello \"world\"");

    assert_string_equal(buf.data, "hello \\\"world\\\"");

    apr_pool_destroy(pool);
}

/* Test: String with backslashes */
static void test_json_escape_backslashes(void **state)
{
    apr_pool_t *pool;
    testbuf_t buf;
    (void)state;

    apr_pool_create(&pool, NULL);
    testbuf_init(&buf, pool, 256);

    append_json_string(&buf, "path\\to\\file");

    assert_string_equal(buf.data, "path\\\\to\\\\file");

    apr_pool_destroy(pool);
}

/* Test: String with newlines and tabs */
static void test_json_escape_newlines_tabs(void **state)
{
    apr_pool_t *pool;
    testbuf_t buf;
    (void)state;

    apr_pool_create(&pool, NULL);
    testbuf_init(&buf, pool, 256);

    append_json_string(&buf, "line1\nline2\ttab");

    assert_string_equal(buf.data, "line1\\nline2\\ttab");

    apr_pool_destroy(pool);
}

/* Test: String with control characters */
static void test_json_escape_control_chars(void **state)
{
    apr_pool_t *pool;
    testbuf_t buf;
    (void)state;

    apr_pool_create(&pool, NULL);
    testbuf_init(&buf, pool, 256);

    /* Test with bell character (0x07) - use octal literal */
    append_json_string(&buf, "test\007bell");

    /* Should contain unicode escape for bell (0x07) */
    assert_true(strstr(buf.data, "\\u0007") != NULL);

    apr_pool_destroy(pool);
}

/* Test: NULL string */
static void test_json_escape_null_string(void **state)
{
    apr_pool_t *pool;
    testbuf_t buf;
    (void)state;

    apr_pool_create(&pool, NULL);
    testbuf_init(&buf, pool, 256);

    append_json_string(&buf, NULL);

    assert_string_equal(buf.data, "");

    apr_pool_destroy(pool);
}

/* Test: Complex user agent string */
static void test_json_escape_user_agent(void **state)
{
    apr_pool_t *pool;
    testbuf_t buf;
    const char *ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \"Test\"";
    (void)state;

    apr_pool_create(&pool, NULL);
    testbuf_init(&buf, pool, 512);

    append_json_string(&buf, ua);

    assert_true(strstr(buf.data, "\\\"Test\\\"") != NULL);

    apr_pool_destroy(pool);
}

static int group_setup(void **state)
{
    (void)state;
    return apr_initialize();
}

static int group_teardown(void **state)
{
    (void)state;
    apr_terminate();
    return 0;
}

int main(void)
{
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(test_json_escape_empty_string),
        cmocka_unit_test(test_json_escape_simple_string),
        cmocka_unit_test(test_json_escape_quotes),
        cmocka_unit_test(test_json_escape_backslashes),
        cmocka_unit_test(test_json_escape_newlines_tabs),
        cmocka_unit_test(test_json_escape_control_chars),
        cmocka_unit_test(test_json_escape_null_string),
        cmocka_unit_test(test_json_escape_user_agent),
    };

    return cmocka_run_group_tests(tests, group_setup, group_teardown);
}
