/*
 * acq_httpgw.c - 경량 HTTP -> tpcall 게이트웨이 (레거시 Webtier 포지션).
 *
 * 브라우저 업무화면(app/ui)과 Enduro/X TP 모니터 사이의 브릿지:
 *   GET /api/call?svc=ACQUIRE&_tx=1&T_MERCHANT=M0001&T_AMOUNT=1000000
 *     -> UBF 버퍼 구성(CBchg, 문자열->필드타입 자동변환)
 *     -> (옵션 _tx=1: tpbegin 글로벌 XA) tpcall(svc) (tpcommit/tpabort)
 *     -> 응답 UBF 전 필드를 JSON 으로 직렬화해 반환
 *   GET /              -> /app/ui/index.html (업무화면)
 *   GET /ui/<file>     -> 정적 파일
 *
 * 의존성: libatmiclt + libubf 뿐 (외부 HTTP 라이브러리 없음, 단일 스레드 accept 루프).
 * 실제 레거시의 화면(Xplatform)->Webtier/WebLogic->Tuxedo 구조에서 Webtier 대응.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#include <atmi.h>
#include <ubf.h>
#include <userlog.h>
#include <acq.fd.h>
#include <strings.h>
#include <libpq-fe.h>

#define GW_PORT 8090
#define REQ_MAX 16384
#define VAL_MAX 2048

/* ---- acq.fd 필드 이름 <-> ID 정적 매핑 (필드테이블 env 불요) ---------------- */
typedef struct { const char *name; BFLDID id; } fld_map_t;
static const fld_map_t FMAP[] = {
    {"T_MERCHANT", T_MERCHANT}, {"T_AMOUNT", T_AMOUNT}, {"T_FEE", T_FEE},
    {"T_NET", T_NET}, {"T_STATUS", T_STATUS}, {"T_PURCHASE_ID", T_PURCHASE_ID},
    {"T_BIZDATE", T_BIZDATE}, {"T_SETTLE_ID", T_SETTLE_ID}, {"T_MSG", T_MSG},
    {"T_CANCEL_ID", T_CANCEL_ID}, {"T_INSTALL_N", T_INSTALL_N}, {"T_TAX", T_TAX},
    {"T_FX_AMT", T_FX_AMT}, {"T_COUNT", T_COUNT}, {"T_REASON", T_REASON},
    {"T_CATEGORY", T_CATEGORY}, {"T_ISSUER", T_ISSUER}, {"T_CHANNEL", T_CHANNEL},
    {"T_CCY", T_CCY}, {"T_ADJ", T_ADJ}, {"T_DELTA", T_DELTA},
    {"T_SVCNAME", T_SVCNAME}, {"T_RC", T_RC}, {"T_GROSS", T_GROSS},
    {"T_DOCNO", T_DOCNO},
    {"T_ARG1", T_ARG1}, {"T_ARG2", T_ARG2}, {"T_ARG3", T_ARG3}, {"T_ARG4", T_ARG4},
    {"T_ARG5", T_ARG5}, {"T_ARG6", T_ARG6}, {"T_ARG7", T_ARG7}, {"T_ARG8", T_ARG8},
    {"T_STR1", T_STR1}, {"T_STR2", T_STR2}, {"T_STR3", T_STR3}, {"T_STR4", T_STR4},
    {"T_STR5", T_STR5}, {"T_STR6", T_STR6}, {"T_STR7", T_STR7}, {"T_STR8", T_STR8},
    {"T_AMT1", T_AMT1}, {"T_AMT2", T_AMT2}, {"T_AMT3", T_AMT3}, {"T_AMT4", T_AMT4},
    {"T_KEY1", T_KEY1}, {"T_KEY2", T_KEY2}, {"T_ID1", T_ID1}, {"T_ID2", T_ID2},
};
#define NFLD (sizeof(FMAP)/sizeof(FMAP[0]))

static BFLDID fld_by_name(const char *n)
{
    size_t i;
    for (i = 0; i < NFLD; i++)
        if (0 == strcmp(FMAP[i].name, n)) return FMAP[i].id;
    return BBADFLDID;
}
static const char *fld_name(BFLDID id)
{
    size_t i;
    for (i = 0; i < NFLD; i++)
        if (FMAP[i].id == id) return FMAP[i].name;
    return NULL;
}

