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 jakarta.servlet.http.HttpServletRequest;

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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.ResourceAccessException;

import com.bb.model.NexusBalanceData;
import com.bb.model.NexusResponse;
import com.bb.model.NexusSmlData;
import com.bb.model.NexusSmlRequest;
import com.bb.service.AsyncSiteService;
import com.bb.service.CallBackService;
import com.bb.service.SiteService;

import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

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

	@Autowired
	SiteService siteService;

	@Autowired
	CallBackService callBackService;

	@Autowired
	AsyncSiteService asyncCallBackService;


	@ResponseBody
	@RequestMapping(path = "/callBalance")
	public ResponseEntity<NexusResponse> callBalance(HttpServletRequest request, @RequestBody net.sf.json.JSONObject requestBody) throws Exception {
		NexusResponse response = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getString("apiKey");
		net.sf.json.JSONObject params = requestBody.getJSONObject("params");
		String siteUsername = params.getString("siteUsername");
		String requestedAt = params.getString("requestedAt");

		if(siteUsername == null || siteUsername.equals("") || siteUsername.equals("invalid-user")) {
			response = new NexusResponse();
			response.setCode(-1);
			response.setMsg("존재하지 않는 회원입니다");
			httpStatus = HttpStatus.OK;
			return new ResponseEntity<NexusResponse>(response, httpStatus);
		}

		long siteIdx = Long.parseLong(siteUsername.substring(0, 3), 16);
		String memberId = siteUsername.substring(3, siteUsername.length());
		final String LOG_PREFIX = "#-CB::NEXUS::::callBalance::"+memberId+"::"+requestedAt+":::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

		try {
			//사이트정보
			HashMap sParam = new HashMap();
			sParam.put("siteIdx", siteIdx);
			sParam.put("memberId", memberId);
			sParam.put("vendor", "nexus");
			sParam.put("apiKey", apiKey);
			log.info(LOG_PREFIX + "getSiteVendorInfo::"+sParam.toString());
			HashMap<String,String> siteApiInfo = siteService.getSiteApiInfoByApiKey(sParam);
			if(siteApiInfo == null) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("API키가 일치하지 않습니다");
				httpStatus = HttpStatus.OK;
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			if(siteApiInfo.get("memberIdx") == null) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("존재하지 않는 회원입니다");
				httpStatus = HttpStatus.OK;
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			// ##--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);
				long startTimestamp = System.currentTimeMillis();
				JSONObject resData  = callBackService.getBalance(siteApiInfo, member);
				if(resData == null) {
					response = new NexusResponse();
					response.setCode(-1);
					response.setMsg("BALANCE_CALLBACK_ERROR");
					httpStatus = HttpStatus.OK;
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}
				long endTimestamp = System.currentTimeMillis();
				double diffTime = (startTimestamp - endTimestamp)/1000.0; //두 시간에 차 계산
				if(diffTime < -1.5) {
					log.error(LOG_PREFIX+ "잔액요청 응답시간("+diffTime+"초) 초과");
				}
				log.info(LOG_PREFIX + "resData::"+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);
				}
			}

			NexusBalanceData data = new NexusBalanceData();
			data.setBalance(balance);
			data.setCurrency("KRW");
			response = new NexusResponse();
			response.setCode(0);
			response.setData(data);
			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(-999);
            response.setMsg("TIMEOUT_ERROR");
			httpStatus = HttpStatus.OK;

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

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

		return res;
	}


	@ResponseBody
	@RequestMapping(path = "/callBet")
	public ResponseEntity<NexusResponse> callBet(HttpServletRequest request, @RequestBody NexusSmlRequest requestBody) throws Exception {
		NexusResponse response = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getApiKey();
		NexusSmlData data = requestBody.getParams();
		String siteUsername = data.getSiteUsername();

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		String betDateTimeStr = data.getCreatedAt();
		if(betDateTimeStr == null || betDateTimeStr.equals("")) {
			betDateTimeStr = sdf.format(System.currentTimeMillis());
		} else {
			betDateTimeStr = betDateTimeStr.replace("T", " ").replace("Z", "");
			betDateTimeStr = makeEdate(betDateTimeStr, 32400000);
		}
		data.setBetDateTime(betDateTimeStr);

		if(siteUsername == null || siteUsername.equals("") || siteUsername.equals("invalid-user")) {
			response = new NexusResponse();
			response.setCode(-1);
			response.setMsg("존재하지 않는 회원입니다");
			httpStatus = HttpStatus.OK;
			return new ResponseEntity<NexusResponse>(response, httpStatus);
		}

		long siteIdx = Long.parseLong(siteUsername.substring(0, 3), 16);
		String memberId = siteUsername.substring(3, siteUsername.length());
		final String LOG_PREFIX = "#-CB::NEXUS::"+memberId+"::"+data.getTransactionKey()+"::callBet:::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

		try {
			//사이트정보
			HashMap sParam = new HashMap();
			sParam.put("siteIdx", siteIdx);
			sParam.put("memberId", memberId);
			sParam.put("vendor", "nexus");
			sParam.put("vendorGameCode", data.getVendorKey());
			log.info(LOG_PREFIX + "getSiteVendorInfo::"+sParam.toString());
			HashMap<String,String> siteVendorInfo = siteService.getSiteVendorInfo(sParam);
			if(siteVendorInfo == null) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("존재하지 않는 회원입니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "존재하지 않는 회원입니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}
			if(!apiKey.equals(siteVendorInfo.get("vendorApiKey").toString())) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("API키가 일치하지 않습니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "API키가 일치하지 않습니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

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

			// vendors_info.useYn 게임막기 기능
			if("N".equals(siteVendorInfo.get("vendorAllUseYn").toString())) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("사용불가 벤더사");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "사용불가 벤더사::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

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

			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 {
				Long lastInVendorIdx = Long.parseLong(siteVendorInfo.get("lastInVendorIdx").toString());
				Long vendorIdx = Long.parseLong(siteVendorInfo.get("vendorIdx").toString());
				if(lastInVendorIdx.longValue() != vendorIdx.longValue()) {
					response = new NexusResponse();
					response.setCode(-1);
					response.setMsg("VendorIdx doesn't match");
					httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "VendorIdx doesn't match::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}
			}

			Double amountD = Double.parseDouble(data.getAmount());
			int amount = Math.abs(amountD.intValue());
			long siteCredit = Long.parseLong(siteVendorInfo.get("credit").toString());
			// ##--isTransfer 처리-##
			if(siteCredit < amount && IS_TRANSFER.equals("N")) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("NO_SITE_CREDIT");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "NO_SITE_CREDIT::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			if(amount < 0) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("금액이 올바르지 않습니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "금액이 올바르지 않습니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			// 최대 베팅 금액 처리 (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);
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("MAX_BET_AMOUNT_OVER");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "MAX_BET_AMOUNT_OVER::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			String vendorTranKey = data.getTransactionKey();
			String tranId = data.getTransactionKey();
			// ##--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);
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance + amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}
			}

			String betId = data.getParentTransactionKey();
			if(betId == null || betId.equals("")) {
				betId = data.getTransactionKey();
			}
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();

			String gameName = data.getGameName();
			String gameType = data.getGameType().replaceAll(" ", "").toLowerCase();
			if(data.getGameId() == null) data.setGameId("");
			String gameId = data.getGameId().replaceAll(" ", "").toLowerCase();
			String gameKey = null;
			if(vendorIdx == 1) {
				gameKey = siteService.getGameKeyByNexusGameId(data.getGameId());
			}
			if(gameKey == null) gameKey = gameId;
			String banGameType = siteVendorInfo.get("banGameType").toString().replaceAll(" ", "").toLowerCase();
			String banGameId = siteVendorInfo.get("banGameId").toString().replaceAll(" ", "").toLowerCase();

			if(!"".equals(gameType) && !"".equals(banGameType) && banGameType.contains(gameType+"|")) {
				// Ban Game Type Exception
				HashMap logParam = new HashMap();
				logParam.put("siteIdx", siteIdx);
				logParam.put("memberIdx", memberIdx);
				logParam.put("vendorIdx", vendorIdx);
				logParam.put("statusCode", 501L);
				logParam.put("callBackType", "debit");
				logParam.put("tranId", tranId);
				logParam.put("callBackReqBody", "gameType:"+gameType+"이 블락설정");
				logParam.put("callBackResBody", "");
				logParam.put("errorCode", "BLOCK_GAME_TYPE");
				logParam.put("errorMsg", "gameType:"+gameType+"이 블락설정");
				long startTimestamp = System.currentTimeMillis();
				logParam.put("regDate", startTimestamp);
				logParam.put("updDate", startTimestamp);
				callBackService.insertCallBackErrLog(logParam);
				log.error(LOG_PREFIX+ "Ban Game Type : " + gameType);

				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("Ban Game Type : " + gameType);
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "VendorIdx doesn't match::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			if(!"".equals(gameId) && !"".equals(banGameId) && banGameId.contains(gameId+"|")) {
				// Ban Game Id Exception
				HashMap logParam = new HashMap();
				logParam.put("siteIdx", siteIdx);
				logParam.put("memberIdx", memberIdx);
				logParam.put("vendorIdx", vendorIdx);
				logParam.put("statusCode", 502L);
				logParam.put("callBackType", "debit");
				logParam.put("tranId", tranId);
				logParam.put("callBackReqBody", "gameId:"+gameId+"("+gameName+")이 블락설정");
				logParam.put("callBackResBody", "");
				logParam.put("errorCode", "BLOCK_GAME_ID");
				logParam.put("errorMsg", "gameId:"+gameId+"("+gameName+")이 블락설정");
				long startTimestamp = System.currentTimeMillis();
				logParam.put("regDate", startTimestamp);
				logParam.put("updDate", startTimestamp);
				callBackService.insertCallBackErrLog(logParam);
				log.error(LOG_PREFIX+ "Ban Game Id : " + gameId);

				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("Ban Game Id : " + gameId);
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "VendorIdx doesn't match::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			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", "nexus");
			tranParam.put("vendorTranKey", vendorTranKey);
			tranParam.put("gameType", data.getGameType());
			tranParam.put("gameId", data.getGameId());
			tranParam.put("gameIdx", data.getGameName());
			tranParam.put("tranType", "debit");
			tranParam.put("depositAmt", amount);
			tranParam.put("creditAmt", "0");
			tranParam.put("isCancel", "N");
			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("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "nexus");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", data.getGameName());
			if(data.getGameId() == null) data.setGameId("");
			callBackObj.put("gameId", data.getGameId());
			callBackObj.put("gameType", data.getGameType());
			callBackObj.put("tranType", "debit");
			callBackObj.put("debit", amount);
			callBackObj.put("credit", 0);
			callBackObj.put("isCancel", 0);
			if(data.getIsBonus().equals("true")) {
				callBackObj.put("isBonus", 1);
			} else {
				callBackObj.put("isBonus", 0);
			}
			callBackObj.put("requestAt", betDateTimeStr);
			log.info(LOG_PREFIX + "SEND_BET request body: " + callBackObj.toString());

			// ##--isTransfer 처리-##
			if(IS_TRANSFER.equals("N")) {
				log.info(LOG_PREFIX + "PLAY_SEAMLESS");
				log.info(LOG_PREFIX + "-----INSERT_BET_START-----");
				long startTime = System.currentTimeMillis();
				siteService.commonBetinsert(tranParam);
				long endTime = System.currentTimeMillis(); // 코드 실행 후에 시간 받아오기
				double diffTime = (startTime - endTime)/1000.0; //두 시간에 차 계산
				if(diffTime < -1.0) {
					log.error(LOG_PREFIX + "commonBetinsert::배팅 쓰기지연::"+diffTime+"초 지연발생");
					response = new NexusResponse();
					response.setCode(-999);
					response.setMsg("TIMEOUT_ERROR");
					httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "TIMEOUT_ERROR::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}
				log.info(LOG_PREFIX + "-----INSERT_BET_END-----");

				JSONObject resData = null;
				if(data.getGameCategory().equals("casino")) {
					resData = callBackService.changeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				} else {
					resData = callBackService.changeBalanceSlot(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");
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance + amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
				} else if(resData.getLong("result_code") == 80) {
					response = new NexusResponse();
	 				response.setCode(-1);
	 				response.setMsg("금액을 확인하여 주세요");
	 				httpStatus = HttpStatus.OK;
				} else if(resData.getLong("result_code") == 98) {
					int balance = resData.getInt("balance");
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance + amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
				} else {
					response = new NexusResponse();
	 				response.setCode(-999);
	 				String msg = "";
					if(resData.has("error_msg")) msg = resData.getString("error_msg");
	 				response.setMsg(msg);
	 				httpStatus = HttpStatus.OK;
				}
			} 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 = new NexusResponse();
	 				response.setCode(-1);
	 				response.setMsg("금액을 확인하여 주세요");
	 				httpStatus = HttpStatus.OK;
	 				log.error(LOG_PREFIX+ "잔액부족::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}

				log.info(LOG_PREFIX + "-----INSERT_BET_START-----");
				long startTime = System.currentTimeMillis();
				siteService.commonBetinsert(tranParam);
				long endTime = System.currentTimeMillis(); // 코드 실행 후에 시간 받아오기
				double diffTime = (startTime - endTime)/1000.0; //두 시간에 차 계산
				if(diffTime < -1.0) {
					log.error(LOG_PREFIX + "commonBetinsert::배팅 쓰기지연::"+diffTime+"초 지연발생");
					response = new NexusResponse();
					response.setCode(-999);
					response.setMsg("TIMEOUT_ERROR");
					httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "TIMEOUT_ERROR::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}
				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");

				NexusBalanceData balanceData = new NexusBalanceData();
				balanceData.setBalance(afBalance);
				balanceData.setBeforeBalance(balance);
				balanceData.setCurrency("KRW");
				response = new NexusResponse();
				response.setCode(0);
				response.setData(balanceData);
				httpStatus = HttpStatus.OK;

				if(siteVendorInfo.get("siteCbUrl") != null && !"".equals(siteVendorInfo.get("siteCbUrl").toString())) {
					callBackObj.put("balance", afBalance);
					if(data.getGameCategory().equals("casino")) {
						asyncCallBackService.asyncChangeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
					} else {
						asyncCallBackService.asyncChangeBalanceSlot(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());
            }

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

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

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

		return res;
	}


	@ResponseBody
	@RequestMapping(path = "/callResult")
	public ResponseEntity<NexusResponse> callResult(HttpServletRequest request, @RequestBody NexusSmlRequest requestBody) throws Exception {
		NexusResponse response = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getApiKey();
		NexusSmlData data = requestBody.getParams();
		String siteUsername = data.getSiteUsername();

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		String betDateTimeStr = data.getCreatedAt();
		if(betDateTimeStr == null || betDateTimeStr.equals("")) {
			betDateTimeStr = sdf.format(System.currentTimeMillis());
		} else {
			betDateTimeStr = betDateTimeStr.replace("T", " ").replace("Z", "");
			betDateTimeStr = makeEdate(betDateTimeStr, 32400000);
		}
		data.setBetDateTime(betDateTimeStr);

		if(siteUsername == null || siteUsername.equals("") || siteUsername.equals("invalid-user")) {
			response = new NexusResponse();
			response.setCode(-1);
			response.setMsg("존재하지 않는 회원입니다");
			httpStatus = HttpStatus.OK;
			return new ResponseEntity<NexusResponse>(response, httpStatus);
		}

		long siteIdx = Long.parseLong(siteUsername.substring(0, 3), 16);
		String memberId = siteUsername.substring(3, siteUsername.length());
		final String LOG_PREFIX = "#-CB::NEXUS::"+memberId+"::"+data.getTransactionKey()+"::callResult:::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

		if(data.getType().equals("turn_promotion")) {
			data.setVendorKey("pragmaticplay_casino");
		}

		try {
			//사이트정보
			HashMap sParam = new HashMap();
			sParam.put("siteIdx", siteIdx);
			sParam.put("memberId", memberId);
			sParam.put("vendor", "nexus");
			sParam.put("vendorGameCode", data.getVendorKey());
			log.info(LOG_PREFIX + "getSiteVendorInfo::"+sParam.toString());
			HashMap<String,String> siteVendorInfo = siteService.getSiteVendorInfo(sParam);
			if(siteVendorInfo == null) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("존재하지 않는 회원입니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "존재하지 않는 회원입니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}
			if(!apiKey.equals(siteVendorInfo.get("vendorApiKey").toString())) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("API키가 일치하지 않습니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "API키가 일치하지 않습니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

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

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

			Double amountD = Double.parseDouble(data.getAmount());
			int amount = Math.abs(amountD.intValue());
			if(amount < 0) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("금액이 올바르지 않습니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "금액이 올바르지 않습니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			String vendorTranKey = data.getTransactionKey();
			String tranId = data.getTransactionKey();
			String betId = data.getParentTransactionKey();
			if(betId == null || betId.equals("")) {
				betId = data.getTransactionKey();
			}

			// ##--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);
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance + amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}

				// 베팅 체크
				int cntBet = siteService.getBetIdCheck2(betId);
				if(cntBet == 0) {
					response = new NexusResponse();
	 				response.setCode(-999);
	 				response.setMsg("NOT_FOUND_REF_DATA");
	 				httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "NOT_FOUND_REF_DATA::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}
			}

			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			String gameName = data.getGameName();

			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", "nexus");
			tranParam.put("vendorTranKey", vendorTranKey);
			tranParam.put("gameType", data.getGameType());
			tranParam.put("gameId", data.getGameId());
			tranParam.put("gameIdx", gameName);
			tranParam.put("tranType", "credit");
			tranParam.put("depositAmt", "0");
			tranParam.put("creditAmt", amount);
			tranParam.put("isCancel", "N");
			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("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "nexus");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			if(data.getGameId() == null) data.setGameId("");
			callBackObj.put("gameId", data.getGameId());
			callBackObj.put("gameType", data.getGameType());
			callBackObj.put("tranType", "credit");
			callBackObj.put("debit", 0);
			callBackObj.put("credit", amount);
			callBackObj.put("isCancel", 0);
			if(data.getIsBonus().equals("true") || data.getIsPromo().equals("true") || data.getType().equals("turn_promotion")) {
				callBackObj.put("isBonus", 1);
			} else {
				callBackObj.put("isBonus", 0);
			}
			callBackObj.put("requestAt", betDateTimeStr);
			log.info(LOG_PREFIX + "SEND_RESULT request body: " + callBackObj.toString());

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

				JSONObject resData = null;
				if(data.getGameCategory().equals("casino")) {
					resData = callBackService.changeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				} else {
					resData = callBackService.changeBalanceSlot(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");
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance - amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
				} else if(resData.getLong("result_code") == 98) {
					int balance = resData.getInt("balance");
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance - amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
				} else {
					response = new NexusResponse();
	 				response.setCode(-999);
	 				String msg = "";
					if(resData.has("error_msg")) msg = resData.getString("error_msg");
	 				response.setMsg(msg);
	 				httpStatus = HttpStatus.OK;
				}
			} 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");

				NexusBalanceData balanceData = new NexusBalanceData();
				balanceData.setBalance(afBalance);
				balanceData.setBeforeBalance(balance);
				balanceData.setCurrency("KRW");
				response = new NexusResponse();
				response.setCode(0);
				response.setData(balanceData);
				httpStatus = HttpStatus.OK;

				if(siteVendorInfo.get("siteCbUrl") != null && !"".equals(siteVendorInfo.get("siteCbUrl").toString())) {
					callBackObj.put("balance", afBalance);
					if(data.getGameCategory().equals("casino")) {
						asyncCallBackService.asyncChangeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
					} else {
						asyncCallBackService.asyncChangeBalanceSlot(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());
            }

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

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

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

		return res;
	}


	@ResponseBody
	@RequestMapping(path = "/callCancel")
	public ResponseEntity<NexusResponse> callCancel(HttpServletRequest request, @RequestBody NexusSmlRequest requestBody) throws Exception {
		NexusResponse response = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getApiKey();
		NexusSmlData data = requestBody.getParams();
		String siteUsername = data.getSiteUsername();

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		String betDateTimeStr = data.getCreatedAt();
		if(betDateTimeStr == null || betDateTimeStr.equals("")) {
			betDateTimeStr = sdf.format(System.currentTimeMillis());
		} else {
			betDateTimeStr = betDateTimeStr.replace("T", " ").replace("Z", "");
			betDateTimeStr = makeEdate(betDateTimeStr, 32400000);
		}
		data.setBetDateTime(betDateTimeStr);

		if(siteUsername == null || siteUsername.equals("") || siteUsername.equals("invalid-user")) {
			response = new NexusResponse();
			response.setCode(-1);
			response.setMsg("존재하지 않는 회원입니다");
			httpStatus = HttpStatus.OK;
			return new ResponseEntity<NexusResponse>(response, httpStatus);
		}

		long siteIdx = Long.parseLong(siteUsername.substring(0, 3), 16);
		String memberId = siteUsername.substring(3, siteUsername.length());
		final String LOG_PREFIX = "#-CB::NEXUS::"+memberId+"::"+data.getTransactionKey()+"::callCancel:::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

		try {
			//사이트정보
			HashMap sParam = new HashMap();
			sParam.put("siteIdx", siteIdx);
			sParam.put("memberId", memberId);
			sParam.put("vendor", "nexus");
			sParam.put("vendorGameCode", data.getVendorKey());
			log.info(LOG_PREFIX + "getSiteVendorInfo::"+sParam.toString());
			HashMap<String,String> siteVendorInfo = siteService.getSiteVendorInfo(sParam);
			if(siteVendorInfo == null) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("존재하지 않는 회원입니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "존재하지 않는 회원입니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}
			if(!apiKey.equals(siteVendorInfo.get("vendorApiKey").toString())) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("API키가 일치하지 않습니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "API키가 일치하지 않습니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

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

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

			Double amountD = Double.parseDouble(data.getAmount());
			int amount = Math.abs(amountD.intValue());
			if(amount < 0) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("금액이 올바르지 않습니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "금액이 올바르지 않습니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

			String vendorTranKey = data.getTransactionKey();
			String tranId = data.getTransactionKey();
			String betId = data.getParentTransactionKey();
			if(betId == null || betId.equals("")) {
				betId = data.getTransactionKey();
			}

			// ##--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);
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance + amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}

				// 베팅 체크
				int cntBet = siteService.getBetIdCheck2(betId);
				if(cntBet == 0) {
					response = new NexusResponse();
	 				response.setCode(-999);
	 				response.setMsg("NOT_FOUND_REF_DATA");
	 				httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "NOT_FOUND_REF_DATA::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}
			}

			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			String gameName = data.getGameName();

			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", "nexus");
			tranParam.put("vendorTranKey", vendorTranKey);
			tranParam.put("gameType", data.getGameType());
			tranParam.put("gameId", data.getGameId());
			tranParam.put("gameIdx", gameName);
			tranParam.put("tranType", "credit");
			tranParam.put("depositAmt", "0");
			tranParam.put("creditAmt", amount);
			tranParam.put("isCancel", "Y");
			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("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "nexus");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			if(data.getGameId() == null) data.setGameId("");
			callBackObj.put("gameId", data.getGameId());
			callBackObj.put("gameType", data.getGameType());
			callBackObj.put("tranType", "credit");
			callBackObj.put("debit", 0);
			callBackObj.put("credit", amount);
			callBackObj.put("isCancel", 1);
			callBackObj.put("isBonus", 0);
			callBackObj.put("requestAt", betDateTimeStr);
			log.info(LOG_PREFIX + "SEND_CANCEL request body: " + callBackObj.toString());

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

				JSONObject resData = null;
				if(data.getGameCategory().equals("casino")) {
					resData = callBackService.changeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				} else {
					resData = callBackService.changeBalanceSlot(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");
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance - amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
				} else if(resData.getLong("result_code") == 98) {
					int balance = resData.getInt("balance");
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance - amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
				} else {
					response = new NexusResponse();
	 				response.setCode(-999);
	 				String msg = "";
					if(resData.has("error_msg")) msg = resData.getString("error_msg");
	 				response.setMsg(msg);
	 				httpStatus = HttpStatus.OK;
				}
			} 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");

				NexusBalanceData balanceData = new NexusBalanceData();
				balanceData.setBalance(afBalance);
				balanceData.setBeforeBalance(balance);
				balanceData.setCurrency("KRW");
				response = new NexusResponse();
				response.setCode(0);
				response.setData(balanceData);
				httpStatus = HttpStatus.OK;

				if(siteVendorInfo.get("siteCbUrl") != null && !"".equals(siteVendorInfo.get("siteCbUrl").toString())) {
					callBackObj.put("balance", afBalance);
					if(data.getGameCategory().equals("casino")) {
						asyncCallBackService.asyncChangeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
					} else {
						asyncCallBackService.asyncChangeBalanceSlot(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());
            }

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

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

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

		return res;
	}


	@ResponseBody
	@RequestMapping(path = "/callAdjust")
	public ResponseEntity<NexusResponse> callAdjust(HttpServletRequest request, @RequestBody NexusSmlRequest requestBody) throws Exception {
		NexusResponse response = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getApiKey();
		NexusSmlData data = requestBody.getParams();
		String siteUsername = data.getSiteUsername();

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		String betDateTimeStr = data.getCreatedAt();
		if(betDateTimeStr == null || betDateTimeStr.equals("")) {
			betDateTimeStr = sdf.format(System.currentTimeMillis());
		} else {
			betDateTimeStr = betDateTimeStr.replace("T", " ").replace("Z", "");
			betDateTimeStr = makeEdate(betDateTimeStr, 32400000);
		}
		data.setBetDateTime(betDateTimeStr);

		if(siteUsername == null || siteUsername.equals("") || siteUsername.equals("invalid-user")) {
			response = new NexusResponse();
			response.setCode(-1);
			response.setMsg("존재하지 않는 회원입니다");
			httpStatus = HttpStatus.OK;
			return new ResponseEntity<NexusResponse>(response, httpStatus);
		}

		long siteIdx = Long.parseLong(siteUsername.substring(0, 3), 16);
		String memberId = siteUsername.substring(3, siteUsername.length());
		final String LOG_PREFIX = "#-CB::NEXUS::"+memberId+"::"+data.getTransactionKey()+"::callAdjust:::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

		try {
			//사이트정보
			HashMap sParam = new HashMap();
			sParam.put("siteIdx", siteIdx);
			sParam.put("memberId", memberId);
			sParam.put("vendor", "nexus");
			sParam.put("vendorGameCode", data.getVendorKey());
			log.info(LOG_PREFIX + "getSiteVendorInfo::"+sParam.toString());
			HashMap<String,String> siteVendorInfo = siteService.getSiteVendorInfo(sParam);
			if(siteVendorInfo == null) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("존재하지 않는 회원입니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "존재하지 않는 회원입니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}
			if(!apiKey.equals(siteVendorInfo.get("vendorApiKey").toString())) {
				response = new NexusResponse();
				response.setCode(-1);
				response.setMsg("API키가 일치하지 않습니다");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "API키가 일치하지 않습니다::Response::" + response.toString());
				return new ResponseEntity<NexusResponse>(response, httpStatus);
			}

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

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

			Double amountD = Double.parseDouble(data.getAmount());
			int amount = amountD.intValue();

			String vendorTranKey = data.getTransactionKey();
			String tranId = data.getTransactionKey();
			String betId = data.getParentTransactionKey();
			if(betId == null || betId.equals("")) {
				betId = data.getTransactionKey();
			}

			// ##--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);
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance + amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}

				// 베팅 체크
				int cntBet = siteService.getBetIdCheck2(betId);
				if(cntBet == 0) {
					response = new NexusResponse();
	 				response.setCode(-999);
	 				response.setMsg("NOT_FOUND_REF_DATA");
	 				httpStatus = HttpStatus.OK;
					log.error(LOG_PREFIX+ "NOT_FOUND_REF_DATA::Response::" + response.toString());
					return new ResponseEntity<NexusResponse>(response, httpStatus);
				}
			}

			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			String gameName = data.getGameName();

			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", "nexus");
			tranParam.put("vendorTranKey", vendorTranKey);
			tranParam.put("gameType", data.getGameType());
			tranParam.put("gameId", data.getGameId());
			tranParam.put("gameIdx", gameName);
			if(amount >= 0) {
				tranParam.put("tranType", "credit");
				tranParam.put("depositAmt", "0");
				tranParam.put("creditAmt", amount);
			} else {
				tranParam.put("tranType", "debit");
				tranParam.put("depositAmt", amount*-1);
				tranParam.put("creditAmt", "0");
			}
			tranParam.put("isCancel", "N");
			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("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "nexus");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			if(data.getGameId() == null) data.setGameId("");
			callBackObj.put("gameId", data.getGameId());
			callBackObj.put("gameType", data.getGameType());
			callBackObj.put("tranType", "adjust");
			if(amount >= 0) {
				callBackObj.put("debit", 0);
				callBackObj.put("credit", amount);
			} else {
				callBackObj.put("debit", amount*-1);
				callBackObj.put("credit", 0);
			}
			callBackObj.put("isCancel", 0);
			callBackObj.put("isBonus", 0);
			callBackObj.put("requestAt", betDateTimeStr);
			log.info(LOG_PREFIX + "SEND_ADJUST request body: " + callBackObj.toString());

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

				JSONObject resData = null;
				if(data.getGameCategory().equals("casino")) {
					resData = callBackService.changeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
				} else {
					resData = callBackService.changeBalanceSlot(LOG_PREFIX, siteVendorInfo, callBackObj);
				}
				log.info(LOG_PREFIX + "SEND_ADJUST status code: " + resData.getLong("result_code"));
				log.info(LOG_PREFIX + "SEND_ADJUST 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");
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance + amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
				} else if(resData.getLong("result_code") == 98) {
					int balance = resData.getInt("balance");
					NexusBalanceData balanceData = new NexusBalanceData();
					balanceData.setBalance(balance);
					balanceData.setBeforeBalance(balance + amount);
					balanceData.setCurrency("KRW");
					response = new NexusResponse();
					response.setCode(0);
					response.setData(balanceData);
					httpStatus = HttpStatus.OK;
				} else {
					response = new NexusResponse();
	 				response.setCode(-999);
	 				String msg = "";
					if(resData.has("error_msg")) msg = resData.getString("error_msg");
	 				response.setMsg(msg);
	 				httpStatus = HttpStatus.OK;
				}
			} 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_ADJUST_START-----");
				siteService.commonBetinsert(tranParam);
				log.info(LOG_PREFIX + "-----INSERT_ADJUST_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");

				NexusBalanceData balanceData = new NexusBalanceData();
				balanceData.setBalance(afBalance);
				balanceData.setBeforeBalance(balance);
				balanceData.setCurrency("KRW");
				response = new NexusResponse();
				response.setCode(0);
				response.setData(balanceData);
				httpStatus = HttpStatus.OK;

				if(siteVendorInfo.get("siteCbUrl") != null && !"".equals(siteVendorInfo.get("siteCbUrl").toString())) {
					callBackObj.put("balance", afBalance);
					if(data.getGameCategory().equals("casino")) {
						asyncCallBackService.asyncChangeBalance(LOG_PREFIX, siteVendorInfo, callBackObj);
					} else {
						asyncCallBackService.asyncChangeBalanceSlot(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());
            }

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

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

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

		return res;
	}


	/**
	 * 에볼루션 상세 콜백
	 * @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::callDetail::nexus::"+apiKey+":::";
		// log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

		try {
			JSONObject bodyObj = new JSONObject(requestBody.toString());
			JSONArray params = bodyObj.getJSONArray("params");
			String prevRefId = "";
			for(int i=0; i<params.length(); i++) {
				JSONObject orgDetail = params.getJSONObject(i);
				String refId = orgDetail.getString("refId");
				String LOG_PREFIX2 = "#-CB::callDetail::nexus::"+apiKey+"::"+refId+":::";
				if(prevRefId.equals(refId)) continue;
				prevRefId = refId;
				log.info(LOG_PREFIX2+ "refId::" + refId);
				
				// 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;
				}

			}

			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 makeDateStrForUTC(boolean flag, String strDate) {
		String result = "";
		if(flag) {
			// 2022-05-01 00:00:00.000 => 2022-05-01T00:00:00.000Z
			result = strDate.replace(" ", "T") + "Z";
		} else {
			// 2022-05-01T00:00:00.000Z => 2022-05-01 00:00:00.000
			result = strDate.replace("T", " ").replace("Z", "");
		}
		return result;
	}

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