/*
 * txcore.c - TxCore 공통 프레임워크 구현
 */
#include "txcore.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define TX_MAX_SERVICES 512

typedef struct {
    char          name[TX_KEY_LEN];
    tx_service_fn fn;
} tx_entry_t;

static tx_entry_t g_registry[TX_MAX_SERVICES];
static int        g_registry_count = 0;

static int  g_loglevel = TX_LOG_INFO;
static long g_xid_seq  = 0;
static long g_cur_xid  = 0;

static const char *level_name(int level)
{
    switch (level) {
    case TX_LOG_DEBUG: return "DEBUG";
    case TX_LOG_INFO:  return "INFO ";
    case TX_LOG_WARN:  return "WARN ";
    case TX_LOG_ERROR: return "ERROR";
    default:           return "?????";
    }
}

void tx_set_loglevel(int level) { g_loglevel = level; }

void tx_log(int level, const char *fmt, ...)
{
    va_list ap;
    time_t     now;
    struct tm  tmv;
    char       ts[20];

    if (level < g_loglevel)
        return;

    now = time(NULL);
    localtime_r(&now, &tmv);
    strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", &tmv);

    fprintf(stderr, "[%s] %s ", ts, level_name(level));
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fputc('\n', stderr);
}

const char *tx_strerror(int rc)
{
    switch (rc) {
    case TX_OK:      return "정상";
    case TX_FAIL:    return "일반 실패";
    case TX_ENOENT:  return "서비스 미등록";
    case TX_EINVAL:  return "파라미터 오류";
    case TX_EDB:     return "DB 오류";
    case TX_ENOMEM:  return "자원 부족";
    case TX_ENOKEY:  return "버퍼 키 없음";
    default:         return "알 수 없는 오류";
    }
}

TXBUF *tx_buf_alloc(void) { return (TXBUF *)calloc(1, sizeof(TXBUF)); }
void   tx_buf_free(TXBUF *buf) { if (buf) free(buf); }
void   tx_buf_reset(TXBUF *buf) { if (buf) memset(buf, 0, sizeof(*buf)); }

static TXFIELD *find_slot(TXBUF *buf, const char *key)
{
    int i;
    for (i = 0; i < TX_MAX_SLOTS; i++) {
        if (buf->slots[i].used &&
            strncmp(buf->slots[i].key, key, TX_KEY_LEN) == 0)
            return &buf->slots[i];
    }
    return NULL;
}

static TXFIELD *alloc_slot(TXBUF *buf, const char *key)
{
    int i;
    TXFIELD *f = find_slot(buf, key);
    if (f)
        return f;
    for (i = 0; i < TX_MAX_SLOTS; i++) {
        if (!buf->slots[i].used) {
            f = &buf->slots[i];
            memset(f, 0, sizeof(*f));
            strncpy(f->key, key, TX_KEY_LEN - 1);
            f->used = 1;
            buf->count++;
            return f;
        }
    }
    return NULL;
}

int tx_buf_set(TXBUF *buf, const char *key, const char *val, int len)
{
    TXFIELD *f;
    if (!buf || !key || !val)
        return TX_EINVAL;
    if (len < 0 || len >= TX_VAL_LEN)
        return TX_ENOMEM;
    f = alloc_slot(buf, key);
    if (!f)
        return TX_ENOMEM;
    memcpy(f->val, val, len);
    f->val[len] = '\0';
    f->len = len;
    return TX_OK;
}

int tx_buf_sets(TXBUF *buf, const char *key, const char *val)
{
    if (!val)
        return TX_EINVAL;
    return tx_buf_set(buf, key, val, (int)strlen(val));
}

int tx_buf_setlong(TXBUF *buf, const char *key, long val)
{
    char tmp[32];
    snprintf(tmp, sizeof(tmp), "%ld", val);
    return tx_buf_set(buf, key, tmp, (int)strlen(tmp));
}