/* ---- 유틸 ------------------------------------------------------------------ */
static void url_decode(char *s)
{
    char *o = s;
    while (*s) {
        if (*s == '+') { *o++ = ' '; s++; }
        else if (*s == '%' && s[1] && s[2]) {
            int hi = (s[1] >= 'a') ? s[1]-'a'+10 : (s[1] >= 'A') ? s[1]-'A'+10 : s[1]-'0';
            int lo = (s[2] >= 'a') ? s[2]-'a'+10 : (s[2] >= 'A') ? s[2]-'A'+10 : s[2]-'0';
            *o++ = (char)(hi*16+lo); s += 3;
        } else *o++ = *s++;
    }
    *o = '\0';
}

static void json_escape(const char *in, char *out, size_t cap)
{
    size_t o = 0;
    for (; *in && o + 6 < cap; in++) {
        unsigned char c = (unsigned char)*in;
        if (c == '"' || c == '\\') { out[o++]='\\'; out[o++]=c; }
        else if (c == '\n') { out[o++]='\\'; out[o++]='n'; }
        else if (c == '\r') { out[o++]='\\'; out[o++]='r'; }
        else if (c == '\t') { out[o++]='\\'; out[o++]='t'; }
        else if (c < 0x20) { o += snprintf(out+o, cap-o, "\\u%04x", c); }
        else out[o++] = c;
    }
    out[o] = '\0';
}

static void http_send(int fd, int code, const char *ctype, const char *body, long blen)
{
    char hdr[512];
    const char *msg = (code == 200) ? "OK" : (code == 404) ? "Not Found" : "Bad Request";
    int hl = snprintf(hdr, sizeof(hdr),
        "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %ld\r\n"
        "Cache-Control: no-store\r\nConnection: close\r\n\r\n", code, msg, ctype, blen);
    if (write(fd, hdr, hl) < 0) return;
    if (blen > 0 && write(fd, body, blen) < 0) return;
}

/* ---- /api/call ------------------------------------------------------------- */
static void handle_api_call(int fd, char *query)
{
    char svc[64] = "";
    int use_tx = 0, in_tx = 0;
    UBFH *b = NULL;
    static char out[262144];
    long olen = 0;
    char val[VAL_MAX], esc[VAL_MAX*2];
    char *tok, *save = NULL;

    b = (UBFH *)tpalloc("UBF", NULL, 16384);
    if (!b) { http_send(fd, 500, "application/json", "{\"ok\":false,\"error\":\"tpalloc\"}", 30); return; }

    /* 쿼리 파라미터 -> UBF (CBchg: 문자열 -> 필드 타입 자동 변환) */
    for (tok = strtok_r(query, "&", &save); tok; tok = strtok_r(NULL, "&", &save)) {
        char *eq = strchr(tok, '=');
        if (!eq) continue;
        *eq = '\0';
        strncpy(val, eq + 1, sizeof(val) - 1); val[sizeof(val)-1] = '\0';
        url_decode(tok); url_decode(val);
        if (0 == strcmp(tok, "svc")) { strncpy(svc, val, sizeof(svc)-1); continue; }
        if (0 == strcmp(tok, "_tx")) { use_tx = atoi(val); continue; }
        BFLDID id = fld_by_name(tok);
        if (id == BBADFLDID) continue;
        BFLDOCC occ = Boccur(b, id);            /* 같은 필드 반복 = 다음 occurrence */
        if (CBchg(b, id, occ, val, 0, BFLD_STRING) < 0)
            userlog("acq_httpgw: CBchg(%s) 실패: %s", tok, Bstrerror(Berror));
    }
    if (!svc[0]) {
        const char *e = "{\"ok\":false,\"error\":\"svc required\"}";
        tpfree((char *)b); http_send(fd, 400, "application/json", e, (long)strlen(e)); return;
    }

    if (use_tx) {
        if (tpbegin(60, 0) < 0) {
            snprintf(out, sizeof(out), "{\"ok\":false,\"svc\":\"%s\",\"error\":\"tpbegin: %s\"}", svc, tpstrerror(tperrno));
            tpfree((char *)b); http_send(fd, 500, "application/json", out, (long)strlen(out)); return;
        }
        in_tx = 1;
    }

    long rlen = 0;
    int rc = tpcall(svc, (char *)b, 0L, (char **)&b, &rlen, 0L);

    if (in_tx) {
        if (rc >= 0) { if (tpcommit(0) < 0) rc = -1; }
        else tpabort(0);
    }

    if (rc < 0) {
        json_escape(tpstrerror(tperrno), esc, sizeof(esc));
        olen = snprintf(out, sizeof(out), "{\"ok\":false,\"svc\":\"%s\",\"error\":\"%s\"}", svc, esc);
        http_send(fd, 200, "application/json", out, olen);
        tpfree((char *)b);
        return;
    }

    /* 응답 UBF -> JSON */
    olen = snprintf(out, sizeof(out), "{\"ok\":true,\"svc\":\"%s\",\"tx\":%d,\"fields\":{", svc, use_tx);
    BFLDID id = BFIRSTFLDID; BFLDOCC occ;
    int first = 1;
    while (1 == Bnext(b, &id, &occ, NULL, NULL)) {
        BFLDLEN vl = sizeof(val);
        if (CBget(b, id, occ, val, &vl, BFLD_STRING) < 0) continue;
        json_escape(val, esc, sizeof(esc));
        const char *nm = fld_name(id);
        char nbuf[32];
        if (!nm) { snprintf(nbuf, sizeof(nbuf), "F_%d", (int)id); nm = nbuf; }
        olen += snprintf(out + olen, sizeof(out) - olen, "%s\"%s%s\":\"%s\"",
                         first ? "" : ",", nm, occ > 0 ? "#" : "", esc);
        first = 0;
        if ((size_t)olen > sizeof(out) - 4096) break;
    }
    olen += snprintf(out + olen, sizeof(out) - olen, "}}");
    http_send(fd, 200, "application/json", out, olen);
    tpfree((char *)b);
}


