/*
 * TOKENIZE.pgc - au 모듈 서비스 TOKENIZE (one service = one file).
 * PAN 토큰화 조회/발급: 입력 PAN 을 SHA-256 해시로 card_vault 에서 조회하고,
 *   있으면 기존 토큰을, 없으면 신규 토큰('TKN-'||md5 앞12)을 발급/적재한다.
 *   PCI 스코프 축소 규율: 원문 PAN 은 저장하지 않고(마스킹+해시만), 응답에도
 *   절대 싣지 않는다. 응답은 토큰과 마스킹 문자열, 발급 상태만 반환한다.
 * 처리단계: 입력검증 -> 해시산출 -> 볼트조회 -> 업무규칙분기(원문/마스킹) ->
 *   본처리(신규발급 INSERT) -> 적재 재검증 -> 응답 -> 로깅.
 * 호출자 XA 브랜치에서 수행(EXEC SQL CONNECT 없음). au_svr 가 tpsvrinit 에서 광고.
 * 캐스팅 규율: 호스트변수 date 캐스팅 금지. 해시는 convert_to(...,'UTF8') 로
 *   bytea 변환하여 시드(sha256(card_no::bytea))와 동일 해시를 얻는다(ASCII 동치).
 */
#include "TOKENIZE.h"

/* TOKENIZE - PAN 토큰화 조회/발급 (원문 PAN 미응답). */
void TOKENIZE(TPSVCINFO *p)
{
    UBFH *b = (UBFH *)p->data;
    const char *svcname = "TOKENIZE";
    /* ---- 지역 스크래치 (DECLARE SECTION 밖에서 초기화) ---- */
    long rc = 0, is_pan = 0, i = 0, ndigit = 0, slen = 0;
    char inbuf[48];

    EXEC SQL BEGIN DECLARE SECTION;
    char h_card[48];     /* 입력 원문/마스킹 — 응답에 원문으로 절대 싣지 않음 */
    char h_token[48];
    char h_masked[48];
    char h_hash[80];
    long h_found;
    long h_tokseq;
    EXEC SQL END DECLARE SECTION;

    memset(inbuf, 0, sizeof(inbuf));
    memset(h_card, 0, sizeof(h_card));   memset(h_token, 0, sizeof(h_token));
    memset(h_masked, 0, sizeof(h_masked)); memset(h_hash, 0, sizeof(h_hash));
    h_found = 0; h_tokseq = 0;
    userlog("[%s] 진입: PAN 토큰화 조회/발급 (원문 미저장/미응답)", svcname);

    /* ===== 1) 입력 검증부 ===== */
    gets_(b, T_KEY1, inbuf, sizeof(inbuf));
    if (inbuf[0] == 0) {
        setl(b, T_RC, 8801L); Bchg(b, T_MSG, 0, "카드번호 필수 누락", 0L);
        userlog("[%s] 입력검증 실패: 카드번호 누락", svcname); FAIL(b);
    }
    strncpy(h_card, inbuf, sizeof(h_card) - 1); h_card[sizeof(h_card) - 1] = 0;
    slen = (long)strlen(h_card);
    if (slen < 4) {
        setl(b, T_RC, 8802L); Bchg(b, T_MSG, 0, "카드번호 형식 오류(길이 부족)", 0L);
        userlog("[%s] 입력검증 실패: 길이 부족 len=%ld", svcname, slen); FAIL(b);
    }

    /* ===== 2) 입력 유형 판정부 (원문 PAN vs 마스킹) =====
     * 전부 숫자이고 길이 12~19 이면 원문 PAN 으로 보고 신규발급을 허용한다.
     * 그 외(대시/별표 포함)는 마스킹 문자열로 보고 조회만 수행한다. */
    for (i = 0, ndigit = 0; i < slen; i++) {
        if (h_card[i] >= '0' && h_card[i] <= '9') ndigit++;
    }
    is_pan = (ndigit == slen && slen >= 12 && slen <= 19) ? 1 : 0;
    userlog("[%s] 입력유형 is_pan=%ld len=%ld digits=%ld", svcname, is_pan, slen, ndigit);

    /* ===== 3) 해시 산출부 (원문 불가역 SHA-256) =====
     * 시드(97c)는 sha256(card_no::bytea) 로 적재했다. convert_to(text,'UTF8') 는
     * ASCII 숫자열에 대해 동일 bytea 를 주므로 같은 해시로 조회가 성립한다. */
    EXEC SQL SELECT encode(sha256(convert_to(:h_card, 'UTF8')), 'hex') INTO :h_hash;
    if (sqlca.sqlcode < 0) {
        setl(b, T_RC, 8810L); Bchg(b, T_MSG, 0, "해시 산출 실패", 0L);
        userlog("[%s] 해시 산출 실패 [%d]", svcname, sqlca.sqlcode); FAIL(b);
    }

    /* ===== 4) 볼트 조회부 (해시 일치 또는 마스킹 일치) ===== */
    EXEC SQL SELECT token, pan_masked INTO :h_token, :h_masked
        FROM card_vault
        WHERE pan_hash = :h_hash OR pan_masked = :h_card
        LIMIT 1;
    if (sqlca.sqlcode == 100) {
        h_found = 0;
    } else if (sqlca.sqlcode < 0) {
        setl(b, T_RC, 8811L); Bchg(b, T_MSG, 0, "토큰볼트 조회 오류", 0L);
        userlog("[%s] 볼트 조회 오류 [%d]", svcname, sqlca.sqlcode); FAIL(b);
    } else {
        h_found = 1;
    }

    /* ===== 5) 업무규칙 분기부 ===== */
    if (h_found) {
        /* 이미 토큰화된 카드: 기존 토큰 반환 */
        strcpy(inbuf, "FOUND"); rc = 0;
        userlog("[%s] 기존 토큰 조회 성공 token=%s", svcname, h_token);
    } else if (!is_pan) {
        /* 마스킹 입력인데 미발견: 원문이 없어 발급 불가 — 조회 미스로 응답 */
        strcpy(inbuf, "NOTFOUND"); rc = 1;
        strncpy(h_masked, h_card, sizeof(h_masked) - 1);  /* 입력이 이미 마스킹(비민감) */
        h_masked[sizeof(h_masked) - 1] = 0;
        h_token[0] = 0;
        userlog("[%s] 마스킹 입력 미발견 — 발급 보류 rc=1", svcname);
    } else {
        /* ===== 6) 본처리부 (신규 토큰 발급 INSERT) ===== */
        strcpy(inbuf, "NEW"); rc = 0;
        EXEC SQL SELECT nextval('card_token_seq') INTO :h_tokseq;   /* audit 채번 */
        if (sqlca.sqlcode < 0) {
            setl(b, T_RC, 8820L); Bchg(b, T_MSG, 0, "토큰 시퀀스 채번 실패", 0L);
            userlog("[%s] 시퀀스 채번 실패 [%d]", svcname, sqlca.sqlcode); FAIL(b);
        }
        EXEC SQL INSERT INTO card_vault
            (token, pan_masked, pan_hash, bin, last4)
            VALUES ('TKN-' || substr(md5(:h_card), 1, 12),
                    substr(:h_card, 1, 4) || '-****-****-' || right(:h_card, 4),
                    :h_hash, substr(:h_card, 1, 6), right(:h_card, 4))
            RETURNING token, pan_masked INTO :h_token, :h_masked;
        if (sqlca.sqlcode < 0) {
            setl(b, T_RC, 8821L); Bchg(b, T_MSG, 0, "토큰볼트 적재 실패", 0L);
            userlog("[%s] 볼트 적재 실패 [%d]", svcname, sqlca.sqlcode); FAIL(b);
        }
        userlog("[%s] 신규 토큰 발급 seq=%ld token=%s", svcname, h_tokseq, h_token);
    }

    /* ===== 7) 적재 재검증부 (발급/조회 결과 정합성) ===== */
    if (h_token[0]) {
        EXEC SQL SELECT count(*) INTO :h_found FROM card_vault WHERE token = :h_token;
        if (sqlca.sqlcode < 0) {
            setl(b, T_RC, 8830L); Bchg(b, T_MSG, 0, "토큰 재검증 조회 오류", 0L); FAIL(b);
        }
        if (h_found <= 0) {
            setl(b, T_RC, 8831L); Bchg(b, T_MSG, 0, "토큰 재검증 실패(적재행 미확인)", 0L);
            userlog("[%s] 재검증 실패 token=%s", svcname, h_token); FAIL(b);
        }
    }

    /* ===== 8) 응답부 (토큰/마스킹/상태만 — 원문 PAN 미포함) ===== */
    Bchg(b, T_STR1, 0, h_token, 0L);          /* token (비민감) */
    Bchg(b, T_STR2, 0, h_masked, 0L);         /* pan_masked (비민감) */
    Bchg(b, T_STATUS, 0, inbuf, 0L);          /* NEW / FOUND / NOTFOUND */
    if (h_tokseq > 0) setl(b, T_ID1, h_tokseq);
    setl(b, T_RC, rc);
    if (rc == 0) Bchg(b, T_MSG, 0, "토큰화 정상", 0L);

    /* ===== 9) 완료 로깅 ===== */
    userlog("[%s] 완료: status=%s rc=%ld token=%s masked=%s",
            svcname, inbuf, rc, h_token, h_masked);
    OK(b);
}

/* vim: set ts=4 sw=4 et smartindent: */