int tx_buf_get(TXBUF *buf, const char *key, char *out, int outlen)
{
    TXFIELD *f;
    if (!buf || !key || !out || outlen <= 0)
        return TX_EINVAL;
    f = find_slot(buf, key);
    if (!f)
        return TX_ENOKEY;
    if (f->len >= outlen)
        return TX_ENOMEM;
    memcpy(out, f->val, f->len);
    out[f->len] = '\0';
    return TX_OK;
}

int tx_buf_getlong(TXBUF *buf, const char *key, long *out)
{
    TXFIELD *f;
    char *end;
    long v;
    if (!buf || !key || !out)
        return TX_EINVAL;
    f = find_slot(buf, key);
    if (!f)
        return TX_ENOKEY;
    v = strtol(f->val, &end, 10);
    if (end == f->val)
        return TX_EINVAL;
    *out = v;
    return TX_OK;
}

int tx_register(const char *name, tx_service_fn fn)
{
    int i;
    if (!name || !fn)
        return TX_EINVAL;
    if (g_registry_count >= TX_MAX_SERVICES)
        return TX_ENOMEM;
    for (i = 0; i < g_registry_count; i++) {
        if (strncmp(g_registry[i].name, name, TX_KEY_LEN) == 0) {
            g_registry[i].fn = fn;
            return TX_OK;
        }
    }
    strncpy(g_registry[g_registry_count].name, name, TX_KEY_LEN - 1);
    g_registry[g_registry_count].fn = fn;
    g_registry_count++;
    tx_log(TX_LOG_DEBUG, "서비스 등록: %s", name);
    return TX_OK;
}

int tx_service_count(void) { return g_registry_count; }

static tx_service_fn lookup(const char *name)
{
    int i;
    for (i = 0; i < g_registry_count; i++) {
        if (strncmp(g_registry[i].name, name, TX_KEY_LEN) == 0)
            return g_registry[i].fn;
    }
    return NULL;
}

int tx_call(const char *svc, TXBUF *in, TXBUF *out)
{
    tx_service_fn fn;
    TXSVCINFO     ctx;
    if (!svc || !in || !out)
        return TX_EINVAL;
    fn = lookup(svc);
    if (!fn) {
        tx_log(TX_LOG_ERROR, "tx_call: 서비스 미등록 svc=%s", svc);
        return TX_ENOENT;
    }
    memset(&ctx, 0, sizeof(ctx));
    strncpy(ctx.name, svc, TX_KEY_LEN - 1);
    ctx.in    = in;
    ctx.out   = out;
    ctx.rcode = TX_OK;
    ctx.xid   = g_cur_xid;
    fn(&ctx);
    return ctx.rcode;
}

int tx_return(TXSVCINFO *ctx, int rc, TXBUF *out)
{
    if (!ctx)
        return TX_EINVAL;
    ctx->rcode = rc;
    if (out && ctx->out && out != ctx->out)
        memcpy(ctx->out, out, sizeof(TXBUF));
    return rc;
}

int tx_begin(void)
{
    if (g_cur_xid != 0) {
        tx_log(TX_LOG_WARN, "tx_begin: 이미 진행중 xid=%ld", g_cur_xid);
        return TX_FAIL;
    }
    g_cur_xid = ++g_xid_seq;
    tx_log(TX_LOG_INFO, "tx_begin  xid=%ld", g_cur_xid);
    return TX_OK;
}

int tx_commit(void)
{
    if (g_cur_xid == 0) {
        tx_log(TX_LOG_WARN, "tx_commit: 트랜잭션 없음");
        return TX_FAIL;
    }
    tx_log(TX_LOG_INFO, "tx_commit xid=%ld", g_cur_xid);
    g_cur_xid = 0;
    return TX_OK;
}

int tx_abort(void)
{
    if (g_cur_xid == 0) {
        tx_log(TX_LOG_WARN, "tx_abort: 트랜잭션 없음");
        return TX_FAIL;
    }
    tx_log(TX_LOG_WARN, "tx_abort  xid=%ld", g_cur_xid);
    g_cur_xid = 0;
    return TX_OK;
}

long tx_current_xid(void) { return g_cur_xid; }