/* ---- /api/query (읽기전용 조회 — 화면 그리드용, MIS/리포팅 경로) ------------- */
static PGconn *g_pg = NULL;

static void handle_api_query(int fd, char *query)
{
    static char out[524288];
    static char sql[8192];
    char esc[4096];
    char *tok, *save = NULL;
    sql[0] = '\0';

    for (tok = strtok_r(query, "&", &save); tok; tok = strtok_r(NULL, "&", &save)) {
        char *eq = strchr(tok, '=');
        if (!eq) continue;
        *eq = '\0';
        if (0 == strcmp(tok, "q")) {
            strncpy(sql, eq + 1, sizeof(sql) - 1); sql[sizeof(sql)-1] = '\0';
            url_decode(sql);
        }
    }
    char *p = sql;
    while (*p == ' ' || *p == '\n' || *p == '\t') p++;
    if (g_pg && PQstatus(g_pg) != CONNECTION_OK) PQreset(g_pg);
    if (!g_pg || PQstatus(g_pg) != CONNECTION_OK ||
        strncasecmp(p, "select", 6) != 0 || strchr(p, ';')) {
        const char *e = "{\"ok\":false,\"error\":\"select only / no db\"}";
        http_send(fd, 400, "application/json", e, (long)strlen(e)); return;
    }
    PGresult *res = PQexec(g_pg, p);
    if (PQresultStatus(res) != PGRES_TUPLES_OK) {
        json_escape(PQerrorMessage(g_pg), esc, sizeof(esc));
        long n = snprintf(out, sizeof(out), "{\"ok\":false,\"error\":\"%s\"}", esc);
        http_send(fd, 200, "application/json", out, n); PQclear(res); return;
    }
    int nc = PQnfields(res), nr = PQntuples(res);
    if (nr > 500) nr = 500;
    long o = snprintf(out, sizeof(out), "{\"ok\":true,\"cols\":[");
    int c, r;
    for (c = 0; c < nc; c++) {
        json_escape(PQfname(res, c), esc, sizeof(esc));
        o += snprintf(out + o, sizeof(out) - o, "%s\"%s\"", c ? "," : "", esc);
    }
    o += snprintf(out + o, sizeof(out) - o, "],\"rows\":[");
    for (r = 0; r < nr && (size_t)o < sizeof(out) - 8192; r++) {
        o += snprintf(out + o, sizeof(out) - o, "%s[", r ? "," : "");
        for (c = 0; c < nc; c++) {
            json_escape(PQgetvalue(res, r, c), esc, sizeof(esc));
            o += snprintf(out + o, sizeof(out) - o, "%s\"%s\"", c ? "," : "", esc);
        }
        o += snprintf(out + o, sizeof(out) - o, "]");
    }
    o += snprintf(out + o, sizeof(out) - o, "]}");
    http_send(fd, 200, "application/json", out, o);
    PQclear(res);
}

