/*
 * acq_extsw.c - 외부 카드망 시뮬레이터 (밴사/VAN/발급망 대역).
 *   실제 카드 매입시스템이 소켓 위로 ISO 8583 전문을 주고받는 상대 endpoint 를
 *   재현한다. 프레이밍: 4-hex 길이접두 + ISO 8583 전문(hex).
 *   수신 0200 → 파싱 → 승인판정 → 0210 응답 조립(DE38 승인번호, DE39 응답코드).
 *   금액 임계/난수로 일부 거절(05)·타임아웃 시늉. 마이그레이션시 이 소켓·프레이밍·
 *   전문 상태처리가 Spring 으로 옮기기 가장 까다로운 지점.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "acq_iso8583.h"

static int read_n(int fd, char *buf, int n) {
    int got = 0;
    while (got < n) {
        int r = read(fd, buf + got, n - got);
        if (r <= 0) return -1;
        got += r;
    }
    return got;
}

/* 승인번호 6자리 생성(STAN 기반 결정적) */
static void gen_approval(const char *stan, char *out) {
    unsigned h = 5381; const char *p = stan;
    while (*p) h = ((h << 5) + h) + (unsigned char)*p++;
    snprintf(out, 7, "%06u", h % 1000000);
}

static void handle(int cfd) {
    char lenhex[5], wire[4096];
    if (read_n(cfd, lenhex, 4) != 4) return;
    lenhex[4] = 0;
    int len = (int)strtol(lenhex, NULL, 16);
    if (len <= 0 || len >= (int)sizeof(wire)) return;
    if (read_n(cfd, wire, len) != len) return;
    wire[len] = 0;

    iso_msg_t req; int c = iso_parse(wire, &req);
    if (c < 0) return;

    /* 승인판정: 금액 파싱, 임계 초과·STAN 말미로 일부 거절 */
    long amt = atol(iso_get(&req, 4));
    const char *stan = iso_get(&req, 11);
    int decline = (amt > 490000000) || ((stan[strlen(stan) > 0 ? strlen(stan) - 1 : 0] == '7') && amt > 3000000);

    iso_msg_t rsp; iso_init(&rsp);
    /* 응답 MTI = 요청 MTI + 0x10 (0200->0210, 0800->0810, 0420->0430) */
    strncpy(rsp.mti, req.mti, 4); rsp.mti[3] = (req.mti[3] == '0') ? '0' : req.mti[3];
    if (req.mti[2] == '0') rsp.mti[2] = '1';        /* 20x0 -> 21x0 */
    else rsp.mti[2] = (req.mti[2] == '2') ? '3' : req.mti[2];
    rsp.mti[4] = 0;

    /* 응답 필드: 요청 에코 + 응답코드/승인번호 */
    iso_set(&rsp, 2,  iso_get(&req, 2));
    iso_set(&rsp, 3,  iso_get(&req, 3));
    iso_set(&rsp, 4,  iso_get(&req, 4));
    iso_set(&rsp, 7,  iso_get(&req, 7));
    iso_set(&rsp, 11, stan);
    iso_set(&rsp, 41, iso_get(&req, 41));
    iso_set(&rsp, 42, iso_get(&req, 42));
    iso_set(&rsp, 49, iso_get(&req, 49));
    if (decline) {
        iso_set(&rsp, 39, "05");                    /* 거절 */
    } else {
        char appr[8]; gen_approval(stan, appr);
        iso_set(&rsp, 38, appr);
        iso_set(&rsp, 39, "00");                    /* 승인 */
    }
    /* RRN(DE37) = 전송일시+STAN 조합 */
    char rrn[16]; snprintf(rrn, sizeof(rrn), "%.6s%.6s", iso_get(&req, 7), stan);
    iso_set(&rsp, 37, rrn);

    char out[4096]; int ol = iso_build(&rsp, out, sizeof(out));
    if (ol < 0) return;
    char frame[4200]; int fl = snprintf(frame, sizeof(frame), "%04X%s", ol, out);
    write(cfd, frame, fl);
}

int main(int argc, char **argv) {
    int port = (argc > 1) ? atoi(argv[1]) : 9500;
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1; setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port);
    if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
    if (listen(sfd, 32) < 0) { perror("listen"); return 1; }
    fprintf(stderr, "acq_extsw: 외부 카드망 시뮬레이터 listening on :%d\n", port);
    for (;;) {
        int cfd = accept(sfd, NULL, NULL);
        if (cfd < 0) continue;
        handle(cfd);
        close(cfd);
    }
    return 0;
}
