/*
 * PREAUTH_CANCEL.pgc - au 모듈 서비스 PREAUTH_CANCEL (one service = one file).
 * 선승인 취소: 미확정(P) 선승인을 X 로 전환해 홀드 금액을 해제한다.
 * 상태별로 세분화된 RC(없음/이미 확정/이미 취소)를 돌려줘 콜센터 응대에 쓴다.
 * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
 */
#include "PREAUTH_CANCEL.h"

/*
 * 심화 구현 노트 (PREAUTH_CANCEL):
 *   입력검증→마스터/정책조회→업무규칙분기→본처리→이력/재집계→예외/보상→응답
 *   의 9부 구조로 전개한다. 승인/한도 원장(au_authorization/card_limit)과
 *   집계(au_summary)만 사용하며 새 테이블은 만들지 않는다.
 */
void PREAUTH_CANCEL(TPSVCINFO *p)
{
    UBFH *b = (UBFH *)p->data;
    const char *svcname = "PREAUTH_CANCEL";
    long rc = 0, outv = 0, avail = 0, total = 0, buckets = 0, peak = 0;
    char outs[64], res_status[16], outmsg[128], peakkey[24];
    long fee_amt = 0, vat_amt = 0;
    char nextbiz[16];
    EXEC SQL BEGIN DECLARE SECTION;
    char h_card[24], h_card2[24], h_merch[64], h_bizdate[16], h_status[16];
    char h_reason[160], h_type[16], h_appr[16], h_grp[24], h_ccy[8];
    char h_channel[16], h_category[16], h_issuer[16], h_docno[40], h_mstat[16];
    long h_amount = 0, h_limit = 0, h_used = 0, h_temp = 0, h_cnt = 0;
    long h_sum = 0, h_appr_cnt = 0, h_dec_cnt = 0, h_auth = 0, h_id = 0;
    long h_delta = 0, h_install = 0, h_score = 0, h_v1 = 0, h_v2 = 0;
    long h_v3 = 0, h_gcnt = 0, h_gsum = 0, h_mdr = 0, h_dlim = 0;
    long h_black = 0, h_vel = 0, h_risk = 0, h_hist = 0, h_prev = 0;
    char h_cstat[16];
    EXEC SQL END DECLARE SECTION;

    memset(outs, 0, sizeof(outs)); memset(res_status, 0, sizeof(res_status));
    memset(outmsg, 0, sizeof(outmsg)); memset(peakkey, 0, sizeof(peakkey));
    memset(h_card, 0, sizeof(h_card)); memset(h_merch, 0, sizeof(h_merch));
    memset(h_bizdate, 0, sizeof(h_bizdate)); memset(h_status, 0, sizeof(h_status));
    memset(h_reason, 0, sizeof(h_reason)); memset(h_mstat, 0, sizeof(h_mstat));
    memset(h_grp, 0, sizeof(h_grp)); memset(h_appr, 0, sizeof(h_appr));
    memset(nextbiz, 0, sizeof(nextbiz)); memset(h_cstat, 0, sizeof(h_cstat));
    userlog("[%s] 진입: 선승인 취소: 미확정(P) 선승인을 X 로 전환해 홀드 금액을 해제한다.", svcname);

    /* 공통 컨텍스트 로드 (요청에 없으면 공백/0) */
    gets_(b, T_KEY1, h_card, sizeof(h_card));
    gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));
    gets_(b, T_BIZDATE, h_bizdate, sizeof(h_bizdate));
    h_amount = getl(b, T_AMOUNT);

    /* 1) 입력 검증부: 이 서비스가 읽는 필드를 개별 코드로 점검 */
    h_id = getl(b, T_ID2);
    if (h_id <= 0) { setl(b, T_RC, 8801L); Bchg(b, T_MSG, 0, "식별자(ID) 범위 오류", 0L); userlog("[%s] 입력검증 실패: ID<=0", svcname); FAIL(b); }

    /* 2) 상태 조회부: 대상 승인의 현재 상태/금액/카드 확보 */
    if (h_auth <= 0) h_auth = getl(b, T_ID2);
    EXEC SQL SELECT status, amount, card_no, to_char(biz_date,'YYYY-MM-DD')
        INTO :h_status, :h_amount, :h_card, :h_bizdate
        FROM au_authorization WHERE auth_id = :h_auth;
    if (sqlca.sqlcode == 100) { setl(b, T_RC, 31L);
        Bchg(b, T_MSG, 0, "대상 승인건 없음", 0L);
        userlog("[%s] 대상없음 auth_id=%ld", svcname, h_auth); FAIL(b); }
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 32L); FAIL(b); }
    strncpy(res_status, h_status, sizeof(res_status)-1);

    /* 3) 전이 가능여부 분기부: 허용 상태에서만 X 로 전이 */
    if (strchr("P", h_status[0]) == NULL) {
        setl(b, T_RC, 33L); Bchg(b, T_MSG, 0, "상태전이 불가", 0L);
        userlog("[%s] 전이불가 cur=%s auth_id=%ld", svcname, h_status, h_auth); rc = 33;
        setl(b, T_RC, rc); goto respond; }
    if (h_delta < 0) h_delta = -h_delta;

    /* 4) 본처리부: 원장 상태 전이 (+금액조정/취소원장) */
    EXEC SQL UPDATE au_authorization SET status = 'X', updated_at = now()
        WHERE auth_id = :h_auth AND status IN ('P');
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 34L); FAIL(b); }
    if (sqlca.sqlerrd[2] == 0) { setl(b, T_RC, 35L);
        Bchg(b, T_MSG, 0, "반영 건수 0 (경합/이미전이)", 0L); rc = 35; goto respond; }
    strcpy(res_status, "X"); rc = 0;
    /* 금액조정형: 조정 델타를 승인금액에 반영 */
    if (h_delta > 0) {
        EXEC SQL UPDATE au_authorization SET amount = amount + :h_delta, updated_at = now()
            WHERE auth_id = :h_auth;
        if (sqlca.sqlcode < 0) { setl(b, T_RC, 36L); FAIL(b); }
        h_amount += h_delta;
    }

    /* 5) 이력/재집계부: 카드 당일 상태분포 재집계 */
    if (h_card[0]) {
        EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum
            FROM au_authorization WHERE card_no = :h_card AND biz_date = :h_bizdate;
        if (sqlca.sqlcode < 0) { setl(b, T_RC, 37L); FAIL(b); }
    }
    /* 6) 집계 갱신부: 상태 전이 결과 상위 카운트 */
    EXEC SQL SELECT count(*) INTO :h_v1 FROM au_authorization
        WHERE card_no = :h_card AND status = 'X';
    if (sqlca.sqlcode < 0) h_v1 = 0;
    /* 7) 예외/보상: 위 EXEC SQL 실패는 FAIL→XA 롤백으로 부분전이 방지 */
