package com.bb.front;

import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;

import org.apache.http.conn.ConnectTimeoutException;
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.PostMapping;
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 org.springframework.web.client.RestTemplate;

import com.bb.model.OnixResponse;
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;
/**
 * Onix Seamless API Callback Controller
 *
 * API 엔드포인트:
 * - /balance: 유저 잔고 조회
 * - /changeBalance: 유저 머니 변경 (베팅, 승리, 취소 등)
 *
 * 모든 응답은 2초 이내로 반환되어야 함
 * CORS 헤더 필수: Access-Control-Allow-Origin: *
 */
@Slf4j
@RequiredArgsConstructor
@SecurityRequirement(name = "Authorization")
@EnableScheduling
@RestController
@RequestMapping("/api/callback/onix")
public class CallBackOnixController {

	@Autowired
	private SiteService siteService;

	@Autowired
	private CallBackService callBackService;

	@Autowired
	private AsyncSiteService asyncCallBackService;

	@Autowired
	RestTemplate restTemplate;


	@ResponseBody
	@RequestMapping(path = {"/balance", "/balance/"})
	public ResponseEntity<OnixResponse> balance(HttpServletRequest request, @RequestParam("userid") String onixUsername) throws Exception {
		OnixResponse response = null;

		long siteIdx = Long.parseLong(onixUsername.substring(0, 3), 16);
		String memberId = onixUsername.substring(3, onixUsername.length());
		final String LOG_PREFIX = "#-CB::ONIX::balance::"+memberId+":::";
		log.info(LOG_PREFIX+ "Request::" + onixUsername);

		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 OnixResponse();
				response.setResult(0);
				response.setBalance(0);
				return new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
			}

			if(siteApiInfo.get("memberIdx") == null) {
				log.error(LOG_PREFIX+ "존재하지 않는 회원");
				response = new OnixResponse();
				response.setResult(0);
				response.setBalance(0);
				return new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
			}
			
