/*
 * LIMIT_OVER_ALERT.pgc - au 모듈 서비스 LIMIT_OVER_ALERT (one service = one file).
 * 한도 소진 임박 알림: 사용액/일한도를 bps 로 환산해 90% 이상이면 경보
 * (RC=1)와 관제 로그를 내고, 잔여 한도액도 함께 응답한다.
 * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
 */
#include "LIMIT_OVER_ALERT.h"

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

    /* 공통 컨텍스트 로드 (요청에 없으면 공백/0) */
    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) 입력 검증부: 이 서비스가 읽는 필드를 개별 코드로 점검 */
    gets_(b, T_KEY1, h_card, sizeof(h_card));
    if (h_card[0] == 0) { setl(b, T_RC, 8801L); Bchg(b, T_MSG, 0, "카드번호 필수 누락", 0L); userlog("[%s] 입력검증 실패: 카드번호 누락", svcname); FAIL(b); }

    /* 2) 한도 마스터 조회부 */
    EXEC SQL SELECT daily_limit, used_amount, temp_limit, status
        INTO :h_limit, :h_used, :h_temp, :h_status
        FROM card_limit WHERE card_no = :h_card;
    if (sqlca.sqlcode == 100) { setl(b, T_RC, 41L);
        Bchg(b, T_MSG, 0, "카드 한도원장 미존재", 0L);
        userlog("[%s] 원장없음 card=%s", svcname, h_card); FAIL(b); }
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 42L); FAIL(b); }
    avail = h_limit + h_temp - h_used;

    /* 3) 한도 판정 분기부: 상태/구간별 가용성 산출 */
    if (strcmp(h_status, "ACTIVE") != 0) {
        rc = 8; strcpy(res_status, "BLOCK"); strcpy(outmsg, "카드상태 비활성");
    } else if (avail <= 0) {
        rc = 2; strcpy(res_status, "FULL"); strcpy(outmsg, "가용한도 소진");
    } else if (h_amount > avail) {
        rc = 1; strcpy(res_status, "SHORT"); strcpy(outmsg, "요청액 > 가용한도");
    } else {
        rc = 0; strcpy(res_status, "OK");
    }

    /* 4) 사용률 본처리부: bps 환산 + 잔여 산정 */
    h_v1 = (h_limit > 0) ? (h_used * 10000L / h_limit) : 0L;
    h_v2 = avail;
    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, 43L); FAIL(b); }
    if (h_v1 >= 9000L) { rc = 7; strcpy(res_status, "ALERT"); }

    /* 5) 재집계부: 당일 거절건도 함께 파악(감사) */
    EXEC SQL SELECT count(*) INTO :h_dec_cnt FROM au_authorization
        WHERE card_no = :h_card AND biz_date = :h_bizdate AND status = 'D';
    if (sqlca.sqlcode < 0) h_dec_cnt = 0;
    /* 6) 요약부: 가용/사용률 산출값 확정 */
    outv = avail; h_temp = h_temp;
    /* 7) 예외/보상: 조회 전용 - 상태 변경 없음 */
    userlog("[%s] 한도점검 card=%s avail=%ld rc=%ld", svcname, h_card, avail, rc);

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

    /* ---------------------------------------------------------------
     * 업무체인: LIMIT_OVER_ALERT → CM_LOG (한도초과 경보 공통로그 적재)
     * XA 규율: 하위 서비스는 같은 글로벌 트랜잭션의 '다른 브랜치'라서 이 서비스가
     *   방금 INSERT/UPDATE 한 미커밋 행을 볼 수 없다. 그러므로 하위가 필요로 하는
     *   값은 모두 전문(T_*)에 실어 넘기고, 하위 실패 시에는 T_RC/T_MSG 를 채운 뒤
     *   FAIL(b) → tpreturn(TPFAIL) 로 체인 전체를 롤백한다(별도 보상 불필요).
     * --------------------------------------------------------------- */
    if (h_card[0] && strlen(h_bizdate) == 10 && h_bizdate[4] == '-') {
        long ch_len = 0, ch_rc = 0;
        char ch_str1[64], ch_line[200];
        ch_str1[0] = 0;
        gets_(b, T_STR1, ch_str1, sizeof(ch_str1));      /* 원 전문값 보존 */
        snprintf(ch_line, sizeof(ch_line), "한도초과경보 card=%s 한도=%ld 사용=%ld", h_card, h_limit, h_used);
        Bchg(b, T_SVCNAME, 0, (char *)svcname, 0L);
        Bchg(b, T_MSG, 0, ch_line, 0L);
        Bchg(b, T_BIZDATE, 0, h_bizdate, 0L);
        Bchg(b, T_STR1, 0, "AU_ALERT", 0L);
        setl(b, T_AMOUNT, (h_amount > 0 ? h_amount : 0L));
        userlog("[%s] 체인 호출 → CM_LOG [%s]", svcname, ch_line);
        if (tpcall("CM_LOG", (char *)b, 0L, (char **)&b, &ch_len, 0L) < 0) {
            userlog("[%s] tpcall(CM_LOG) 실패: %s", svcname, tpstrerror(tperrno));
            setl(b, T_RC, 7131L);
            Bchg(b, T_MSG, 0, "한도초과 경보 체인 실패", 0L);
            FAIL(b);
        }
        ch_rc = getl(b, T_RC);                 /* 공통로그 결과 반영 */
        Bchg(b, T_STR1, 0, ch_str1, 0L);
        setl(b, T_AMOUNT, h_amount);
        Bchg(b, T_MSG, 0, ch_line, 0L);
        userlog("[%s] 체인 완료 CM_LOG rc=%ld id=%ld", svcname, ch_rc, getl(b, T_ID1));
    }

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