/*
 * ACQ_CANCEL.pgc - ac 모듈 서비스 ACQ_CANCEL (one service = one file).
 * 매입 전액 취소: 취소 가능 상태를 확인하고 ac_cancel 에 FULL 취소행을 만든 뒤
 * 매입을 취소(C)로 전이한다. 취소 수수료 환급분과 가맹점 요약도 함께 갱신한다.
 * 입출력 계약 보존: 입력 T_PURCHASE_ID/T_AMOUNT/T_BIZDATE/T_REASON,
 *                   출력 T_CANCEL_ID / T_STATUS='C'.
 * Runs on the caller's XA branch (no EXEC SQL CONNECT). Advertised by ac_svr.
 */
#include "ACQ_CANCEL.h"

void ACQ_CANCEL(TPSVCINFO *p)
{
    UBFH *b = (UBFH *)p->data;
    const char *SVC = "ACQ_CANCEL";
    char bizdate[16], reason[128], logmsg[256];
    long grade = 0, ratio = 0;
    EXEC SQL BEGIN DECLARE SECTION;
    long h_id, h_pid, h_amt;
    long h_orig = 0, h_fee = 0, h_prior = 0, h_cnt = 0, h_sum = 0, h_logid = 0;
    char h_bizdate[16], h_reason[128], h_prev[8], h_merch[64], h_logmsg[256];
    char h_cardno[32];
    EXEC SQL END DECLARE SECTION;

    userlog("%s 진입: 매입 전액 취소 요청", SVC);

    /* 1) 입력 검증부 */
    h_pid = getl(b, T_PURCHASE_ID);
    if (h_pid <= 0) {
        Bchg(b, T_MSG, 0, "매입 ID 오류", 0L); setl(b, T_RC, 102); FAIL(b);
    }
    h_amt = getl(b, T_AMOUNT);
    gets_(b, T_BIZDATE, bizdate, sizeof(bizdate));
    gets_(b, T_REASON, reason, sizeof(reason)); if (reason[0] == 0) strcpy(reason, "CUSTOMER");
    strncpy(h_bizdate, bizdate, sizeof(h_bizdate)-1); h_bizdate[sizeof(h_bizdate)-1] = 0;
    strncpy(h_reason, reason, sizeof(h_reason)-1); h_reason[sizeof(h_reason)-1] = 0;
    if (h_bizdate[0] == 0) {
        EXEC SQL SELECT param_val INTO :h_bizdate FROM cm_param WHERE param_key = 'BIZ_DATE';
        if (sqlca.sqlcode != 0) strcpy(h_bizdate, "2026-07-19");
    }

    /* 2) 마스터/상태 조회부 */
    EXEC SQL SELECT status, merchant_id, amount, fee INTO :h_prev, :h_merch, :h_orig, :h_fee
        FROM purchase WHERE purchase_id = :h_pid;
    if (sqlca.sqlcode < 0) {
        userlog("%s: 매입원장 조회 실패 [%d] %s", SVC, sqlca.sqlcode, sqlca.sqlerrm.sqlerrmc);
        setl(b, T_RC, 90); FAIL(b);
    }
    if (sqlca.sqlcode == 100) {
        Bchg(b, T_MSG, 0, "매입 미존재", 0L); setl(b, T_RC, 111); FAIL(b);
    }
    /* 취소 금액 미지정 시 원 매입금액을 전액 취소한다 */
    if (h_amt <= 0) h_amt = h_orig;
    userlog("%s 컨텍스트: pid=%ld merch=%s 상태=%s 원금=%ld 취소=%ld",
            SVC, h_pid, h_merch, h_prev, h_orig, h_amt);

    /* 기취소 누계 확인 */
    EXEC SQL SELECT coalesce(sum(amount),0), count(*) INTO :h_prior, :h_cnt
        FROM ac_cancel WHERE purchase_id = :h_pid;
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 90); FAIL(b); }

    /* 3) 업무규칙 분기부: 상태/누계/금액 검증 (3+ 분기) */
    if (strcmp(h_prev, "C") == 0 || strcmp(h_prev, "V") == 0) {
        userlog("%s 거절: 이미 종료된 매입 상태=%s", SVC, h_prev);
        Bchg(b, T_MSG, 0, "이미 취소/무효된 매입", 0L); setl(b, T_RC, 115); FAIL(b);
    }
    if (h_prior + h_amt > h_orig) {
        Bchg(b, T_MSG, 0, "취소 누계가 원금 초과", 0L); setl(b, T_RC, 116); FAIL(b);
    }
    if (strcmp(h_prev, "S") == 0) {
        grade = 3; Bchg(b, T_MSG, 0, "정산완료건 취소", 0L);
    } else if (h_prior > 0) {
        grade = 2; Bchg(b, T_MSG, 0, "부분취소 후 잔액 취소", 0L);
    } else {
        grade = 1; Bchg(b, T_MSG, 0, "전액 취소", 0L);
    }
    /* 수수료 환급분(비례배분) */
    ratio = (h_orig > 0) ? (h_fee * h_amt / h_orig) : 0;

    /* 4) 본처리부 */
    EXEC SQL SELECT nextval('ac_cancel_seq') INTO :h_id;
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 90); FAIL(b); }
    EXEC SQL INSERT INTO ac_cancel (cancel_id, purchase_id, cancel_type, amount, reason, biz_date)
        VALUES (:h_id, :h_pid, 'FULL', :h_amt, :h_reason, :h_bizdate);
    if (sqlca.sqlcode < 0) {
        userlog("%s: 취소행 적재 실패 [%d]", SVC, sqlca.sqlcode);
        setl(b, T_RC, 90); FAIL(b);
    }
    if (acdb_update_status(h_pid, "C") < 0) {
        userlog("%s: 취소 상태 전이 실패 pid=%ld", SVC, h_pid);
        Bchg(b, T_MSG, 0, "상태 전이 실패", 0L); setl(b, T_RC, 96); FAIL(b);
    }

    /* 6) 집계/요약 갱신부: 가맹점 취소 실적 재집계 */
    EXEC SQL SELECT count(*), coalesce(sum(amount),0) INTO :h_cnt, :h_sum
        FROM purchase WHERE merchant_id = :h_merch AND biz_date = :h_bizdate
          AND status = 'C';
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 90); FAIL(b); }

    /* 5) 이력/감사 적재부 */
    snprintf(logmsg, sizeof(logmsg), "매입취소 pid=%ld cid=%ld 직전=%s 취소=%ld 환급=%ld",
             h_pid, h_id, h_prev, h_amt, ratio);
    strncpy(h_logmsg, logmsg, sizeof(h_logmsg)-1); h_logmsg[sizeof(h_logmsg)-1] = 0;
    EXEC SQL SELECT nextval('cm_log_seq') INTO :h_logid;
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 90); FAIL(b); }
    EXEC SQL INSERT INTO cm_log (log_id, svc_name, log_level, msg, biz_date)
        VALUES (:h_logid, 'ACQ_CANCEL', 'I', :h_logmsg, :h_bizdate);
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 90); FAIL(b); }

    /* --- 업무 체인: 매입취소 -> 한도원복(LIMIT_RST) -> 원장역분개(LG_REVERSE) -> 정산취소(ST_CANCEL) ---
     * 하위 서비스는 별도 XA 브랜치라 이 서비스가 방금 INSERT/UPDATE 한
     * 미커밋 행을 볼 수 없다. 판정에 필요한 값은 전부 UBF 전문으로 실어 보낸다.
     * 하위 실패 시 같은 글로벌 트랜잭션이므로 FAIL 로 전체 롤백한다(보상 불필요).
     * 자기 응답 계약은 이 블록 뒤 응답부에서 다시 확정한다. */
    {
        long rlen = 0, chain_rc = 0;
        char keep_status[16];

        keep_status[0] = 0;
        gets_(b, T_STATUS, keep_status, sizeof(keep_status));
        Bchg(b, T_SVCNAME, 0, "ACQ_CANCEL", 0L);
        setl(b, T_PURCHASE_ID, h_pid);
        setl(b, T_AMOUNT, h_amt);
        Bchg(b, T_MERCHANT, 0, h_merch, 0L);
        Bchg(b, T_BIZDATE, 0, h_bizdate, 0L);
        setl(b, T_CANCEL_ID, h_id);

        /* 1단계: 취소금액만큼 가맹점/카드 한도를 되돌린다.
         * LIMIT_RST 는 카드번호(T_KEY1)를 필수로 요구하지만 매입원장에는 카드가 없다.
         * 승인원장(au_authorization.auth_id = purchase_id)에서 카드를 끌어와 전문에 싣는다.
         * 승인 없이 접수된 매입(무승인 매입 등)은 원복할 카드 컨텍스트 자체가 없으므로
         * 한도원복 단계를 건너뛴다 — 실패가 아니라 해당 없음이다. */
        h_cardno[0] = 0;
        EXEC SQL SELECT card_no INTO :h_cardno FROM au_authorization WHERE auth_id = :h_pid;
        if (sqlca.sqlcode < 0) {
            userlog("%s: 승인원장 카드 조회 실패 [%d]", SVC, sqlca.sqlcode);
            setl(b, T_RC, 90); FAIL(b);
        }
        if (h_cardno[0] != 0) {
            Bchg(b, T_KEY1, 0, h_cardno, 0L);
            setl(b, T_ARG3, h_orig);
            Bchg(b, T_REASON, 0, h_reason, 0L);
            if (tpcall("LIMIT_RST", (char *)b, 0L, (char **)&b, &rlen, 0L) < 0) {
                userlog("%s: tpcall(LIMIT_RST) 실패: %s", SVC, tpstrerror(tperrno));
                Bchg(b, T_MSG, 0, "한도 원복 실패", 0L); setl(b, T_RC, 141);
                FAIL(b);
            }
            chain_rc = getl(b, T_RC);
            userlog("%s 체인: LIMIT_RST 응답 rc=%ld card=%s", SVC, chain_rc, h_cardno);
        } else {
            userlog("%s 체인: pid=%ld 승인원장 카드 없음 — 한도원복 생략", SVC, h_pid);
        }

        /* 2단계: 원 매입 전표를 역분개해 회계 잔액을 맞춘다 */
        setl(b, T_ARG4, h_id);
        setl(b, T_ARG5, ratio);
        if (tpcall("LG_REVERSE", (char *)b, 0L, (char **)&b, &rlen, 0L) < 0) {
            userlog("%s: tpcall(LG_REVERSE) 실패: %s", SVC, tpstrerror(tperrno));
            Bchg(b, T_MSG, 0, "원장 역분개 실패", 0L); setl(b, T_RC, 142);
            FAIL(b);
        }
        chain_rc = getl(b, T_RC);
        userlog("%s 체인: LG_REVERSE 응답 rc=%ld", SVC, chain_rc);

        /* 3단계: 정산 예정분을 취소해 지급 대상에서 제외한다 */
        setl(b, T_ARG6, h_fee);
        if (tpcall("ST_CANCEL", (char *)b, 0L, (char **)&b, &rlen, 0L) < 0) {
            userlog("%s: tpcall(ST_CANCEL) 실패: %s", SVC, tpstrerror(tperrno));
            Bchg(b, T_MSG, 0, "정산 취소 실패", 0L); setl(b, T_RC, 143);
            FAIL(b);
        }
        chain_rc = getl(b, T_RC);
        userlog("%s 체인: ST_CANCEL 응답 rc=%ld", SVC, chain_rc);

        /* 하위가 덮어쓴 자기 상태 계약값 복원 */
        if (keep_status[0] != 0) Bchg(b, T_STATUS, 0, keep_status, 0L);
        setl(b, T_RC, 0);
    }

    /* 8) 응답부 (계약 보존: T_CANCEL_ID / T_STATUS='C') */
    setl(b, T_CANCEL_ID, h_id);
    Bchg(b, T_STATUS, 0, "C", 0L);
    Bchg(b, T_MERCHANT, 0, h_merch, 0L);
    setl(b, T_AMOUNT, h_amt);
    setl(b, T_FEE, ratio);
    setl(b, T_ARG1, grade);
    setl(b, T_COUNT, h_cnt);
    setl(b, T_GROSS, h_sum);
    setl(b, T_RC, 0);
    userlog("%s 완료: pid=%ld cid=%ld 취소=%ld 등급=%ld", SVC, h_pid, h_id, h_amt, grade);
    OK(b);
}
