/*
 * FRAUD_RULE_AMT.pgc - au 모듈 서비스 FRAUD_RULE_AMT (one service = one file).
 * 이상거래 고액 룰: 거래금액이 카드의 30일 평균 사용액의 3배를 넘고
 * 절대금액 100만원 이상이면 배수 기반 점수를 산출해 부정거래 로그에
 * 적발 기록을 남긴다. 평균 이력이 없는 신규 카드는 룰을 건너뛴다.
 * Runs on the caller's XA branch (no EXEC SQL CONNECT); advertised by au_svr.
 */
#include "FRAUD_RULE_AMT.h"

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

    /* 공통 컨텍스트 로드 (요청에 없으면 공백/0) */
    gets_(b, T_MERCHANT, h_merch, sizeof(h_merch));

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

    /* 2) 위험 컨텍스트 조회부: 카드 최근 승인 실적 확보 */
    EXEC SQL SELECT count(*), coalesce(sum(amount),0), coalesce(avg(amount),0)::bigint
        INTO :h_cnt, :h_sum, :h_v2
        FROM au_authorization WHERE card_no = :h_card AND biz_date = :h_bizdate;
    if (sqlca.sqlcode < 0) { setl(b, T_RC, 81L); FAIL(b); }
    EXEC SQL SELECT count(DISTINCT merchant_id) INTO :h_v1 FROM au_authorization
        WHERE card_no = :h_card AND biz_date = :h_bizdate;
    if (sqlca.sqlcode < 0) h_v1 = 0;

    /* 3) 룰 스코어링 분기부: 금액/빈도/집중도별 가점 */
    h_score = 0;
    if (h_amount >= 1000000) h_score += 50;
    if (h_amount > h_v2 * 3 && h_v2 > 0) h_score += 40;
    if (h_cnt > 10) h_score += 30;
    if (h_v1 >= 5) h_score += 25;
    h_score += (h_cnt * 2);
    if (h_score >= 90) { rc = 2; strcpy(res_status, "BLOCK"); strcpy(h_reason, "HIGH_RISK"); }
    else if (h_score >= 50) { rc = 1; strcpy(res_status, "REVIEW"); strcpy(h_reason, "WATCH"); }
    else { rc = 0; strcpy(res_status, "PASS"); strcpy(h_reason, "OK"); }

    /* 4) 본처리부: 위험 판정시 부정 로그 채번·적재 */
    if (h_score >= 50) {
        h_id = audb_insert_fraud(h_auth, h_card, h_score, h_reason, h_bizdate);
        if (h_id < 0) { setl(b, T_RC, 83L); Bchg(b, T_MSG, 0, "부정로그 적재 실패", 0L); FAIL(b); }
    }
    /* 5) 재집계부: 카드 부정 이력 종합(건수/최고점) */
    EXEC SQL SELECT count(*), coalesce(max(score),0) INTO :h_appr_cnt, :h_v3
        FROM au_fraud_log WHERE card_no = :h_card;
    if (sqlca.sqlcode < 0) { h_appr_cnt = 0; h_v3 = 0; }
    /* 6) 요약부: 종합 위험도 확정 */
    if (h_v3 > h_score) h_score = h_v3;
    /* 7) 예외/보상: 부정로그 적재 실패는 XA 롤백 */
    outv = h_score;
    userlog("[%s] 룰판정 card=%s score=%ld rc=%ld", svcname, h_card, h_score, rc);

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

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