respond:
    outv = h_amount; avail = h_v1;
    userlog("[%s] 전이 %s->%s auth_id=%ld rc=%ld", svcname, h_status, res_status, h_auth, rc);

    /* ---------------------------------------------------------------
     * 5) 이력/감사 적재부 (PREAUTH_CANCEL)
     *    본 스키마에는 범용 이력(cm_log) 테이블이 없으므로, 스펙에 따라
     *    SELECT 기반 재집계로 감사 요약을 만들고 userlog 로 처리 이력을
     *    남긴다. 승인 원장 자체가 이력원장 역할을 겸한다.
     * --------------------------------------------------------------- */
    if (h_card[0]) {
        EXEC SQL SELECT count(*), coalesce(sum(amount),0)
            INTO :h_hist, :h_prev FROM au_authorization
            WHERE card_no = :h_card AND (:h_bizdate = '' OR biz_date = :h_bizdate::date);
        if (sqlca.sqlcode < 0) { h_hist = 0; h_prev = 0; }
        userlog("[%s] 감사요약 card=%s 누적건수=%ld 누적금액=%ld",
                svcname, h_card, h_hist, h_prev);
    }

    /* 추가 업무규칙 분기부 (1): 블랙리스트 등재 여부 - 등재분은 경보코드로 */
    if (h_card[0]) {
        EXEC SQL SELECT count(*) INTO :h_black
            FROM au_blacklist WHERE card_no = :h_card;
        if (sqlca.sqlcode < 0) h_black = 0;
        if (h_black > 0) {
            if (rc == 0) rc = 91;
            strcpy(outmsg, "블랙리스트 등재 카드");
            userlog("[%s] 블랙리스트 적중 card=%s rc=%ld", svcname, h_card, rc);
        }
    }

    /* 추가 업무규칙 분기부 (2): 카드 상태 검증 (한도원장 status) */
    if (h_card[0]) {
        EXEC SQL SELECT status INTO :h_cstat
            FROM card_limit WHERE card_no = :h_card;
        if (sqlca.sqlcode == 100) strcpy(h_cstat, "NONE");
        else if (sqlca.sqlcode < 0) strcpy(h_cstat, "ERR");
        if (strcmp(h_cstat, "ACTIVE") != 0 && strcmp(h_cstat, "NONE") != 0) {
            if (rc == 0) rc = 92;
            userlog("[%s] 카드상태 비정상 card=%s status=%s", svcname, h_card, h_cstat);
        }
    }

    /* 추가 업무규칙 분기부 (3): velocity 판정 - 직전 1시간 승인 시도 건수 */
    if (h_card[0]) {
        EXEC SQL SELECT count(*) INTO :h_vel FROM au_authorization
            WHERE card_no = :h_card AND created_at > now() - interval '1 hour';
        if (sqlca.sqlcode < 0) h_vel = 0;
        if (h_vel >= 10) {
            if (rc == 0) rc = 93;
            strcpy(outmsg, "단시간 승인시도 과다(velocity)");
            userlog("[%s] velocity 경보 card=%s 1시간 %ld건", svcname, h_card, h_vel);
        }
    }

    /* 추가 업무규칙 분기부 (4): 이상거래 누적 점수(최고점) 조회 */
    if (h_card[0]) {
        EXEC SQL SELECT coalesce(max(score),0), count(*) INTO :h_risk, :h_v3
            FROM au_fraud_log WHERE card_no = :h_card;
        if (sqlca.sqlcode < 0) { h_risk = 0; }
        if (h_risk >= 90) {
            strcpy(outmsg, "고위험 카드(부정 적발이력)");
            userlog("[%s] 고위험 카드 card=%s 최고점수=%ld", svcname, h_card, h_risk);
        }
    }

    /* 6) 집계 갱신부: 가맹점 당일 승인 집계를 재계산해 au_summary 로 upsert */
    if (h_merch[0] && h_bizdate[0]) {
        EXEC SQL SELECT count(*), count(*) FILTER (WHERE status = 'A'),
                        count(*) FILTER (WHERE status = 'D'), coalesce(sum(amount),0)
            INTO :h_gcnt, :h_appr_cnt, :h_dec_cnt, :h_gsum
            FROM au_authorization
            WHERE merchant_id = :h_merch AND biz_date = :h_bizdate;
        if (sqlca.sqlcode < 0) {
            setl(b, T_RC, 108L);
            Bchg(b, T_MSG, 0, "가맹점 집계 재계산 실패", 0L);
            userlog("[%s] 집계 재계산 실패 merch=%s", svcname, h_merch);
            FAIL(b);
        }
        if (audb_upsert_auth_summary(h_merch, h_bizdate, h_gcnt,
                                     h_appr_cnt, h_dec_cnt, h_gsum) < 0) {
            setl(b, T_RC, 109L);
            Bchg(b, T_MSG, 0, "가맹점 집계 upsert 실패", 0L);
            FAIL(b);
        }
        userlog("[%s] 집계갱신 merch=%s 건수=%ld 승인=%ld 거절=%ld 금액=%ld",
                svcname, h_merch, h_gcnt, h_appr_cnt, h_dec_cnt, h_gsum);
    }

    /* 정산 참고치: 매입 수수료/부가세/차기 영업일 (libacqcommon 공통유틸) */
    if (h_amount > 0) {
        fee_amt = acq_fee(h_amount, (h_mdr > 0 ? h_mdr : 25L));
        vat_amt = acq_vat(fee_amt);
    }
    if (h_bizdate[0] && acq_next_bizday(h_bizdate, nextbiz) == 0) {
        userlog("[%s] 정산참고 차기영업일=%s 수수료=%ld 부가세=%ld",
                svcname, nextbiz, fee_amt, vat_amt);
    }

    /* 7) 예외/보상부: 위 각 구간의 EXEC SQL 실패는 그 지점에서 T_RC/T_MSG 를
     *    세팅한 뒤 FAIL(b) → tpreturn(TPFAIL) 로 되돌린다. 서비스가 호출자와
     *    같은 XA 브랜치에서 돌기 때문에 TPFAIL 은 브랜치 롤백을 유발하고,
     *    따라서 원장/한도/집계가 부분만 반영되는 상태가 남지 않는다. */
    if (outmsg[0]) Bchg(b, T_MSG, 0, outmsg, 0L);
    if (fee_amt > 0) setl(b, T_FEE, fee_amt);

    /* ---------------------------------------------------------------
     * 업무체인: PREAUTH_CANCEL → LIMIT_RST (예비승인 취소 한도원복)
     * XA 규율: 하위 서비스는 같은 글로벌 트랜잭션의 '다른 브랜치'라서 이 서비스가
     *   방금 INSERT/UPDATE 한 미커밋 행을 볼 수 없다. 그러므로 하위가 필요로 하는
     *   값은 모두 전문(T_*)에 실어 넘기고, 하위 실패 시에는 T_RC/T_MSG 를 채운 뒤
     *   FAIL(b) → tpreturn(TPFAIL) 로 체인 전체를 롤백한다(별도 보상 불필요).
     * --------------------------------------------------------------- */
    if (h_card[0]) {
        long ch_len = 0, ch_rc = 0, ch_amt = h_amount;
        if (ch_amt < 0) ch_amt = 0;
        Bchg(b, T_KEY1, 0, h_card, 0L);
        if (h_merch[0]) Bchg(b, T_MERCHANT, 0, h_merch, 0L);
        if (h_bizdate[0]) Bchg(b, T_BIZDATE, 0, h_bizdate, 0L);
        setl(b, T_AMOUNT, ch_amt);
        userlog("[%s] 체인 호출 → LIMIT_RST card=%s 금액=%ld", svcname, h_card, ch_amt);
        Bchg(b, T_STR8, 0, "CHAIN", 0L);       /* 체인 하위 호출 표식 */
        if (tpcall("LIMIT_RST", (char *)b, 0L, (char **)&b, &ch_len, 0L) < 0) {
            userlog("[%s] tpcall(LIMIT_RST) 실패: %s", svcname, tpstrerror(tperrno));
            setl(b, T_RC, 7118L);
            Bchg(b, T_MSG, 0, "예비승인 취소 원복 체인 실패", 0L);
            FAIL(b);
        }
        ch_rc = getl(b, T_RC);                 /* 하위 응답 반영 */
        Bchg(b, T_STR8, 0, "", 0L);            /* 체인 표식 해제 */
        setl(b, T_AMOUNT, h_amount);           /* 요청 금액 복원 */
        if (ch_rc != 0 && rc == 0) rc = 7168L;
        Bchg(b, T_MSG, 0, (ch_rc == 0 ? "예비승인 원복 완료" : "예비승인 원복 경고"), 0L);
        userlog("[%s] 체인 완료 LIMIT_RST rc=%ld", svcname, ch_rc);
    }

    /* 8) 응답부: 성공 결과코드 + 계약상 출력 필드 세팅 */
    setl(b, T_RC, rc);
    setl(b, T_AMT1, h_limit);
    userlog("[%s] 완료: rc=%ld cnt=%ld sum=%ld id=%ld", svcname, rc, h_cnt, h_sum, (long)(h_auth?h_auth:h_id));
    OK(b);
}
