package com.bb.front;

import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;

import org.apache.http.conn.ConnectTimeoutException;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.ResourceAccessException;

import com.bb.model.NexusResponse;
import com.bb.model.PrimeResponse;
import com.bb.service.AsyncSiteService;
import com.bb.service.CallBackService;
import com.bb.service.SiteService;

import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequestMapping("/api/callback/dpcore")
@RequiredArgsConstructor
@SecurityRequirement(name = "Authorization")
@EnableScheduling
@RestController
public class CallBackDpcoreController {

	@Autowired
	SiteService siteService;

	@Autowired
	CallBackService callBackService;

	@Autowired
	AsyncSiteService asyncCallBackService;


	@ResponseBody
	@RequestMapping(path = "/balance")
	public ResponseEntity<PrimeResponse> balance(HttpServletRequest request, @RequestParam("username") String primeUsername) throws Exception {
		PrimeResponse response = null;

		long siteIdx = 0L;
		try {
			siteIdx = Long.parseLong(primeUsername.substring(0, 3), 16);
		} catch (NumberFormatException e) {
		    log.error("#-CB::DPCORE::balance::Invalid username format (hex expected): {}", primeUsername);
		    log.error("#-CB::DPCORE::balance::Error Msg: NOT_FOUND_USER");
			response = new PrimeResponse();
			response.setBalance(0);
			return new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
		}
		String memberId = primeUsername.substring(3, primeUsername.length());
		final String LOG_PREFIX = "#-CB::DPCORE::balance::"+memberId+":::";
		log.info(LOG_PREFIX+ "Request::" + primeUsername);

		try {
			//사이트정보
			HashMap sParam = new HashMap();
			sParam.put("siteIdx", siteIdx);
			sParam.put("memberId", memberId);
			log.info(LOG_PREFIX + "SITE_API_INFO_PARAM::"+sParam.toString());
			HashMap siteApiInfo = siteService.getSiteApiInfo(sParam);
			if(siteApiInfo == null) {
				log.error(LOG_PREFIX+ "Error Msg: NOT_FOUND_SITE_INFO");
				response = new PrimeResponse();
				response.setBalance(0);
				return new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
			}

			if(siteApiInfo.get("memberIdx") == null) {
				log.error(LOG_PREFIX+ "존재하지 않는 회원");
				response = new PrimeResponse();
				response.setBalance(0);
				return new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
			}

			// ##--isTransfer 처리-##
			int balance = 0;
			if("N".equals(siteApiInfo.get("isTransfer").toString())) {
				log.info(LOG_PREFIX + "PLAY_SEAMLESS");
				JSONObject member = new JSONObject();
				member.put("userId", memberId);
				log.info(LOG_PREFIX+ "balance Req ::"+ member.toString());
				JSONObject resData  = callBackService.getBalance(siteApiInfo, member);
				if(resData == null) {
					log.error(LOG_PREFIX+ "Error Msg: BALANCE_CALLBACK_ERROR");
					response = new PrimeResponse();
					response.setBalance(0);
					return new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
				}
				log.info(LOG_PREFIX+ "balance Res ::"+ resData.toString());
				balance = resData.getInt("balance");
			} else {
				log.info(LOG_PREFIX + "PLAY_TRANSFER");
				HashMap balanceMap = new HashMap<>();
				balanceMap.put("siteIdx", siteIdx);
				balanceMap.put("memberId", memberId);
				balance = siteService.getUserBalance(balanceMap);

				if(siteApiInfo.get("siteCbUrl") != null && !"".equals(siteApiInfo.get("siteCbUrl").toString())) {
					JSONObject member = new JSONObject();
					member.put("userId", memberId);
					member.put("balance", balance);
					asyncCallBackService.asyncBalance(LOG_PREFIX, siteApiInfo, member);
				}
			}

			response = new PrimeResponse();
			response.setBalance(balance);

		} catch (ResourceAccessException rae) {
            if(rae.getCause() instanceof ConnectTimeoutException) {
            	log.error(LOG_PREFIX+ "[ConnectTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof SocketTimeoutException) {
            	log.error(LOG_PREFIX+ "[SocketTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof InterruptedException) {
            	log.error(LOG_PREFIX+ "[InterruptedException]"+rae.getMessage());
            }

            response = new PrimeResponse();
            response.setBalance(0);
            return new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			response = new PrimeResponse();
			response.setBalance(0);
			return new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		}

		ResponseEntity<PrimeResponse> res = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);
		log.info(LOG_PREFIX+ "Response::" + response.toString());

		return res;
	}


	@ResponseBody
	@RequestMapping(path = "/changeBalance")
	public ResponseEntity<PrimeResponse> changeBalance(HttpServletRequest request, @RequestBody net.sf.json.JSONObject requestBody) throws Exception {
		ResponseEntity<PrimeResponse> resEntity = null;

		String primeUsername = requestBody.getString("username");
		long siteIdx = 0L;
		try {
			siteIdx = Long.parseLong(primeUsername.substring(0, 3), 16);
		} catch (NumberFormatException e) {
		    log.error("#-CB::DPCORE::balance::Invalid username format (hex expected): {}", primeUsername);
		    log.error("#-CB::DPCORE::balance::Error Msg: NOT_FOUND_USER");
		    PrimeResponse response = new PrimeResponse();
			resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
			return resEntity;
		}
		String memberId = primeUsername.substring(3, primeUsername.length());
		net.sf.json.JSONObject transaction = requestBody.getJSONObject("transaction");
		net.sf.json.JSONObject game = transaction.getJSONObject("details").getJSONObject("game");
		String transactionId = transaction.getString("id");
		String transactionType = transaction.getString("type");

		final String LOG_PREFIX = "#-CB::DPCORE::changeBalance::"+memberId+"::"+transactionId+"::"+transactionType+":::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

		try {
			// "processed_at":"2025-01-16T09:25:23.000000Z"
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS");
			String betDateTimeStr = transaction.getString("processed_at");
			log.info(LOG_PREFIX+ "========== BF_betDateTimeStr::" + betDateTimeStr);
			betDateTimeStr = betDateTimeStr.substring(0, 19);
			betDateTimeStr = betDateTimeStr.replace("T", " ");
			log.info(LOG_PREFIX+ "========== MF_betDateTimeStr::" + betDateTimeStr);
			betDateTimeStr = makeEdate(LOG_PREFIX, betDateTimeStr, 32400000);
			log.info(LOG_PREFIX+ "========== AF_betDateTimeStr::" + betDateTimeStr);

			//사이트정보
			HashMap sParam = new HashMap();
			sParam.put("siteIdx", siteIdx);
			sParam.put("memberId", memberId);
			sParam.put("vendor", "dpcore");
			sParam.put("vendorGameCode", game.getString("vendor_code"));
			log.info(LOG_PREFIX + "getSiteVendorInfo::"+sParam.toString());
			HashMap<String,String> siteVendorInfo = siteService.getSiteVendorInfo(sParam);
			if(siteVendorInfo == null) {
				log.error(LOG_PREFIX+ "존재하지 않는 회원입니다");
				PrimeResponse response = new PrimeResponse();
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
				return resEntity;
			}

			if(transactionType.equals("bet")) {
				int amount = Math.abs(requestBody.getInt("amount"));
				resEntity = callBet(LOG_PREFIX, siteIdx, memberId, transaction, betDateTimeStr, siteVendorInfo, amount);

			} else if(transactionType.equals("win")) {
				int amount = Math.abs(requestBody.getInt("amount"));
				resEntity = callWin(LOG_PREFIX, siteIdx, memberId, transaction, betDateTimeStr, siteVendorInfo, amount, requestBody);

			} else if(transactionType.equals("cancel")) {
				int amount = Math.abs(requestBody.getInt("amount"));
				resEntity = callCancel(LOG_PREFIX, siteIdx, memberId, transaction, betDateTimeStr, siteVendorInfo, amount);

			} else if(transactionType.equals("adjust")) {
				int amount = requestBody.getInt("amount");
				//resEntity = callAdjust(LOG_PREFIX, siteIdx, memberId, transaction, betDateTimeStr, siteVendorInfo, amount);

			} else if(transactionType.equals("promo_win")) {
				int amount = Math.abs(requestBody.getInt("amount"));
				//resEntity = callPromoWin(LOG_PREFIX, siteIdx, memberId, transaction, betDateTimeStr, siteVendorInfo, amount);

			} else {
				log.error(LOG_PREFIX+ "unknown_transaction_type");
				PrimeResponse response = new PrimeResponse();
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
				return resEntity;
			}

		} catch (ResourceAccessException rae) {
            if(rae.getCause() instanceof ConnectTimeoutException) {
            	log.error(LOG_PREFIX+ "[ConnectTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof SocketTimeoutException) {
            	log.error(LOG_PREFIX+ "[SocketTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof InterruptedException) {
            	log.error(LOG_PREFIX+ "[InterruptedException]"+rae.getMessage());
            }

            PrimeResponse response = new PrimeResponse();
			resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
			return resEntity;

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			PrimeResponse response = new PrimeResponse();
			resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
			return resEntity;
		}

		log.info(LOG_PREFIX+ "Response::" + resEntity.toString());

		return resEntity;
	}

	private ResponseEntity<PrimeResponse> callBet(String LOG_PREFIX, long siteIdx, String memberId,
			net.sf.json.JSONObject transaction, String betDateTimeStr, HashMap<String,String> siteVendorInfo, int amount) {

		LOG_PREFIX = LOG_PREFIX + "callBet:::";
		ResponseEntity<PrimeResponse> resEntity = null;
		PrimeResponse response = new PrimeResponse();

		// ##--isTransfer 처리-##
		final String IS_TRANSFER = siteVendorInfo.get("isTransfer").toString();

		try {
			net.sf.json.JSONObject details = transaction.getJSONObject("details");
			net.sf.json.JSONObject game = details.getJSONObject("game");

			String gameType = "";
			if(game.has("gameType")) {
				gameType = game.getString("gameType").replaceAll(" ", "").toLowerCase();
			} else {
				gameType = game.getString("type").replaceAll(" ", "").toLowerCase();
			}

			// gameType 변환 (하부로 전달 시 blackjack으로 통일)
			if ("classicbetstackerbj".equals(gameType) || "freebet".equals(gameType)
					|| "funfun21scalablebj".equals(gameType) || "easybj".equals(gameType)
					|| "lightningscalablebj".equals(gameType) || "classicfreebet".equals(gameType)
					|| gameType.contains("blackjack")) {
				gameType = "blackjack";
			}

			String betKey = game.getString("id")+"#"+transaction.getString("referer_id");
			String roundId = betKey;
			String gameName = game.getString("title");

			String tableId = game.getString("game_code");
			String targetTableId = tableId.replaceAll(" ", "").toLowerCase();

			String banGameType = siteVendorInfo.get("banGameType").toString().replaceAll(" ", "").toLowerCase();
			String banGameId = siteVendorInfo.get("banGameId").toString().replaceAll(" ", "").toLowerCase();

			if(!"".equals(gameType) && !"".equals(banGameType) && banGameType.contains(gameType+"|")) {
				log.error(LOG_PREFIX+ "Ban Game Type : " + gameType);
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);	// 501
				return resEntity;
			}

			if(!"".equals(targetTableId) && !"".equals(banGameId) && banGameId.contains(targetTableId+"|")) {
				log.error(LOG_PREFIX+ "Ban Game Id : " + banGameId);
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);	// 502
				return resEntity;
			}

			String siteId = siteVendorInfo.get("siteId").toString();
			long memberIdx = Long.parseLong(siteVendorInfo.get("memberIdx").toString());
			String vendorApiId = siteVendorInfo.get("vendorApiId").toString();

			String tranId = transaction.getString("id");
			String betId = transaction.getString("referer_id");
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();

			/*
			int cnt1 = siteService.getTranIdCheck(tranId);
			if(cnt1 != 0) {
				// 중복베팅
				log.error(LOG_PREFIX+ "중복베팅");
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				return resEntity;
			}
			*/

			// ##--isTransfer 처리-##
			if(IS_TRANSFER.equals("Y")) {
				// tranId 중복체크
				int cntTr = siteService.getTranIdCheck2(tranId);
				if(cntTr > 0) {
					HashMap balanceMap = new HashMap<>();
					balanceMap.put("siteIdx", siteIdx);
					balanceMap.put("memberId", memberId);
					int balance = siteService.getUserBalance(balanceMap);
					response.setBalance(balance);
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					return new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);
				}
			}

			if(siteVendorInfo.get("lastInVendorIdx")==null || "0".equals(siteVendorInfo.get("lastInVendorIdx").toString())) {
				HashMap memParam = new HashMap<>();
				memParam.put("siteIdx", siteIdx);
				memParam.put("memberIdx", memberIdx);
				memParam.put("lastInVendorIdx", Integer.parseInt(siteVendorInfo.get("vendorIdx").toString()));
				int updResult = siteService.updMemLastVendorIdx(memParam);
			} else {
				int lastInVendorIdx = Integer.parseInt(siteVendorInfo.get("lastInVendorIdx").toString());
				if(lastInVendorIdx != vendorIdx) {
					log.error(LOG_PREFIX+ "VendorIdx doesn't match");
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
					return resEntity;
				}
			}

			long siteCredit = Long.parseLong(siteVendorInfo.get("credit").toString());
			// ##--isTransfer 처리-##
			if(siteCredit < amount && IS_TRANSFER.equals("N")) {
				log.error(LOG_PREFIX+ "NO_SITE_CREDIT");
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
				return resEntity;
			}

			// 최대 베팅 금액 처리 (userMaxBet 우선 적용)
			long siteMaxBet = Long.parseLong(siteVendorInfo.get("siteMaxBet").toString());
			long userMaxBet = 0;
			if(siteVendorInfo.get("userMaxBet") != null && !siteVendorInfo.get("userMaxBet").toString().isEmpty()) {
				try {
					userMaxBet = Long.parseLong(siteVendorInfo.get("userMaxBet").toString());
				} catch (NumberFormatException e) {
					userMaxBet = 0;
				}
			}

			// 최종 maxBet 결정: userMaxBet이 0이면 siteMaxBet 사용, 0이 아니면 userMaxBet 사용
			long finalMaxBet = (userMaxBet > 0) ? userMaxBet : siteMaxBet;
			log.info(LOG_PREFIX+ "MaxBet Info - siteMaxBet: " + siteMaxBet + ", userMaxBet: " + userMaxBet + ", finalMaxBet: " + finalMaxBet);

			if(finalMaxBet < amount) {
				log.error(LOG_PREFIX+ "MAX_BET_AMOUNT_OVER");
				log.error(LOG_PREFIX+ "finalMaxBet : " + finalMaxBet +", amount : " + amount);
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.BAD_REQUEST);
				return resEntity;
			}

			boolean isParsing = false;
			log.info(LOG_PREFIX + "BF_isParsing::"+isParsing);
			Boolean genuine = false;
			if(transaction.has("genuine")) {
				log.info(LOG_PREFIX + "transaction.has(\"genuine\") is true");
				genuine = transaction.getBoolean("genuine");
				log.info(LOG_PREFIX + "transaction.genuine::"+genuine.booleanValue());
				// genuine = true : 정품
				// genuine = null or false : 하이브리드(파싱)
				if(genuine != null && genuine.booleanValue() == true) {
					isParsing = false;
					if(game.has("id")) {
						roundId = game.getString("id");
					}
				} else {
					isParsing = true;
				}
			} else {
				log.info(LOG_PREFIX + "transaction.has(\"genuine\") is false");
				isParsing = true;
			}
			log.info(LOG_PREFIX + "AF_isParsing::"+isParsing);

			HashMap tranParam = new HashMap();
			tranParam.put("tranId", tranId);
			tranParam.put("refId",  betId);
			tranParam.put("siteIdx", siteIdx);
			tranParam.put("siteId", siteId);
			tranParam.put("memberIdx", memberIdx);
			tranParam.put("memberId", memberId);
			tranParam.put("vendorCetegory", vendorCetegory);
			tranParam.put("vendorIdx", vendorIdx);
			tranParam.put("vendorCode", "dpcore");
			tranParam.put("vendorTranKey", roundId);
			tranParam.put("gameType", gameType);
			tranParam.put("gameId", tableId);
			tranParam.put("gameIdx", gameName);
			tranParam.put("tranType", "debit");
			tranParam.put("depositAmt", amount);
			tranParam.put("creditAmt", "0");
			tranParam.put("isCancel", "N");
			if(isParsing) {
				tranParam.put("isTie", "P");
			} else {
				tranParam.put("isTie", "N");
			}
			tranParam.put("apiStatus", 0);
			tranParam.put("vendorApiId", vendorApiId);

			JSONObject callBackObj = new JSONObject();
			callBackObj.put("betId", betId);
			callBackObj.put("tranId", tranId);
			callBackObj.put("betKey", roundId);
			callBackObj.put("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "dpcore");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			callBackObj.put("gameId", tableId);
			callBackObj.put("gameType", game.getString("type"));
			callBackObj.put("tranType", "debit");
			callBackObj.put("debit", amount);
			callBackObj.put("credit", 0);
			callBackObj.put("isCancel", 0);
			callBackObj.put("isBonus", 0);
			if(isParsing) {
				callBackObj.put("isData", "Y");
			} else {
				callBackObj.put("isData", "N");
			}
			callBackObj.put("requestAt", betDateTimeStr);

			// ##--isTransfer 처리-##
			if(IS_TRANSFER.equals("N")) {
				log.info(LOG_PREFIX + "PLAY_SEAMLESS");
				log.info(LOG_PREFIX + "-----INSERT_BET_START-----");
				siteService.commonBetinsert(tranParam);
				log.info(LOG_PREFIX + "-----INSERT_BET_END-----");

				log.info(LOG_PREFIX + "SEND_BET request body: " + callBackObj.toString());
				JSONObject resData = callBackService.changeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				log.info(LOG_PREFIX + "SEND_BET status code: " + resData.getLong("result_code"));
				log.info(LOG_PREFIX + "SEND_BET response body: " + resData.toString());

				if(resData.getLong("result_code") == 0) {
					tranParam.put("balance", resData.getInt("balance"));
					tranParam.put("isTransfer", IS_TRANSFER);
					int res = siteService.updateCbApi(tranParam);
					log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

					int balance = resData.getInt("balance");
					response.setBalance(balance);
					response.setBeforeBalance(balance+amount);
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);
				} else if(resData.getLong("result_code") == 98) {
					int balance = resData.getInt("balance");
					response.setBalance(balance);
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);
				} else if(resData.getLong("result_code") == 80) {
					int balance = resData.getInt("balance");
					response.setBalance(balance);
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				} else {
					String msg = "";
					if(resData.has("error_msg")) msg = resData.getString("error_msg");
	 				log.error(LOG_PREFIX+ msg);
	 				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				}
			} else {
				log.info(LOG_PREFIX + "PLAY_TRANSFER");
				HashMap balanceMap = new HashMap<>();
				balanceMap.put("siteIdx", siteIdx);
				balanceMap.put("memberId", memberId);
				int balance = siteService.getUserBalance(balanceMap);
				int afBalance = balance - amount;

				if(balance < amount) {
					response.setBalance(balance);
					log.error(LOG_PREFIX+ "Response::금액을 확인하여 주세요::" + response.toString());
					return new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				}

				log.info(LOG_PREFIX + "-----INSERT_BET_START-----");
				siteService.commonBetinsert(tranParam);
				log.info(LOG_PREFIX + "-----INSERT_BET_END-----");

				tranParam.put("balance", afBalance);
				tranParam.put("preBalance", balance);
				tranParam.put("isTransfer", IS_TRANSFER);
				int res = siteService.updateCbApi(tranParam);
				log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

				response.setBalance(afBalance);
				response.setBeforeBalance(balance);
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);

