package com.bb.front;

import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Random;

import org.apache.http.conn.ConnectTimeoutException;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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 org.springframework.web.reactive.function.client.WebClient;

import com.bb.jwt.JwtManager;
import com.bb.model.ApiResponse;
import com.bb.model.TplusCBData;
import com.bb.model.TplusCBRequest;
import com.bb.model.TplusData;
import com.bb.model.TplusResponse;
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;

@RestController
@Slf4j
@RequestMapping("/api/callback/tplus")
@RequiredArgsConstructor
@SecurityRequirement(name = "Authorization")
public class CallBackTplusController {

	@Autowired
	private final JwtManager jwtManager;

	@Autowired
	SiteService siteService;

	@Autowired
	CallBackService callBackService;

	@Autowired
	WebClient webClient;


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

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

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

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

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

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

			data = new TplusData();
			data.setUserName(siteUsername);
			response = new TplusResponse();
			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 TplusResponse();
			response.setCode(-1);
			response.setMsg("ResourceAccessException");
			httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			response = new TplusResponse();
			response.setCode(-1);
			response.setMsg("Exception");
			httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
		}

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

		return res;
	}


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

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

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

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

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

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

			JSONObject member = new JSONObject();
			member.put("userId", memberId);
			JSONObject resData  = callBackService.getBalance(siteApiInfo, member);
			if(resData == null) {
				response = new TplusResponse();
				response.setCode(-1);
				response.setMsg("BALANCE_CALLBACK_ERROR");
				httpStatus = HttpStatus.OK;
				return new ResponseEntity<TplusResponse>(response, httpStatus);
			}
			log.info(LOG_PREFIX + "resData::"+resData.toString());
			int balance = resData.getInt("balance");

			data = new TplusData();
			data.setBalance((double) balance);
			data.setCurrency("KRW");
			response = new TplusResponse();
			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 TplusResponse();
			response.setCode(-1);
			response.setMsg("ResourceAccessException");
			httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			response = new TplusResponse();
			response.setCode(-1);
			response.setMsg("Exception");
			httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
		}

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

		return res;
	}


	@ResponseBody
	@RequestMapping(path = "/bet")
	public ResponseEntity<TplusResponse> callBet(HttpServletRequest request, @RequestBody TplusCBRequest requestBody) throws Exception {
		TplusResponse response = null;
		TplusData data = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getApiKey();
		TplusCBData param = requestBody.getParam();
		String siteUsername = param.getUserName();

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

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

		long siteIdx = Long.parseLong(siteUsername.substring(0, 3), 16);
		String memberId = siteUsername.substring(3, siteUsername.length());
		String vendorTranKey = param.getTransactionKey();
		String tranId = param.getTransactionKey();
		String betId = param.getPairTransactionKey();
		if(betId == null || betId.equals("")) {
			betId = param.getTransactionKey();
		}

		final String LOG_PREFIX = "#-CB::TPLUS::"+memberId+"::"+tranId+"::callBet:::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

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

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

			long siteCredit = Long.parseLong(siteVendorInfo.get("credit").toString());
			if(siteCredit < amount) {
				response = new TplusResponse();
				response.setCode(-1);
				response.setMsg("NO_SITE_CREDIT");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "NO_SITE_CREDIT::Response::" + response.toString());
				return new ResponseEntity<TplusResponse>(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 TplusResponse();
				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<TplusResponse>(response, httpStatus);
			}

			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			String gameName = param.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", "tplus");
			tranParam.put("vendorTranKey", vendorTranKey);
			tranParam.put("gameType", "slot");
			tranParam.put("gameId", param.getGameSymbol());
			tranParam.put("gameIdx", gameName);
			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);
			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 TplusResponse();
				response.setCode(-1);
				response.setMsg("BET_TIMEOUT_ERROR");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "BET_TIMEOUT_ERROR::Response::" + response.toString());
				return new ResponseEntity<TplusResponse>(response, httpStatus);
			}
			log.info(LOG_PREFIX + "-----INSERT_BET_END-----");

			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", "tplus");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			callBackObj.put("gameType", "slot");
			callBackObj.put("gameId", param.getGameSymbol());
			callBackObj.put("tranType", "debit");
			callBackObj.put("debit", amount);
			callBackObj.put("credit", 0);
			callBackObj.put("isCancel", 0);
			callBackObj.put("isBonus", 0);
			callBackObj.put("requestAt", param.getProcessedAt());
			log.info(LOG_PREFIX + "SEND_BET request body: " + callBackObj.toString());
			JSONObject resData = callBackService.changeBalanceSlot(LOG_PREFIX, siteVendorInfo, callBackObj);
			log.info(LOG_PREFIX + "SEND_BET resData::"+resData.toString());
			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"));
				int res = siteService.updateCbApi(tranParam);
				log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

				int balance = resData.getInt("balance");
				data = new TplusData();
				data.setBalance((double) balance);
				data.setBeforeBalance((double) (balance + amount));
				data.setCurrency("KRW");
				response = new TplusResponse();
				response.setCode(0);
				response.setData(data);
				httpStatus = HttpStatus.OK;
			} else if(resData.getLong("result_code") == 80) {
				// 보유금액 초과
				response = new TplusResponse();
 				response.setCode(-1);
 				response.setMsg("금액을 확인하여 주세요");
 				httpStatus = HttpStatus.OK;
			} else if(resData.getLong("result_code") == 98) {
				// 중복자료
				int balance = resData.getInt("balance");
				data = new TplusData();
				data.setBalance((double) balance);
				data.setBeforeBalance((double) (balance + amount));
				data.setCurrency("KRW");
				response = new TplusResponse();
				response.setCode(0);
				response.setData(data);
				httpStatus = HttpStatus.OK;
			} else {
				response = new TplusResponse();
 				response.setCode(-1);
 				response.setMsg("UNKNOWN_ERROR");
 				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 TplusResponse();
			response.setCode(-1);
			response.setMsg("TIMEOUT_ERROR");
			httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

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

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

		return res;
	}


	@ResponseBody
	@RequestMapping(path = "/win")
	public ResponseEntity<TplusResponse> callWin(HttpServletRequest request, @RequestBody TplusCBRequest requestBody) throws Exception {
		TplusResponse response = null;
		TplusData data = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getApiKey();
		TplusCBData param = requestBody.getParam();
		String siteUsername = param.getUserName();

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

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

		long siteIdx = Long.parseLong(siteUsername.substring(0, 3), 16);
		String memberId = siteUsername.substring(3, siteUsername.length());
		String tranId = param.getTransactionKey();
		String betId = param.getPairTransactionKey();
		if(betId == null || betId.equals("")) {
			betId = param.getTransactionKey();
		}

		final String LOG_PREFIX = "#-CB::TPLUS::"+memberId+"::"+tranId+"::callWin:::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

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

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

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

			String vendorTranKey = param.getTransactionKey();
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			String gameName = param.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", "tplus");
			tranParam.put("vendorTranKey", vendorTranKey);
			tranParam.put("gameType", "slot");
			tranParam.put("gameId", param.getGameSymbol());
			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);
			log.info(LOG_PREFIX + "-----INSERT_RESULT_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 TplusResponse();
				response.setCode(-1);
				response.setMsg("BET_TIMEOUT_ERROR");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "BET_TIMEOUT_ERROR::Response::" + response.toString());
				return new ResponseEntity<TplusResponse>(response, httpStatus);
			}
			log.info(LOG_PREFIX + "-----INSERT_RESULT_END-----");

			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", "tplus");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			callBackObj.put("gameType", "slot");
			callBackObj.put("gameId", param.getGameSymbol());
			callBackObj.put("tranType", "credit");
			callBackObj.put("debit", 0);
			callBackObj.put("credit", amount);
			callBackObj.put("isCancel", 0);
			callBackObj.put("isBonus", 0);
			callBackObj.put("requestAt", param.getProcessedAt());
			log.info(LOG_PREFIX + "SEND_RESULT request body: " + callBackObj.toString());
			JSONObject resData = callBackService.changeBalanceSlot(LOG_PREFIX, siteVendorInfo, callBackObj);
			log.info(LOG_PREFIX + "SEND_RESULT resData::"+resData.toString());
			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"));
				int res = siteService.updateCbApi(tranParam);
				log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

				int balance = resData.getInt("balance");
				data = new TplusData();
				data.setBalance((double) balance);
				data.setBeforeBalance((double) (balance - amount));
				data.setCurrency("KRW");
				response = new TplusResponse();
				response.setCode(0);
				response.setData(data);
				httpStatus = HttpStatus.OK;
			} else if(resData.getLong("result_code") == 98) {
				int balance = resData.getInt("balance");
				data = new TplusData();
				data.setBalance((double) balance);
				data.setBeforeBalance((double) (balance - amount));
				data.setCurrency("KRW");
				response = new TplusResponse();
				response.setCode(0);
				response.setData(data);
				httpStatus = HttpStatus.OK;
			} else {
				response = new TplusResponse();
 				response.setCode(-1);
 				response.setMsg("UNKNOWN_ERROR");
 				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 TplusResponse();
			response.setCode(-1);
			response.setMsg("TIMEOUT_ERROR");
			httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

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

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

		return res;
	}


	@ResponseBody
	@RequestMapping(path = "/cancel")
	public ResponseEntity<TplusResponse> callCancel(HttpServletRequest request, @RequestBody TplusCBRequest requestBody) throws Exception {
		TplusResponse response = null;
		TplusData data = null;
		HttpStatus httpStatus = null;

		String apiKey = requestBody.getApiKey();
		TplusCBData param = requestBody.getParam();
		String siteUsername = param.getUserName();

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

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

		long siteIdx = Long.parseLong(siteUsername.substring(0, 3), 16);
		String memberId = siteUsername.substring(3, siteUsername.length());
		String tranId = param.getTransactionKey();
		String betId = param.getPairTransactionKey();
		if(betId == null || betId.equals("")) {
			betId = param.getTransactionKey();
		}

		final String LOG_PREFIX = "#-CB::TPLUS::"+memberId+"::"+tranId+"::callCancel:::";
		log.info(LOG_PREFIX+ "Request::" + requestBody.toString());

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

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

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

			String vendorTranKey = param.getTransactionKey();
			String vendorKey = siteVendorInfo.get("vendorKey").toString();
			int vendorIdx = Integer.parseInt(siteVendorInfo.get("vendorIdx").toString());
			String vendorCetegory = siteVendorInfo.get("vendorCetegory").toString();
			String gameName = param.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", "tplus");
			tranParam.put("vendorTranKey", vendorTranKey);
			tranParam.put("gameType", "slot");
			tranParam.put("gameId", param.getGameSymbol());
			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);
			log.info(LOG_PREFIX + "-----INSERT_CANCEL_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 TplusResponse();
				response.setCode(-1);
				response.setMsg("BET_TIMEOUT_ERROR");
				httpStatus = HttpStatus.OK;
				log.error(LOG_PREFIX+ "BET_TIMEOUT_ERROR::Response::" + response.toString());
				return new ResponseEntity<TplusResponse>(response, httpStatus);
			}
			log.info(LOG_PREFIX + "-----INSERT_CANCEL_START-----");

			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", "tplus");
			callBackObj.put("gameIdx", vendorIdx);
			callBackObj.put("gameKey", gameName);
			callBackObj.put("gameType", "slot");
			callBackObj.put("gameId", param.getGameSymbol());
			callBackObj.put("tranType", "credit");
			callBackObj.put("debit", 0);
			callBackObj.put("credit", amount);
			callBackObj.put("isCancel", 1);
			callBackObj.put("isBonus", 0);
			callBackObj.put("requestAt", param.getProcessedAt());
			log.info(LOG_PREFIX + "SEND_CANCEL request body: " + callBackObj.toString());
			JSONObject resData = callBackService.changeBalanceSlot(LOG_PREFIX, siteVendorInfo, callBackObj);
			log.info(LOG_PREFIX + "SEND_CANCEL resData::"+resData.toString());
			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"));
				int res = siteService.updateCbApi(tranParam);
				log.info(LOG_PREFIX + "UPDATE_CALLBACK_API_STATUS END");

				int balance = resData.getInt("balance");
				data = new TplusData();
				data.setBalance((double) balance);
				data.setBeforeBalance((double) (balance - amount));
				data.setCurrency("KRW");
				response = new TplusResponse();
				response.setCode(0);
				response.setData(data);
				httpStatus = HttpStatus.OK;
			} else if(resData.getLong("result_code") == 98) {
				int balance = resData.getInt("balance");
				data = new TplusData();
				data.setBalance((double) balance);
				data.setBeforeBalance((double) (balance - amount));
				data.setCurrency("KRW");
				response = new TplusResponse();
				response.setCode(0);
				response.setData(data);
				httpStatus = HttpStatus.OK;
			} else {
				response = new TplusResponse();
 				response.setCode(-1);
 				response.setMsg("UNKNOWN_ERROR");
 				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 TplusResponse();
			response.setCode(-1);
			response.setMsg("TIMEOUT_ERROR");
			httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

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

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

		return res;
	}


	@ResponseBody
	@RequestMapping(value={"/newChangeBalance"})
	public ResponseEntity<ApiResponse> newChangeBalance(HttpServletRequest request) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		HttpHeaders headers = new HttpHeaders();

		int renturnCode = 200;

		try {
			//사이트정보
			HashMap sParam = new HashMap();
			long siteIdx = Long.parseLong(request.getParameter("username").toString().substring(0, 3), 16);
			sParam.put("siteIdx", siteIdx);
			String memberId =request.getParameter("username").toString().substring(3, request.getParameter("username").toString().length());
			sParam.put("memberId", memberId);
			HashMap siteApiInfo = siteService.getSiteApiInfo(sParam);
			if(siteApiInfo==null) {
				renturnCode = 500;
				log.error("#-tplus::newChangeBalance::"+siteIdx+"::"+ request.getQueryString());
				return new ResponseEntity<ApiResponse>(apiResponse, headers, HttpStatus.valueOf(renturnCode)); // ResponseEntity를 활용한 응답 생성;
			}

			String siteId = siteApiInfo.get("siteId").toString();
			long memberIdx = (long) siteApiInfo.get("memberIdx");

			String tranId = (siteId+request.getParameter("transaction_key").toString()).trim();
			final String LOG_PREFIX = "#-tplus::newChangeBalance::"+siteIdx+"::"+memberIdx+"::"+tranId+"::::";

			String tranType = "";
			String vendorApiId = request.getParameter("agent_name").toString();
			String vendorApiKey = request.getParameter("api_key").toString();

			String balanceStr = request.getParameter("balance").toString();
			log.debug(LOG_PREFIX+ "balanceStr() : " + balanceStr);

			Double balanceD = Double.parseDouble(balanceStr);
			log.debug(LOG_PREFIX+ "balanceD() : " + balanceD);

			int balance = balanceD.intValue();
			log.info(LOG_PREFIX+ "balance() : " +balance);

			String amountStr = request.getParameter("amount").toString();
			log.debug(LOG_PREFIX+ "amountStr() : " + amountStr);

			Double amountD = Double.parseDouble(amountStr);
			log.debug(LOG_PREFIX+ "amountD() : " + amountD);

			long amount = amountD.longValue();
			log.info(LOG_PREFIX+ "amount() : " +amount);

			HttpHeaders header = new HttpHeaders();
			header.add("Content-Type", "application/json");
			header.add("Accept", "application/json");
			header.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");

			JSONObject debit = new JSONObject();
			debit.put("tranId", tranId);
			debit.put("userId", memberId);
			debit.put("vendor", "tplus");
			if("BET".equals(request.getParameter("type").toString())) {
				tranType = "debit";
				debit.put("tranType", "debit");
				debit.put("amount", amount);
				debit.put("balance", balance);
			} else if("WIN".equals(request.getParameter("type").toString())) {
				tranType = "credit";
				debit.put("tranType", "credit");
				debit.put("amount",  amount);
				debit.put("balance", balance);
			} else {
				if("deposit".equals(request.getParameter("type").toString())) {
					tranType = "deposit";
				} else {
					tranType = "withdraw";
				}

				renturnCode = 200;
				return new ResponseEntity<ApiResponse>(apiResponse, headers, HttpStatus.valueOf(renturnCode)); // ResponseEntity를 활용한 응답 생성;
			}

			log.info(LOG_PREFIX+ "/syncBalance param: " + debit.toString());

			ResponseEntity<String> responseEntity = webClient.post()
					.uri(siteApiInfo.get("siteCbUrl")+"/changebalance/syncBalance")
					.headers(h -> h.addAll(header))
					.bodyValue(debit.toString())
					.retrieve()
					.toEntity(String.class)
					.block();

			log.info(LOG_PREFIX+ "/syncBalance status code : " + responseEntity.getStatusCode());
			log.info(LOG_PREFIX+ "body: " + responseEntity.getBody());

			org.codehaus.jettison.json.JSONObject resData  = new org.codehaus.jettison.json.JSONObject(responseEntity.getBody());
			log.info(LOG_PREFIX+ "result_code: " + resData.getInt("result_code"));

			if(resData.getLong("result_code") == 0) {
				log.info(LOG_PREFIX+ "SUCC");
				renturnCode = 200;
			} else {
				log.error(LOG_PREFIX+ "FAIL");
				renturnCode = 500;
			}

		} catch (ResourceAccessException rae) {
            if(rae.getCause() instanceof ConnectTimeoutException) {
            	log.error("#-tplus::newChangeBalance::::["+request.getParameter("transaction_key").toString()+"]"+"::[ConnectTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof SocketTimeoutException) {
            	log.error("#-tplus::newChangeBalance::::["+request.getParameter("transaction_key").toString()+"]"+"::[SocketTimeoutException]"+rae.getMessage());
            }

            if(rae.getCause() instanceof InterruptedException) {
            	log.error("#-tplus::newChangeBalance::::["+request.getParameter("transaction_key").toString()+"]"+"::[InterruptedException]"+rae.getMessage());
            }

            renturnCode = 200;
			log.error("#-tplus::newChangeBalance::::["+request.getParameter("transaction_key").toString()+"]"+"::[ResourceAccessException]"+rae.getMessage());
		} catch (Exception e) {
			System.out.println(e.toString());
			e.printStackTrace();
			renturnCode = 500;
			log.error("#-tplus::newChangeBalance::::["+request.getParameter("transaction_key").toString()+"]"+"::[Exception]"+e.getMessage());
		}

		log.info("#-tplus::newChangeBalance::::["+request.getParameter("transaction_key").toString()+"]::"+"::apiResponse: " + apiResponse.toString());
		return new ResponseEntity<ApiResponse>(apiResponse, headers, HttpStatus.valueOf(renturnCode)); // ResponseEntity를 활용한 응답 생성;
	}


	private String makeApiKey(String param) {

		int n = 20; // n자리 쿠폰
		char[] chs = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
		  		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
		  		'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' ,'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };

		Random rd = new Random();
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < n; i++) {
			char ch = chs[rd.nextInt(chs.length)];
			sb.append(ch);
		}
		return param + sb.toString();
	}

}

