/*
 * INSTALL_AUTH.pgc - au 모듈 서비스 INSTALL_AUTH (one service = one file).
 * 할부 승인: 개월 수만큼 분할 승인 레코드.
 * Moved verbatim from the former inline au_svr.pgc; runs on the caller's XA
 * branch (no EXEC SQL CONNECT). Advertised by au_svr at tpsvrinit.
 */
#include "INSTALL_AUTH.h"

/*
 * 심화 구현 노트 (INSTALL_AUTH):
 *   입력검증→마스터/정책조회→업무규칙분기→본처리→이력/재집계→예외/보상→응답
 *   의 9부 구조로 전개한다. 승인/한도 원장(au_authorization/card_limit)과
 *   집계(au_summary)만 사용하며 새 테이블은 만들지 않는다.
 */
void INSTALL_AUTH(TPSVCINFO *p)
{
    UBFH *b = (UBFH *)p->data;
    const char *svcname = "INSTALL_AUTH";
    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] 진입: 할부 승인: 개월 수만큼 분할 승인 레코드.", svcname);

    /* 공통 컨텍스트 로드 (요청에 없으면 공백/0) */

    /* 1) 입력 검증부: 이 서비스가 읽는 필드를 개별 코드로 점검 */
    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);
    h_install = getl(b, T_INSTALL_N);
    if (h_card[0] == 0) { setl(b, T_RC, 8801L); Bchg(b, T_MSG, 0, "카드번호 필수 누락", 0L); userlog("[%s] 입력검증 실패: 카드번호 누락", svcname); FAIL(b); }
    if (h_merch[0] == 0) { setl(b, T_RC, 8802L); Bchg(b, T_MSG, 0, "가맹점ID 필수 누락", 0L); userlog("[%s] 입력검증 실패: 가맹점ID 누락", svcname); FAIL(b); }
    if (h_bizdate[0] == 0) { setl(b, T_RC, 8803L); Bchg(b, T_MSG, 0, "영업일 필수 누락", 0L); userlog("[%s] 입력검증 실패: 영업일 누락", svcname); FAIL(b); }
    if (h_amount < 0) { setl(b, T_RC, 8804L); Bchg(b, T_MSG, 0, "금액/수치 음수 불가", 0L); userlog("[%s] 입력검증 실패: 음수값", svcname); FAIL(b); }
    if (h_install < 0) { setl(b, T_RC, 8805L); Bchg(b, T_MSG, 0, "금액/수치 음수 불가", 0L); userlog("[%s] 입력검증 실패: 음수값", svcname); FAIL(b); }
    if (strlen(h_bizdate) < 8) { setl(b, T_RC, 8806L); Bchg(b, T_MSG, 0, "영업일 형식 오류(YYYY-MM-DD)", 0L); FAIL(b); }

    /* 2) 마스터/정책 조회부: 카드 한도원장 + 가맹점 상태 */
    if (audb_limit_get(h_card, &h_limit, &h_used) < 0) {
        setl(b, T_RC, 21L); Bchg(b, T_MSG, 0, "한도원장 조회 실패", 0L);
        userlog("[%s] 한도원장 조회 실패 card=%s", svcname, h_card); FAIL(b); }
    avail = h_limit - h_used;
    if (h_merch[0]) {
        EXEC SQL SELECT status, mdr_bps, daily_limit INTO :h_mstat, :h_mdr, :h_dlim
            FROM merchant WHERE merchant_id = :h_merch;
        if (sqlca.sqlcode == 100) { strcpy(h_mstat, "NONE"); }
        else if (sqlca.sqlcode < 0) { setl(b, T_RC, 22L); FAIL(b); }
    }

    /* 3) 업무규칙 분기부: 한도/가맹점상태/금액 구간별 승인 판정 */
    if (strcmp(h_mstat, "CLOSED") == 0) {
        strcpy(res_status, "D"); strcpy(h_reason, "MERCH_CLOSED"); rc = 3;
    } else if (h_amount <= 0) {
        strcpy(res_status, "D"); strcpy(h_reason, "ZERO_AMT"); rc = 4;
    } else if (h_used + h_amount > h_limit) {
        strcpy(res_status, "D"); strcpy(h_reason, "OVER_LIMIT"); rc = 1;
    } else if (h_amount >= 1000000) {
        strcpy(res_status, "A"); strcpy(h_reason, "HIGH_OK"); rc = 0;
    } else {
        strcpy(res_status, "A"); strcpy(h_reason, "OK"); rc = 0;
    }

    /* 4) 본처리부: 승인 원장 채번·삽입 (+승인시 한도 차감) */
    h_auth = audb_next_auth_id();
    if (h_auth < 0) { setl(b, T_RC, 23L); FAIL(b); }
    sprintf(h_appr, "%06ld", h_auth % 1000000L);
    if (audb_insert_auth(h_auth, h_card, h_merch, h_amount, h_appr,
            "AUTH", res_status, h_bizdate) < 0) {
        setl(b, T_RC, 24L); Bchg(b, T_MSG, 0, "승인원장 적재 실패", 0L); FAIL(b); }
    if (res_status[0] == 'A') {
        /* 한도 차감은 아래 업무체인의 LIMIT_DEC 가 수행한다(한도원장 단일 소유자).
         * 같은 글로벌 트랜잭션이라도 XA 브랜치가 다르므로, 여기서 card_limit 을
         * 직접 갱신하면 하위 브랜치가 이 미커밋 행 락을 기다려 교착된다.
         * 따라서 이 브랜치는 승인원장만 쓰고 한도 반영치는 예상값으로만 계산한다. */
        h_used += h_amount; avail = h_limit - h_used;
        userlog("[%s] 한도차감 위임 → LIMIT_DEC 체인 (예상 사용=%ld)", svcname, h_used);
    }

    /* 5) 이력/재집계부: 당일 카드 승인 실적 재집계(감사 요약) */
    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, 26L); FAIL(b); }

    /* 6) 집계 갱신부: 가맹점 당일 승인 집계 upsert */
    if (h_merch[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)
            audb_upsert_auth_summary(h_merch, h_bizdate, h_gcnt, h_appr_cnt, h_dec_cnt, h_gsum);
    }
    /* 7) 예외/보상: 개별 EXEC SQL 실패시 상단에서 FAIL→TPFAIL 롤백 (부분처리 방지) */
    outv = h_amount; avail = h_limit - h_used;
    userlog("[%s] 판정 %s auth_id=%ld amt=%ld avail=%ld", svcname, res_status, h_auth, h_amount, avail);

    /* ---------------------------------------------------------------
     * 5) 이력/감사 적재부 (INSTALL_AUTH)
     *    본 스키마에는 범용 이력(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);

    /* ---------------------------------------------------------------
     * 업무체인: INSTALL_AUTH → LIMIT_DEC (할부승인 원금 한도차감)
     * XA 규율: 하위 서비스는 같은 글로벌 트랜잭션의 '다른 브랜치'라서 이 서비스가
     *   방금 INSERT/UPDATE 한 미커밋 행을 볼 수 없다. 그러므로 하위가 필요로 하는
     *   값은 모두 전문(T_*)에 실어 넘기고, 하위 실패 시에는 T_RC/T_MSG 를 채운 뒤
     *   FAIL(b) → tpreturn(TPFAIL) 로 체인 전체를 롤백한다(별도 보상 불필요).
     * --------------------------------------------------------------- */
    if (h_card[0] && h_amount > 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_DEC card=%s 금액=%ld", svcname, h_card, ch_amt);
        Bchg(b, T_STR8, 0, "CHAIN", 0L);       /* 체인 하위 호출 표식 */
        if (tpcall("LIMIT_DEC", (char *)b, 0L, (char **)&b, &ch_len, 0L) < 0) {
            userlog("[%s] tpcall(LIMIT_DEC) 실패: %s", svcname, tpstrerror(tperrno));
            setl(b, T_RC, 7106L);
            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 = 7156L;
        Bchg(b, T_MSG, 0, (ch_rc == 0 ? "할부 한도차감 완료" : "할부 한도차감 경고"), 0L);
        userlog("[%s] 체인 완료 LIMIT_DEC rc=%ld", svcname, ch_rc);
    }

    /* 8) 응답부: 성공 결과코드 + 계약상 출력 필드 세팅 */
    setl(b, T_COUNT, h_cnt);
    setl(b, T_RC, rc);
    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);
}