			// ##--isTransfer 처리-##
			long balance = 0L;
			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 OnixResponse();
					response.setResult(0);
					response.setBalance(0);
					return new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				}
				log.info(LOG_PREFIX+ "balance Res ::"+ resData.toString());
				balance = resData.getLong("balance");  // int -> long 변경
			} 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);
				}
			}

			// Onix 문서: {"result":1, "balance": 10000} 형식으로 응답
			response = new OnixResponse();
			response.setResult(1);  // 성공 = 1
			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 OnixResponse();
            response.setResult(0);
            response.setBalance(0);
            return new ResponseEntity<OnixResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);

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

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

		return res;
	}

	@PostMapping(path = {"/changeBalance", "/changeBalance/"})
	public ResponseEntity<OnixResponse> changeBalance(HttpServletRequest request, @RequestBody net.sf.json.JSONObject requestBody) {

		ResponseEntity<OnixResponse> resEntity = null;
		log.info("#-CB::ONIX::changeBalance::Request::" + requestBody.toString());

		String onixUsername = requestBody.getString("userid");
		long siteIdx = Long.parseLong(onixUsername.substring(0, 3), 16);
		String memberId = onixUsername.substring(3, onixUsername.length());
		String transactionId = requestBody.getString("tranid");
		String transactionType = requestBody.getString("type");

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

		try {
			// "time":"2023-07-05 21:16:39.473"
			String betDateTimeStr = requestBody.getString("time");
			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", "onix");
			sParam.put("vendorGameCode", "evolution");
			log.info(LOG_PREFIX + "getSiteVendorInfo::" + sParam.toString());
			HashMap<String, String> siteVendorInfo = siteService.getSiteVendorInfo(sParam);

			if(siteVendorInfo == null) {
				log.error(LOG_PREFIX + "존재하지 않는 회원입니다");
				OnixResponse response = OnixResponse.failure();
				resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				return resEntity;
			}

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

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

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

			} else {
				log.error(LOG_PREFIX + "unknown_transaction_type");
				OnixResponse response = OnixResponse.failure();
				resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				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());
			}

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

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

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

		return resEntity;
	}

	private ResponseEntity<OnixResponse> 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<OnixResponse> resEntity = null;
		OnixResponse response = new OnixResponse();
		
		// ##--isTransfer 처리-##
		final String IS_TRANSFER = siteVendorInfo.get("isTransfer").toString();

		try {

			String gameType = ""; // transaction.optString("gametype");
			String gameName = transaction.optString("title");

			String tableId = transaction.optString("subcode");
			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);
				response = OnixResponse.failure();
				resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				return resEntity;
			}

			if (!"".equals(targetTableId) && !"".equals(banGameId) && banGameId.contains(targetTableId + "|")) {
				log.error(LOG_PREFIX + "Ban Game Id : " + banGameId);
				response = OnixResponse.failure();
				resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				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("tranid");
			String betId = transaction.getString("realround");
			String betKey = betId;
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			
			// ##--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);
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					return resEntity;
				}
			}

			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");
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					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");
				response = OnixResponse.failure();
				resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				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);
				response = OnixResponse.failure();
				resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				return resEntity;
			}
			
			boolean isParsing = false;
			String game = transaction.optString("game");
			if("evolution_new".equals(game)) {
				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", "onix");
			tranParam.put("vendorTranKey", betKey);
			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", betKey);
			callBackObj.put("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "onix");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			callBackObj.put("gameId", tableId);
			callBackObj.put("gameType", gameType);
			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");

					long balance = resData.getLong("balance");
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				} else if (resData.getLong("result_code") == 98) {
					long balance = resData.getLong("balance");
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				} else if (resData.getLong("result_code") == 80) {
					long balance = resData.getLong("balance");
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				} else {
					String msg = "";
					if (resData.has("error_msg")) msg = resData.getString("error_msg");
					log.error(LOG_PREFIX + msg);
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, 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) {
					log.error(LOG_PREFIX + "beforeBalance is less than amount");
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					return resEntity;
				}
				
				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 = OnixResponse.success(afBalance);
				resEntity = new ResponseEntity<OnixResponse>(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<OnixResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			log.error(LOG_PREFIX + "[Exception]" + e.toString());
			e.printStackTrace();
			resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		}

		return resEntity;
	}

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

		LOG_PREFIX = LOG_PREFIX + "callWin:::";
		ResponseEntity<OnixResponse> resEntity = null;
		OnixResponse response = new OnixResponse();
		
		// ##--isTransfer 처리-##
		final String IS_TRANSFER = siteVendorInfo.get("isTransfer").toString();

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

			String tranId = transaction.getString("tranid");
			String betId = transaction.getString("realround");
			String betKey = betId;
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			
			net.sf.json.JSONArray details = transaction.optJSONArray("details");
			net.sf.json.JSONObject detailObj = null;
			String gameType = ""; // transaction.optString("");
			String gameName = transaction.optString("title");
			if(details != null && details.size() > 0) {
				detailObj = details.optJSONObject(0);
				if(detailObj != null) {
					gameType = detailObj.optString("gameType");
					HashMap detailMap = new HashMap<>();
					detailMap.put("refId", betId);
					detailMap.put("orgDetail", detailObj.toString());
					detailMap.put("parDetail", null);
					try {
						int result = siteService.insertEvoDetail(detailMap);
						log.info(LOG_PREFIX+ "insertEvoDetail result::" + result);
					} catch(DataIntegrityViolationException de) {
						log.error(LOG_PREFIX+ "Duplicate refId");
					}
				}
				if(gameType == null) gameType = "";
			}
			String tableId = transaction.optString("subcode");
			
			// ##--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);
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					return resEntity;
				}
				
				// 베팅 체크
				int cntBet = siteService.getBetIdCheck2(betId);
				if(cntBet == 0) {
					// 처리한 베팅 존재X
					log.error(LOG_PREFIX+ "처리한 베팅 존재X");
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					return resEntity;
				}
			}

			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");
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					return resEntity;
				}
			}
			
			boolean isParsing = false;
			String game = transaction.optString("game");
			if("evolution_new".equals(game)) {
				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", "onix");
			tranParam.put("vendorTranKey", betKey);
			tranParam.put("gameType", gameType);
			tranParam.put("gameId", tableId);
			tranParam.put("gameIdx", gameName);
			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", betKey);
			callBackObj.put("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "onix");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			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);
			
			// ##--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-----");
				
				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");

					long balance = resData.getLong("balance");
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				} else if (resData.getLong("result_code") == 98) {
					long balance = resData.getLong("balance");
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				} else {
					String msg = "";
					if (resData.has("error_msg")) msg = resData.getString("error_msg");
					log.error(LOG_PREFIX + msg);
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, 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");
				
				response = OnixResponse.success(afBalance);
				resEntity = new ResponseEntity<OnixResponse>(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<OnixResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			log.error(LOG_PREFIX + "[Exception]" + e.toString());
			e.printStackTrace();
			resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		}

		return resEntity;
	}

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

		LOG_PREFIX = LOG_PREFIX + "callCancel:::";
		ResponseEntity<OnixResponse> resEntity = null;
		OnixResponse response = new OnixResponse();
		
		// ##--isTransfer 처리-##
		final String IS_TRANSFER = siteVendorInfo.get("isTransfer").toString();

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

			String tranId = transaction.getString("tranid");
			String betId = transaction.getString("realround");
			String betKey = betId;
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			
			net.sf.json.JSONArray details = transaction.optJSONArray("details");
			net.sf.json.JSONObject detailObj = null;
			String gameType = ""; // transaction.optString("");
			String gameName = transaction.optString("title");
			if(details != null && details.size() > 0) {
				detailObj = details.optJSONObject(0);
				if(detailObj != null) {
					gameType = detailObj.optString("gameType");
					HashMap detailMap = new HashMap<>();
					detailMap.put("refId", betId);
					detailMap.put("orgDetail", detailObj.toString());
					detailMap.put("parDetail", null);
					try {
						int result = siteService.insertEvoDetail(detailMap);
						log.info(LOG_PREFIX+ "insertEvoDetail result::" + result);
					} catch(DataIntegrityViolationException de) {
						log.error(LOG_PREFIX+ "Duplicate refId");
					}
				}
				if(gameType == null) gameType = "";
			}
			String tableId = transaction.optString("subcode");
			
			// ##--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);
					log.error(LOG_PREFIX+ "중복요청::Response::" + response.toString());
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					return resEntity;
				}
				
				// 베팅 체크
				int cntBet = siteService.getBetIdCheck2(betId);
				if(cntBet == 0) {
					// 처리한 베팅 존재X
					log.error(LOG_PREFIX+ "처리한 베팅 존재X");
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					return resEntity;
				}
			}

			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");
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
					return resEntity;
				}
			}
			
			boolean isParsing = false;
			String game = transaction.optString("game");
			if("evolution_new".equals(game)) {
				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", "onix");
			tranParam.put("vendorTranKey", betKey);
			tranParam.put("gameType", gameType);
			tranParam.put("gameId", tableId);
			tranParam.put("gameIdx", gameName);
			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", betKey);
			callBackObj.put("userId", memberId);
			callBackObj.put("vendorIdx", vendorIdx);
			callBackObj.put("vendorKey", vendorKey);
			callBackObj.put("vendor", "onix");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			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 + "PLAY_SEAMLESS");
				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");

					long balance = resData.getLong("balance");
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				} else if (resData.getLong("result_code") == 98) {
					long balance = resData.getLong("balance");
					response = OnixResponse.success(balance);
					resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.OK);
				} else {
					String msg = "";
					if (resData.has("error_msg")) msg = resData.getString("error_msg");
					log.error(LOG_PREFIX + msg);
					response = OnixResponse.failure();
					resEntity = new ResponseEntity<OnixResponse>(response, 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");
				
				response = OnixResponse.success(afBalance);
				resEntity = new ResponseEntity<OnixResponse>(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<OnixResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		} catch (Exception e) {
			log.error(LOG_PREFIX + "[Exception]" + e.toString());
			e.printStackTrace();
			resEntity = new ResponseEntity<OnixResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR);
		}

		return resEntity;
	}

	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;
	}
}