/* ---- 정적 파일 ------------------------------------------------------------- */
static void handle_static(int fd, const char *path)
{
    char full[512];
    if (strstr(path, "..")) { http_send(fd, 400, "text/plain", "bad path", 8); return; }
    if (0 == strcmp(path, "/") || 0 == strcmp(path, "/index.html"))
        snprintf(full, sizeof(full), "/app/ui/index.html");
    else if (0 == strncmp(path, "/ui/", 4))
        snprintf(full, sizeof(full), "/app/ui/%s", path + 4);
    else { http_send(fd, 404, "text/plain", "not found", 9); return; }

    FILE *fp = fopen(full, "rb");
    if (!fp) { http_send(fd, 404, "text/plain", "not found", 9); return; }
    fseek(fp, 0, SEEK_END); long sz = ftell(fp); fseek(fp, 0, SEEK_SET);
    char *buf = malloc(sz > 0 ? sz : 1);
    if (fread(buf, 1, sz, fp) != (size_t)sz) { fclose(fp); free(buf); http_send(fd, 500, "text/plain", "read", 4); return; }
    fclose(fp);
    const char *ct = strstr(full, ".css") ? "text/css" :
                     strstr(full, ".js")  ? "application/javascript" :
                     "text/html; charset=utf-8";
    http_send(fd, 200, ct, buf, sz);
    free(buf);
}

int main(void)
{
    signal(SIGPIPE, SIG_IGN);

    if (tpinit(NULL) < 0) { fprintf(stderr, "tpinit FAIL: %s\n", tpstrerror(tperrno)); return 1; }
    if (tpopen() < 0)     { fprintf(stderr, "tpopen FAIL: %s\n", tpstrerror(tperrno)); return 1; }

    g_pg = PQconnectdb("host=db dbname=acq user=acq password=acq");
    if (PQstatus(g_pg) != CONNECTION_OK)
        fprintf(stderr, "경고: 조회DB 연결 실패(%s) — /api/query 비활성\n", PQerrorMessage(g_pg));

    int srv = socket(AF_INET, SOCK_STREAM, 0);
    int on = 1;
    setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    struct sockaddr_in a; memset(&a, 0, sizeof(a));
    a.sin_family = AF_INET; a.sin_addr.s_addr = htonl(INADDR_ANY); a.sin_port = htons(GW_PORT);
    if (bind(srv, (struct sockaddr *)&a, sizeof(a)) < 0) { perror("bind"); return 1; }
    if (listen(srv, 16) < 0) { perror("listen"); return 1; }
    userlog("acq_httpgw: 업무화면 게이트웨이 기동 :%d (HTTP -> tpcall)", GW_PORT);
    fprintf(stderr, "acq_httpgw listening on :%d\n", GW_PORT);

    while (1) {
        int fd = accept(srv, NULL, NULL);
        if (fd < 0) continue;
        static char req[REQ_MAX];
        ssize_t n = read(fd, req, sizeof(req) - 1);
        if (n <= 0) { close(fd); continue; }
        req[n] = '\0';

        /* "GET /path?query HTTP/1.1" 만 처리 */
        char *sp1 = strchr(req, ' ');
        char *sp2 = sp1 ? strchr(sp1 + 1, ' ') : NULL;
        if (!sp1 || !sp2 || strncmp(req, "GET ", 4) != 0) { http_send(fd, 400, "text/plain", "bad req", 7); close(fd); continue; }
        *sp2 = '\0';
        char *path = sp1 + 1;
        char *query = strchr(path, '?');
        if (query) *query++ = '\0';

        if (0 == strcmp(path, "/api/call") && query)
            handle_api_call(fd, query);
        else if (0 == strcmp(path, "/api/query") && query)
            handle_api_query(fd, query);
        else
            handle_static(fd, path);
        close(fd);
    }
    return 0;
}