				if(siteVendorInfo.get("siteCbUrl") != null && !"".equals(siteVendorInfo.get("siteCbUrl").toString())) {
					callBackObj.put("balance", afBalance);
					asyncCallBackService.asyncChangeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				}
			}

		} catch (ResourceAccessException rae) {
            if(rae.getCause() instanceof ConnectTimeoutException) {
            	log.error(LOG_PREFIX+ "[ConnectTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof SocketTimeoutException) {
            	log.error(LOG_PREFIX+ "[SocketTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof InterruptedException) {
            	log.error(LOG_PREFIX+ "[InterruptedException]"+rae.getMessage());
            }

            resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		}

		return resEntity;
	}


	private ResponseEntity<PrimeResponse> callWin(String LOG_PREFIX, long siteIdx, String memberId, net.sf.json.JSONObject transaction, 
			String betDateTimeStr, HashMap<String,String> siteVendorInfo, int amount, net.sf.json.JSONObject requestBody) {

		LOG_PREFIX = LOG_PREFIX + "callWin:::";
		ResponseEntity<PrimeResponse> resEntity = null;
		PrimeResponse response = new PrimeResponse();

		// ##--isTransfer 처리-##
		final String IS_TRANSFER = siteVendorInfo.get("isTransfer").toString();

		try {
			net.sf.json.JSONObject details = transaction.getJSONObject("details");
			net.sf.json.JSONObject game = details.getJSONObject("game");

			String gameType = "";
			if(game.has("gameType")) {
				gameType = game.getString("gameType").replaceAll(" ", "").toLowerCase();
			} else {
				gameType = game.getString("type").replaceAll(" ", "").toLowerCase();
			}

			// gameType 변환 (하부로 전달 시 blackjack으로 통일)
			if ("classicbetstackerbj".equals(gameType) || "freebet".equals(gameType)
					|| "funfun21scalablebj".equals(gameType) || "easybj".equals(gameType)
					|| "lightningscalablebj".equals(gameType) || "classicfreebet".equals(gameType)
					|| gameType.contains("blackjack")) {
				gameType = "blackjack";
			}

			String tableId = game.getString("game_code");
			String betKey = game.getString("id")+"#"+transaction.getString("referer_id");
			String roundId = betKey;
			String siteId = siteVendorInfo.get("siteId").toString();
			long memberIdx = Long.parseLong(siteVendorInfo.get("memberIdx").toString());
			String vendorApiId = siteVendorInfo.get("vendorApiId").toString();

			String tranId = transaction.getString("id");
			String betId = transaction.getString("referer_id");
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();

			/*
			int cnt1 = siteService.getTranIdCheck(tranId);
			if(cnt1 != 0) {
				// 중복베팅
				log.error(LOG_PREFIX+ "중복베팅");
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				return resEntity;
			}
			*/

			// ##--isTransfer 처리-##
			if(IS_TRANSFER.equals("Y")) {
				// tranId 중복체크
				int cntTr = siteService.getTranIdCheck2(tranId);
				if(cntTr > 0) {
					HashMap balanceMap = new HashMap<>();
					balanceMap.put("siteIdx", siteIdx);
					balanceMap.put("memberId", memberId);
					int balance = siteService.getUserBalance(balanceMap);
					response.setBalance(balance);
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					return new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);
				}

				// 베팅 체크
				int cntBet = siteService.getBetIdCheck2(betId);
				if(cntBet == 0) {
					// 처리한 베팅 존재X
					log.error(LOG_PREFIX+ "처리한 베팅 존재X");
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
					return resEntity;
				}
			} else {
				int cnt2 = siteService.getBetIdCheck(betId);
				if(cnt2 == 0) {
					// 처리한 베팅 존재X
					log.error(LOG_PREFIX+ "처리한 베팅 존재X");
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
					return resEntity;
				}
			}

			boolean isParsing = false;
			log.info(LOG_PREFIX + "BF_isParsing::"+isParsing);
			Boolean genuine = false;
			if(transaction.has("genuine")) {
				log.info(LOG_PREFIX + "transaction.has(\"genuine\") is true");
				genuine = transaction.getBoolean("genuine");
				log.info(LOG_PREFIX + "transaction.genuine::"+genuine.booleanValue());
				// genuine = true : 정품
				// genuine = null or false : 하이브리드(파싱)
				if(genuine != null && genuine.booleanValue() == true) {
					isParsing = false;
					if(game.has("id")) {
						roundId = game.getString("id");
					}
				} else {
					isParsing = true;
				}
			} else {
				log.info(LOG_PREFIX + "transaction.has(\"genuine\") is false");
				isParsing = true;
			}
			log.info(LOG_PREFIX + "AF_isParsing::"+isParsing);

			String isDebitParse = siteService.getDebitIsParse(betId);
			log.info(LOG_PREFIX + "isDebitParse::"+isDebitParse);
			if(isDebitParse == null) {
				log.error(LOG_PREFIX+ "처리한 베팅 존재X");
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				return resEntity;
			} else {
				if("P".equals(isDebitParse)) {
					isParsing = true;
				}
			}
			
			HashMap tranParam = new HashMap();
			tranParam.put("tranId", tranId);
			tranParam.put("refId",  betId);
			tranParam.put("siteIdx", siteIdx);
			tranParam.put("siteId", siteId);
			tranParam.put("memberIdx", memberIdx);
			tranParam.put("memberId", memberId);
			tranParam.put("vendorCetegory", vendorCetegory);
			tranParam.put("vendorIdx", vendorIdx);
			tranParam.put("vendorCode", "dpcore");
			tranParam.put("vendorTranKey", roundId);
			tranParam.put("gameType", gameType);
			tranParam.put("gameId", tableId);
			tranParam.put("gameIdx", game.getString("title"));
			tranParam.put("tranType", "credit");
			tranParam.put("depositAmt", "0");
			tranParam.put("creditAmt", amount);
			tranParam.put("isCancel", "N");
			if(isParsing) {
				tranParam.put("isTie", "P");
			} else {
				tranParam.put("isTie", "N");
			}
			tranParam.put("apiStatus", 0);
			tranParam.put("vendorApiId", vendorApiId);

			JSONObject callBackObj = new JSONObject();
			callBackObj.put("betId", betId);
			callBackObj.put("tranId", tranId);
			callBackObj.put("betKey", roundId);
			callBackObj.put("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "dpcore");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", game.getString("title"));
			callBackObj.put("gameId", tableId);
			callBackObj.put("gameType", gameType);
			callBackObj.put("tranType", "credit");
			callBackObj.put("debit", 0);
			callBackObj.put("credit", amount);
			callBackObj.put("isCancel", 0);
			callBackObj.put("isBonus", 0);
			if(isParsing) {
				callBackObj.put("isData", "Y");
			} else {
				callBackObj.put("isData", "N");
			}
			callBackObj.put("requestAt", betDateTimeStr);
			
			/**
			 * issueFlag : flag=Y 유저 이면 결과 강제취소처리
			 */
			String issueFlag = siteVendorInfo.get("issueFlag").toString();
			if("Y".equals(issueFlag)) {
				// ##-- issueFlag 전처리
				if(amount <= 0) { // 여러가지 경우를 테스트함
					int betAmount = Integer.parseInt(siteService.getBetAmountByTranParam(tranParam));
					tranParam.put("creditAmt", betAmount);
					tranParam.put("isCancel", "Y");
					
					callBackObj.put("credit", betAmount);
					callBackObj.put("isCancel", 1);
				}
			}

			// ##--isTransfer 처리-##
			if(IS_TRANSFER.equals("N")) {
				log.info(LOG_PREFIX + "-----INSERT_RESULT_START-----");
				siteService.commonBetinsert(tranParam);
				log.info(LOG_PREFIX + "-----INSERT_RESULT_END-----");

				log.info(LOG_PREFIX + "SEND_RESULT request body: " + callBackObj.toString());
				JSONObject resData = callBackService.changeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				log.info(LOG_PREFIX + "SEND_RESULT status code: " + resData.getLong("result_code"));
				log.info(LOG_PREFIX + "SEND_RESULT response body: " + resData.toString());
				
				if(resData.getLong("result_code") == 0) {
					tranParam.put("balance", resData.getInt("balance"));
					tranParam.put("isTransfer", IS_TRANSFER);
					int res = siteService.updateCbApi(tranParam);
					log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

					int balance = resData.getInt("balance");
					response.setBalance(balance);
					response.setBeforeBalance(balance-amount);
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);

				} else if(resData.getLong("result_code") == 98) {
					int balance = resData.getInt("balance");
					response.setBalance(balance);
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);

				} else {
					String msg = "";
					if(resData.has("error_msg")) msg = resData.getString("error_msg");
	 				log.error(LOG_PREFIX+ msg);
	 				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				}
			} else {
				log.info(LOG_PREFIX + "PLAY_TRANSFER");
				HashMap balanceMap = new HashMap<>();
				balanceMap.put("siteIdx", siteIdx);
				balanceMap.put("memberId", memberId);
				int balance = siteService.getUserBalance(balanceMap);
				int afBalance = balance + amount;

				log.info(LOG_PREFIX + "-----INSERT_RESULT_START-----");
				siteService.commonBetinsert(tranParam);
				log.info(LOG_PREFIX + "-----INSERT_RESULT_END-----");

				tranParam.put("balance", afBalance);
				tranParam.put("preBalance", balance);
				tranParam.put("isTransfer", IS_TRANSFER);
				int res = siteService.updateCbApi(tranParam);
				log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

				response.setBalance(afBalance);
				response.setBeforeBalance(balance);
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);

				if(siteVendorInfo.get("siteCbUrl") != null && !"".equals(siteVendorInfo.get("siteCbUrl").toString())) {
					callBackObj.put("balance", afBalance);
					asyncCallBackService.asyncChangeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				}
			}
			
			/**
			 * issueFlag : flag=Y 유저 이면 결과 강제취소처리
			 */
			if("Y".equals(issueFlag)) {
				// ##-- issueFlag 후처리
				log.error(LOG_PREFIX+ "issueFlag=Y, 결과 강제취소처리");
				
				asyncCallBackService.asyncIssueLogSave(LOG_PREFIX, requestBody, tranParam);
				
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
			}

		} catch (ResourceAccessException rae) {
            if(rae.getCause() instanceof ConnectTimeoutException) {
            	log.error(LOG_PREFIX+ "[ConnectTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof SocketTimeoutException) {
            	log.error(LOG_PREFIX+ "[SocketTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof InterruptedException) {
            	log.error(LOG_PREFIX+ "[InterruptedException]"+rae.getMessage());
            }

            resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		}

		return resEntity;
	}


	private ResponseEntity<PrimeResponse> callCancel(String LOG_PREFIX, long siteIdx, String memberId,
			net.sf.json.JSONObject transaction, String betDateTimeStr, HashMap<String,String> siteVendorInfo, int amount) {

		ResponseEntity<PrimeResponse> resEntity = null;
		PrimeResponse response = new PrimeResponse();

		// ##--isTransfer 처리-##
		final String IS_TRANSFER = siteVendorInfo.get("isTransfer").toString();

		try {
			net.sf.json.JSONObject details = transaction.getJSONObject("details");
			net.sf.json.JSONObject game = details.getJSONObject("game");

			String gameType = "";
			if(game.has("gameType")) {
				gameType = game.getString("gameType").replaceAll(" ", "").toLowerCase();
			} else {
				gameType = game.getString("type").replaceAll(" ", "").toLowerCase();
			}

			// gameType 변환 (하부로 전달 시 blackjack으로 통일)
			if ("classicbetstackerbj".equals(gameType) || "freebet".equals(gameType)
					|| "funfun21scalablebj".equals(gameType) || "easybj".equals(gameType)
					|| "lightningscalablebj".equals(gameType) || "classicfreebet".equals(gameType)
					|| gameType.contains("blackjack")) {
				gameType = "blackjack";
			}

			String tableId = game.getString("game_code");
			String betKey = game.getString("id")+"#"+transaction.getString("referer_id");
			String roundId = betKey;

			String siteId = siteVendorInfo.get("siteId").toString();
			long memberIdx = Long.parseLong(siteVendorInfo.get("memberIdx").toString());
			String vendorApiId = siteVendorInfo.get("vendorApiId").toString();

			String tranId = transaction.getString("id");
			String betId = transaction.getString("referer_id");
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();

			/*
			int cnt1 = siteService.getTranIdCheck(tranId);
			if(cnt1 != 0) {
				// 중복베팅
				log.error(LOG_PREFIX+ "중복베팅");
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				return resEntity;
			}
			*/

			// ##--isTransfer 처리-##
			if(IS_TRANSFER.equals("Y")) {
				// tranId 중복체크
				int cntTr = siteService.getTranIdCheck2(tranId);
				if(cntTr > 0) {
					HashMap balanceMap = new HashMap<>();
					balanceMap.put("siteIdx", siteIdx);
					balanceMap.put("memberId", memberId);
					int balance = siteService.getUserBalance(balanceMap);
					response.setBalance(balance);
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					return new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);
				}

				// 베팅 체크
				int cntBet = siteService.getBetIdCheck2(betId);
				if(cntBet == 0) {
					// 처리한 베팅 존재X
					log.error(LOG_PREFIX+ "처리한 베팅 존재X");
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
					return resEntity;
				}
			} else {
				int cnt2 = siteService.getBetIdCheck(betId);
				if(cnt2 == 0) {
					// 처리한 베팅 존재X
					log.error(LOG_PREFIX+ "처리한 베팅 존재X");
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
					return resEntity;
				}
			}

			boolean isParsing = false;
			log.info(LOG_PREFIX + "BF_isParsing::"+isParsing);
			Boolean genuine = false;
			if(transaction.has("genuine")) {
				log.info(LOG_PREFIX + "transaction.has(\"genuine\") is true");
				genuine = transaction.getBoolean("genuine");
				log.info(LOG_PREFIX + "transaction.genuine::"+genuine.booleanValue());
				// genuine = true : 정품
				// genuine = null or false : 하이브리드(파싱)
				if(genuine != null && genuine.booleanValue() == true) {
					isParsing = false;
					if(game.has("id")) {
						roundId = game.getString("id");
					}
				} else {
					isParsing = true;
				}
			} else {
				log.info(LOG_PREFIX + "transaction.has(\"genuine\") is false");
				isParsing = true;
			}
			log.info(LOG_PREFIX + "AF_isParsing::"+isParsing);

			String isDebitParse = siteService.getDebitIsParse(betId);
			log.info(LOG_PREFIX + "isDebitParse::"+isDebitParse);
			if(isDebitParse == null) {
				log.error(LOG_PREFIX+ "처리한 베팅 존재X");
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				return resEntity;
			} else {
				if("P".equals(isDebitParse)) {
					isParsing = true;
				}
			}

			HashMap tranParam = new HashMap();
			tranParam.put("tranId", tranId);
			tranParam.put("refId",  betId);
			tranParam.put("siteIdx", siteIdx);
			tranParam.put("siteId", siteId);
			tranParam.put("memberIdx", memberIdx);
			tranParam.put("memberId", memberId);
			tranParam.put("vendorCetegory", vendorCetegory);
			tranParam.put("vendorIdx", vendorIdx);
			tranParam.put("vendorCode", "dpcore");
			tranParam.put("vendorTranKey", roundId);
			tranParam.put("gameType", gameType);
			tranParam.put("gameId", tableId);
			tranParam.put("gameIdx", game.getString("title"));
			tranParam.put("tranType", "credit");
			tranParam.put("depositAmt", "0");
			tranParam.put("creditAmt", amount);
			tranParam.put("isCancel", "Y");
			if(isParsing) {
				tranParam.put("isTie", "P");
			} else {
				tranParam.put("isTie", "N");
			}
			tranParam.put("apiStatus", 0);
			tranParam.put("vendorApiId", vendorApiId);

			JSONObject callBackObj = new JSONObject();
			callBackObj.put("betId", betId);
			callBackObj.put("tranId", tranId);
			callBackObj.put("betKey", roundId);
			callBackObj.put("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "dpcore");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", game.getString("title"));
			callBackObj.put("gameId", tableId);
			callBackObj.put("gameType", gameType);
			callBackObj.put("tranType", "credit");
			callBackObj.put("debit", 0);
			callBackObj.put("credit", amount);
			callBackObj.put("isCancel", 1);
			callBackObj.put("isBonus", 0);
			if(isParsing) {
				callBackObj.put("isData", "Y");
			} else {
				callBackObj.put("isData", "N");
			}
			callBackObj.put("requestAt", betDateTimeStr);

			// ##--isTransfer 처리-##
			if(IS_TRANSFER.equals("N")) {
				log.info(LOG_PREFIX + "-----INSERT_CANCEL_START-----");
				siteService.commonBetinsert(tranParam);
				log.info(LOG_PREFIX + "-----INSERT_CANCEL_END-----");

				log.info(LOG_PREFIX + "SEND_CANCEL request body: " + callBackObj.toString());
				JSONObject resData = callBackService.changeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				log.info(LOG_PREFIX + "SEND_CANCEL status code: " + resData.getLong("result_code"));
				log.info(LOG_PREFIX + "SEND_CANCEL response body: " + resData.toString());

				if(resData.getLong("result_code") == 0) {
					tranParam.put("balance", resData.getInt("balance"));
					tranParam.put("isTransfer", IS_TRANSFER);
					int res = siteService.updateCbApi(tranParam);
					log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

					int balance = resData.getInt("balance");
					response.setBalance(balance);
					response.setBeforeBalance(balance-amount);
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);

				} else if(resData.getLong("result_code") == 98) {
					int balance = resData.getInt("balance");
					response.setBalance(balance);
					resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);

				} else {
					String msg = "";
					if(resData.has("error_msg")) msg = resData.getString("error_msg");
	 				log.error(LOG_PREFIX+ msg);
	 				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
				}
			} else {
				log.info(LOG_PREFIX + "PLAY_TRANSFER");
				HashMap balanceMap = new HashMap<>();
				balanceMap.put("siteIdx", siteIdx);
				balanceMap.put("memberId", memberId);
				int balance = siteService.getUserBalance(balanceMap);
				int afBalance = balance + amount;

				log.info(LOG_PREFIX + "-----INSERT_CANCEL_START-----");
				siteService.commonBetinsert(tranParam);
				log.info(LOG_PREFIX + "-----INSERT_CANCEL_END-----");

				tranParam.put("balance", afBalance);
				tranParam.put("preBalance", balance);
				tranParam.put("isTransfer", IS_TRANSFER);
				int res = siteService.updateCbApi(tranParam);
				log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

				response.setBalance(afBalance);
				response.setBeforeBalance(balance);
				resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.OK);

				if(siteVendorInfo.get("siteCbUrl") != null && !"".equals(siteVendorInfo.get("siteCbUrl").toString())) {
					callBackObj.put("balance", afBalance);
					asyncCallBackService.asyncChangeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				}
			}

		} catch (ResourceAccessException rae) {
            if(rae.getCause() instanceof ConnectTimeoutException) {
            	log.error(LOG_PREFIX+ "[ConnectTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof SocketTimeoutException) {
            	log.error(LOG_PREFIX+ "[SocketTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof InterruptedException) {
            	log.error(LOG_PREFIX+ "[InterruptedException]"+rae.getMessage());
            }

            resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			resEntity = new ResponseEntity<PrimeResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		}

		return resEntity;
	}


	/**
	 * 에볼루션 상세 콜백
	 * @param request
	 * @param requestBody
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@RequestMapping(path = "/callDetail")
	public ResponseEntity<NexusResponse> callDetail(HttpServletRequest request, @RequestBody net.sf.json.JSONObject requestBody) throws Exception {
		NexusResponse response = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getString("apiKey");
		final String LOG_PREFIX = "#-CB::DPCORE::callDetail::ace::"+apiKey+":::";
		//log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

		try {
			JSONObject bodyObj = new JSONObject(requestBody.toString());
			JSONArray params = bodyObj.getJSONArray("params");
			String prevVendorTranKey = "";
			for(int i=0; i<params.length(); i++) {
				JSONObject orgDetail = params.getJSONObject(i);
				if(orgDetail.has("refId")) {
					String vendorTranKey = orgDetail.getString("refId");
					String LOG_PREFIX2 = "#-CB::DPCORE::callDetail::ace::"+apiKey+"::"+vendorTranKey+":::";
					if(prevVendorTranKey.equals(vendorTranKey)) continue;
					prevVendorTranKey = vendorTranKey;
					log.info(LOG_PREFIX2+ "vendorTranKey::" + vendorTranKey);

					HashMap betData = siteService.getRefIdByVendorTranKey(vendorTranKey);
					if(betData != null) {
						String refId = betData.get("refId").toString();
						String isParsing = betData.get("isTie").toString();
						if(!isParsing.equals("P")) {
							// 해당 베팅이 정품일때만.
							// UTC를 한국 시간으로 변환
							convertUtcToKst(orgDetail);

							HashMap detailMap = new HashMap<>();
							detailMap.put("refId", refId);
							detailMap.put("orgDetail", orgDetail.toString());
							detailMap.put("parDetail", null);
							try {
								int result = siteService.insertEvoDetail(detailMap);
								log.info(LOG_PREFIX2+ "insertEvoDetail result::" + result);
							} catch(DataIntegrityViolationException de) {
								log.error(LOG_PREFIX2+ "Duplicate refId");
								continue;
							}
						}
					}
				} else {
					log.info(LOG_PREFIX+ "Not found json key _id");
				}
			}

			response = new NexusResponse();
			response.setCode(0);
			response.setMsg("SUCCESS");
			httpStatus = HttpStatus.OK;

		} catch (ResourceAccessException rae) {
            if(rae.getCause() instanceof ConnectTimeoutException) {
            	log.error(LOG_PREFIX+ "[ConnectTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof SocketTimeoutException) {
            	log.error(LOG_PREFIX+ "[SocketTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof InterruptedException) {
            	log.error(LOG_PREFIX+ "[InterruptedException]"+rae.getMessage());
            }

            response = new NexusResponse();
            response.setCode(-8);
            response.setMsg("TIMEOUT_ERROR");
			httpStatus = HttpStatus.OK;

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			response = new NexusResponse();
			response.setCode(-9);
			response.setMsg("DETAIL_SERVER_ERROR");
			httpStatus = HttpStatus.OK;
		}

		ResponseEntity<NexusResponse> res = new ResponseEntity<NexusResponse>(response, httpStatus);
		log.info(LOG_PREFIX+ "Response::" + response.toString());

		return res;
	}


	/**
	 * UTC 시간을 한국 시간(KST)으로 변환
	 * @param utcTimeStr UTC 시간 문자열 (예: 2026-02-06T04:39:05.243Z)
	 * @return 한국 시간 문자열 (예: 2026-02-06T13:39:05.243+09:00)
	 */
	private static String convertUtcToKstTime(String utcTimeStr) {
		try {
			if (utcTimeStr == null || !utcTimeStr.endsWith("Z")) {
				return utcTimeStr;
			}

			// UTC 시간 파싱
			Instant instant = Instant.parse(utcTimeStr);
			// 한국 시간대로 변환 (UTC+9)
			ZonedDateTime kstTime = instant.atZone(ZoneId.of("Asia/Seoul"));
			// ISO 8601 형식으로 포맷 (타임존 포함)
			return kstTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
		} catch (Exception e) {
			log.error("UTC to KST conversion error: " + e.getMessage());
			return utcTimeStr; // 변환 실패시 원본 반환
		}
	}

	/**
	 * JSON 객체를 재귀적으로 순회하며 시간 필드를 UTC에서 KST로 변환
	 * @param jsonObj 변환할 JSON 객체
	 */
	private static void convertUtcToKst(JSONObject jsonObj) {
		try {
			// 변환할 시간 필드 목록
			String[] timeFields = {"startedAt", "settledAt", "placedOn", "recordedAt"};

			Iterator<String> keys = jsonObj.keys();
			while (keys.hasNext()) {
				String key = keys.next();
				Object value = jsonObj.get(key);

				// 시간 필드인 경우 변환
				for (String timeField : timeFields) {
					if (key.equals(timeField) && value instanceof String) {
						String utcTime = (String) value;
						String kstTime = convertUtcToKstTime(utcTime);
						jsonObj.put(key, kstTime);
						break;
					}
				}

				// 중첩된 JSONObject인 경우 재귀 호출
				if (value instanceof JSONObject) {
					convertUtcToKst((JSONObject) value);
				}
				// 중첩된 JSONArray인 경우 각 요소 처리
				else if (value instanceof JSONArray) {
					JSONArray jsonArray = (JSONArray) value;
					for (int i = 0; i < jsonArray.length(); i++) {
						Object arrayItem = jsonArray.get(i);
						if (arrayItem instanceof JSONObject) {
							convertUtcToKst((JSONObject) arrayItem);
						}
					}
				}
			}
		} catch (Exception e) {
			log.error("JSON time conversion error: " + e.getMessage());
		}
	}

	private static String makeEdate(String LOG_PREFIX, String paramDate, int flag) {
		// TODO Auto-generated method stub
		String eDate = "";
		SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		try {
			Date date = sdf1.parse(paramDate);
			Calendar calendar = Calendar.getInstance();
			calendar.setTime(date);
			calendar.add(Calendar.MILLISECOND, flag);
			Date date2 = calendar.getTime();
			eDate = sdf2.format(date2);
		} catch(Exception e) {
			log.error(LOG_PREFIX+ "makeEdate::Exception::"+e.getMessage());
		}
		return eDate;
	}
}
