package com.bb.front;

import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
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.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientRequestException;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.util.UriComponentsBuilder;

import com.bb.exception.ApiException;
import com.bb.jwt.JwtClame;
import com.bb.jwt.JwtManager;
import com.bb.model.ApiResponse;
import com.bb.model.AuthParam;
import com.bb.model.BetParam;
import com.bb.model.CashParam;
import com.bb.model.CommonParam;
import com.bb.model.Credit;
import com.bb.model.Game;
import com.bb.model.Member;
import com.bb.model.MemberParam;
import com.bb.model.NexusApiInfo;
import com.bb.model.PlayParam;
import com.bb.model.Site;
import com.bb.model.TplusApiInfo;
import com.bb.model.UserKickParam;
import com.bb.service.AsyncSiteService;
import com.bb.service.BetRadarService;
import com.bb.service.CreditService;
import com.bb.service.GrandService;
import com.bb.service.GsoftService;
import com.bb.service.NexusService;
import com.bb.service.PandaService;
import com.bb.service.PowerBallService;
import com.bb.service.PracpService;
import com.bb.service.PragmaticService;
import com.bb.service.SiteService;
import com.bb.service.SvendorService;
import com.bb.service.ThenutService;
import com.bb.service.TowerService;
import com.bb.service.TplusService;
import com.bb.service.TransService;
import com.bb.util.AesUtil;
import com.bb.util.IPKit;
import com.bb.util.StringUtils;
import com.bb.util.TempKey;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;

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

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

	@Autowired
	private final JwtManager jwtManager;

	@Autowired
	SiteService siteService;

	@Autowired
	TransService transService;

	@Autowired
	CreditService creditService;

	@Autowired
	TplusService tplusService;

	@Autowired
	NexusService nexusService;

	@Autowired
	GsoftService gsoftService;

	@Autowired
	PowerBallService powerBallService;

	@Autowired
	TowerService towerService;

	@Autowired
	SvendorService svendorService;

	@Autowired
	GrandService grandService;

	@Autowired
	PracpService pracpService;

	@Autowired
	PandaService pandaService;

	@Autowired
	BetRadarService betRadarService;

	@Autowired
	PragmaticService pragmaticService;

	@Autowired
	ThenutService thenutService;

	@Autowired
	AsyncSiteService asyncSiteService;

	@Autowired
	WebClient webClient;

	@GetMapping("/health")
	public String health( HttpServletRequest request,ModelMap model) throws Exception {

		return "health";
	}


	@ResponseBody
	@PostMapping("/kick")
	public ApiResponse userKick(@RequestHeader String token, HttpServletRequest request, @RequestBody UserKickParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();

		try {
			Site site = siteService.getSiteInfoAPI(request);
			if(site == null) {
				apiResponse.setResultCode("9999");
				apiResponse.setResultMessage("Authorization apiKey check");
				return apiResponse;
			}

			if( !( site.getSiteIp()==null || "".equals(site.getSiteIp())
					|| "3.3.3.3".equals(site.getSiteIp()) || site.getSiteIp().indexOf(IPKit.getIpAddressByRequest(request)) >= 0) ) {
				log.error("site.getSiteIp("+site.getSiteIp()+"), denied" + IPKit.getIpAddressByRequest(request));
				apiResponse.setResultCode("10001");
				apiResponse.setResultMessage("access denied IP");
				return apiResponse;
			}

			try {
				JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfoAdmin(token);
				final String LOG_PREFIX = "#-API::USER::userKick::"+tokenInfo.getSid()+"::::";

				log.info(LOG_PREFIX+ "UserKickParam::"+param.toString());

				// 회원 정보 조회
				CashParam cashParam = new CashParam();
				cashParam.setSiteIdx(site.getSiteIdx());
				cashParam.setMemberId(param.getUserid());
				Member member = siteService.getMember2(cashParam);

				if(member == null) {
					log.error(LOG_PREFIX+ "NOT_FOUND_USER");
					apiResponse.setResultCode("0");
					apiResponse.setResultMessage("존재하지 않는 유저입니다");
					return apiResponse;
				}

				// 유저의 마지막 접속 벤더 조회
				String lastVendor = null;
				if(member.getLastInVendorIdx() > 0) {
					// lastInVendorIdx로 벤더 정보 조회
					HashMap<String, Object> vendorParam = new HashMap<>();
					vendorParam.put("siteIdx", member.getSiteIdx());
					vendorParam.put("vendorIdx", member.getLastInVendorIdx());

					HashMap<String, String> vendorInfo = siteService.getSiteOnlyVendorInfo(vendorParam);
					if(vendorInfo != null && vendorInfo.containsKey("vendorCode")) {
						lastVendor = vendorInfo.get("vendorCode");
						log.info(LOG_PREFIX + "Found last vendor: " + lastVendor + " (vendorIdx: " + member.getLastInVendorIdx() + ")");
					}
				}

				boolean kickResult = false;

				if(lastVendor != null && !lastVendor.isEmpty()) {
					log.info(LOG_PREFIX + "Last vendor: " + lastVendor);

					// 벤더별 강제 종료 처리
					switch(lastVendor.toLowerCase()) {
						case "dpcore":
							// TODO: DPCore API 호출
							log.info(LOG_PREFIX + "Calling DPCore kick API");
							kickResult = true;
							break;
						case "ace2":
							// TODO: Ace API 호출
							log.info(LOG_PREFIX + "Calling Ace kick API");
							kickResult = true;
							break;
						case "onix":
							// Onix API 호출
							try {
								HashMap<String, Object> onixVendorParam = new HashMap<>();
								onixVendorParam.put("siteIdx", member.getSiteIdx());
								onixVendorParam.put("vendorCode", "onix");
								HashMap<String, String> onixApiInfo = siteService.getSiteOnlyVendorInfo(onixVendorParam);

								if(onixApiInfo != null && !onixApiInfo.isEmpty()) {
									String apiUrl = onixApiInfo.get("apiUrl");
									String apiKey = onixApiInfo.get("apiKey");

									if(apiUrl != null && apiKey != null) {
										String kickUrl = apiUrl + "/csapi/kick";
										log.info(LOG_PREFIX + "Calling Onix kick API: " + kickUrl);

										try {
											// Request Body 생성
											HashMap<String, String> requestBody = new HashMap<>();
											requestBody.put("userid", member.getMemberId());

											// WebClient로 Onix kick API 호출
											String response = webClient.post()
												.uri(kickUrl)
												.header("Authorization", "Bearer " + apiKey)
												.header("Content-Type", "application/json")
												.bodyValue(requestBody)
												.retrieve()
												.bodyToMono(String.class)
												.block();

											log.info(LOG_PREFIX + "Onix API Response: " + response);

											// Response 파싱
											if(response != null && response.contains("\"result\":1")) {
												kickResult = true;
												log.info(LOG_PREFIX + "Onix kick success");
											} else {
												log.error(LOG_PREFIX + "Onix kick failed: " + response);
											}
										} catch(Exception apiException) {
											log.error(LOG_PREFIX + "Onix API call exception", apiException);
										}
									}
								}
							} catch(Exception e) {
								log.error(LOG_PREFIX + "Onix kick API error", e);
							}
							break;
						case "pink":
							// TODO: Pink API 호출
							log.info(LOG_PREFIX + "Calling Pink kick API");
							kickResult = true;
							break;
						default:
							log.info(LOG_PREFIX + "Vendor not supported for kick: " + lastVendor);
							kickResult = true;
							break;
					}
				} else {
					log.info(LOG_PREFIX + "No vendor info found, returning success");
					kickResult = true;
				}

				if(kickResult) {
					log.info(LOG_PREFIX+ "USER_KICK_SUCCESS::"+param.getUserid());
					apiResponse.setResultCode("1");
					apiResponse.setResultMessage("강퇴 성공");
				} else {
					log.error(LOG_PREFIX+ "USER_KICK_FAILED::"+param.getUserid());
					apiResponse.setResultCode("0");
					apiResponse.setResultMessage("강퇴 실패");
				}

			} catch(Exception e) {
				log.error(e.toString());
				e.printStackTrace();
				apiResponse.setResultCode("0");
				apiResponse.setResultMessage("token is no valid");
			}

		} catch (Exception e) {
			log.error("Kick API error", e);
			e.printStackTrace();
			apiResponse.setResultCode("0");
			apiResponse.setResultMessage("강퇴 실패");
		}

		return apiResponse;
	}


	@ResponseBody
	@PostMapping(value="/auth")
	public synchronized ApiResponse auth(HttpServletRequest request, @RequestBody AuthParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		final String LOG_PREFIX = "#-auth::"+param.getUserId()+"::::";

		// apiKey 체크
		Site site = siteService.getSiteInfo(request);
		if(site == null) {
			log.error(LOG_PREFIX+ "A1001:Access denied Key");
			apiResponse.setResultCode("A1001");
			apiResponse.setResultMessage("Access denied Key");
			return apiResponse;
		}
		 System.out.println("site.getSiteIp().indexOf(IPKit.getIpAddressByRequest("
		+site.getSiteIp()+")"
				+IPKit.getIpAddressByRequest(request) +","+ site.getSiteIp().indexOf(IPKit.getIpAddressByRequest(request)) );

		if( !( site.getSiteIp() == null  ||  "".equals(site.getSiteIp())  ||  "3.3.3.3".equals(site.getSiteIp())  ||  site.getSiteIp().indexOf(IPKit.getIpAddressByRequest(request)) >= 0 ) ) {
			log.error(LOG_PREFIX+ "site.getSiteIp("+site.getSiteIp()+"), denied" + IPKit.getIpAddressByRequest(request));
			log.error(LOG_PREFIX+ "A1002:Access denied IP");
			apiResponse.setResultCode("A1002");
			apiResponse.setResultMessage("Access denied IP");
			return apiResponse;
		}

		ObjectMapper objectMapper = new ObjectMapper();
		String reqJsonStr = objectMapper.writeValueAsString(param);
		JSONObject reqObj = new JSONObject(reqJsonStr);
		log.info(LOG_PREFIX+ "Request {}", reqObj);

		try {
			//회원아이디 유표성체크
			String regex = "^[a-zA-z0-9]{3,25}$";
			Pattern p = Pattern.compile(regex);

			Matcher m = p.matcher(param.getUserId());

			if(!m.matches()) {
				log.error(LOG_PREFIX+ "A1003:Check user id");
				apiResponse.setResultCode("A1003");
				apiResponse.setResultMessage("Check user id");
				return apiResponse;
			}

			if(param.getUserId().length() > 19) {
				log.error(LOG_PREFIX+ "A1004:Check user id length");
				apiResponse.setResultCode("A1004");
				apiResponse.setResultMessage("Check user id length");
				return apiResponse;
			}


			// 회원 유뮤체크
			CommonParam commonParam = new CommonParam();
			commonParam.setSiteIdx(site.getSiteIdx());
			commonParam.setAuthParam(param);
			Member member = siteService.getMember(commonParam);

			// 회원 없을경우 가입시키기
			if(member == null) {
				siteService.insertMember(commonParam);
				member = siteService.getMember(commonParam);
			}

			// 폭탄게임 전용 본인 체크키 업데이트
			if(member.getDowinId() == null || "".equals(member.getDowinId())) {
				String bombToken = new TempKey().generateAuthKey();
				member.setBombAccId(bombToken);
				member.setDowinId(bombToken);
				siteService.updateBombKey(member);
			}

			log.info(LOG_PREFIX+ "member::result: " + member.toString());

			// 토큰생성
			JwtClame jwtinfo = new JwtClame();
			jwtinfo.setSiteId(site.getSiteId());
			jwtinfo.setMemId(commonParam.getAuthParam().getUserId());
			String tok = jwtManager.generateToken(jwtinfo);


			// 파워볼 API Key 발급 및 site_vendor_map.vendorApiId, site_vendor_map.vendorApiKey 세팅 선처리
			try {

			} catch(Exception e) {
				log.error(LOG_PREFIX+ "POWER_BALL::Exception: " + e.getMessage());
				log.error(LOG_PREFIX+ "POWER_BALL::Exception: " + e.toString());
			}

			// 결과보내기
			apiResponse.put("userId", member.getMemberIdx());
			apiResponse.put("token", tok);
			apiResponse.success();

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "Exception::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}


	@ResponseBody
	@GetMapping(value="/playBombDemo")
	public ApiResponse playBombDemo(HttpServletRequest request) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
			String lauchURL="";
			String bombToken = new TempKey().generateAuthKey();

			siteService.inserDemoUSer(bombToken);


			String gameUrl ="http://tpabomb.com?";
			String gameUrl2	= "userID="+bombToken;

			String language = "en";



			gameUrl2 += "&cc=pan" ;
			gameUrl2 += "&nation=" + language;
			gameUrl2 += "&loginToken=" +bombToken;


			log.info("###-BOMB API-### Request : " + gameUrl2);
			lauchURL = gameUrl + AesUtil.getAES128_Encode(gameUrl2);
			apiResponse.put("gameurl", lauchURL);

			return apiResponse;

	}

	@ResponseBody
	@PostMapping(value="/play")
	public ApiResponse play(@RequestHeader String token, HttpServletRequest request, @RequestBody PlayParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();

		final String LOG_PREFIX = "#-play::"+param.getUserId()+"::::";

		try {
			if(param.getRequestAT() == null || param.getRequestAT().equals("")) {
				param.setRequestAT(Long.toString(System.currentTimeMillis()));
			}
			long requestAT1 = Long.parseLong(param.getRequestAT());
			long currentAT1 = System.currentTimeMillis();
			SimpleDateFormat chkSdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
			String requestATStr1 = chkSdf1.format(requestAT1);
			String currentATStr1 = chkSdf1.format(currentAT1);
			log.info(LOG_PREFIX+ "## /play ##[requestATStr:"+requestATStr1+"][currentATStr:"+currentATStr1+"]");
			log.info(LOG_PREFIX+ "Authorization::"+request.getHeader("Authorization"));
			log.info(LOG_PREFIX+ "token::"+token);

			ObjectMapper objectMapper = new ObjectMapper();
			String reqJsonStr = objectMapper.writeValueAsString(param);
			JSONObject reqObj = new JSONObject(reqJsonStr);
			log.info(LOG_PREFIX+ "Request {}", reqObj);

			String ip = param.getUserIp();
			String[] ipArray = ip.split(",");
			log.info(LOG_PREFIX+ "ipArray::"+ipArray.toString());
	        ip = ipArray[0];
	        param.setUserIp(ip);

			log.info(LOG_PREFIX+ "PlayParam::"+param.toString());
			/* Date타입으로 변경 */
			double diff1 = (currentAT1 - requestAT1) / 1000.0; // 초
			if(diff1 > 3.5) {
				log.error(LOG_PREFIX+ "/play ## "+diff1+"초 통신 지연발생[requestATStr:"+requestATStr1+"][currentATStr:"+currentATStr1+"]");
				log.error(LOG_PREFIX+ "P1001:Network time delay");
				throw new ApiException("P1001", "Network time delay");
			}

			//apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site==null) {
				log.error(LOG_PREFIX+ "P1002:Access denied Key");
				throw new ApiException("P1002", "Access denied Key");
			}
			if(token==null) {
				log.error(LOG_PREFIX+ "P1003:Access token check");
				throw new ApiException("P1003", "Access token check");
			}

			//토큰체크
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);
			log.info(LOG_PREFIX+ "TokenInfo::"+tokenInfo.toString());

			final String IS_TRANSFER = site.getIsTransfer();

			//회원정보
			AuthParam authParam = new AuthParam();
			authParam.setUserId(tokenInfo.getMid());
			CommonParam commonParam = new CommonParam();
			commonParam.setSiteIdx(site.getSiteIdx());
			commonParam.setAuthParam(authParam);
			commonParam.setPlayParam(param);
			log.info(LOG_PREFIX+ "CommonParam::"+commonParam.toString());
			Member member = siteService.getMember(commonParam);

			//회사명 회사연동 벤더변 벤더연동
			HashMap venderInfo = siteService.getVenderApiInfo(commonParam);
			if(venderInfo == null) {
				venderInfo = siteService.getOnlyVenderInfo(commonParam);
				if(venderInfo == null) {
					log.error(LOG_PREFIX+ "P1004:Vendor not setting");
					throw new ApiException("P1004", "Vendor not setting");
				}
			}

						// userMaxBet 처리 (유저별 최대베팅제한금액)
			Long userMaxBet = param.getUserMaxBet();
			if(userMaxBet == null) {
				userMaxBet = 0L; // 값이 비워져있으면 0으로 처리
			}

			// site_member 테이블에 userMaxBet 저장
			HashMap userMaxBetParam = new HashMap<>();
			userMaxBetParam.put("siteIdx", site.getSiteIdx());
			userMaxBetParam.put("memberIdx", member.getMemberIdx());
			userMaxBetParam.put("userMaxBet", userMaxBet);
			int updateResult = siteService.updateUserMaxBet(userMaxBetParam);
			log.info(LOG_PREFIX+ "updateUserMaxBet Result: " + updateResult + ", userMaxBet: " + userMaxBet);

			// tran_log 테이블에 로그 기록 시도 (실패해도 게임 플레이는 계속됨)
			try {
				HashMap tranLogParam = new HashMap<>();
				tranLogParam.put("siteIdx", site.getSiteIdx());
				tranLogParam.put("vendorIdx", venderInfo.get("vendorIdx"));
				tranLogParam.put("gameIdx", param.getGameKey());

				// body에 memberIdx와 userMaxBet 정보 포함
				JSONObject bodyJson = new JSONObject();
				bodyJson.put("memberIdx", member.getMemberIdx());
				bodyJson.put("memberId", member.getMemberId());
				bodyJson.put("userMaxBet", userMaxBet);
				bodyJson.put("userId", param.getUserId());
				bodyJson.put("gameKey", param.getGameKey());
				tranLogParam.put("body", bodyJson.toString());

				int logResult = siteService.insertTranLog(tranLogParam);
				log.info(LOG_PREFIX+ "insertTranLog Result: " + logResult);
			} catch (Exception e) {
				log.error(LOG_PREFIX+ "insertTranLog failed but continue play: " + e.getMessage());
			}

			long userBalance = 0;
			long siteCredit = site.getCredit();
			String lauchURL ="";
			String callbackType = "S";
			// ##--isTransfer 처리-##
			if(IS_TRANSFER.equals("N")) {
				if(param.getBalance() == null) {
					log.error(LOG_PREFIX+ "P1005:User balance not null");
					throw new ApiException("P1005", "User balance not null");
				}

				userBalance = param.getBalance();

				if(siteCredit < userBalance) {
					log.error(LOG_PREFIX+ "P1006:Insufficient site credit");
					throw new ApiException("P1006", "Insufficient site credit");
				}
			}

			log.info(LOG_PREFIX+ "venderInfo::" + venderInfo.toString());
			final int START_VENDOR_IDX = Integer.parseInt(venderInfo.get("vendorIdx").toString());
			final String START_VENDOR_TITLE = venderInfo.get("vendorTitle").toString();

			Long requestTime = System.currentTimeMillis();
			HashMap accLog = new HashMap<>();
			accLog.put("siteIdx", site.getSiteIdx());
			accLog.put("memberIdx", member.getMemberIdx());
			// Timeout 체크
			Long userTimeout = siteService.getUserTimeout(accLog);
			if(userTimeout == null) {
				userTimeout = requestTime;
			} else {
				userTimeout = userTimeout + (1*1000);
			}
			if(requestTime < userTimeout) {
				log.error(LOG_PREFIX+ "P1007:재요청 대기시간(8초)");
				throw new ApiException("P1007", "재요청 대기시간(8초)");
			} else {
				log.info(LOG_PREFIX+ "마지막 요청으로부터 8초 이후에 요청");
				accLog.put("requestTime", requestTime);
				int result = siteService.updateUserTimeout(accLog);
				log.info(LOG_PREFIX+ "updateUserTimeout()::"+userTimeout);
				log.info(LOG_PREFIX+ "[TIMEOUT result : " + result + "]");
			}


			// Parsing start user set
			HashMap usePaeseY = siteService.getParseUseYn(site.getSiteIdx());
			boolean isParseStartUser = false;
			HashMap pum = new HashMap();
			if(usePaeseY != null && "Y".equals(usePaeseY.get("useYn").toString()) && START_VENDOR_IDX == 1
				&& (START_VENDOR_TITLE.equals("dpcore"))
			) {
				int parsEvoAmount = (int) usePaeseY.get("parsEvoAmount");
				long totalEvoBetAmt = member.getTotalEvoBetAmt();
				String parseEvoYn = member.getParseEvoYn();
				if(parsEvoAmount == 0) {
					isParseStartUser = true;
				} else {
					if(parsEvoAmount < totalEvoBetAmt && parseEvoYn.equals("Y")) {
						isParseStartUser = true;
					} else {
						isParseStartUser = false;
					}
				}
			} else {
				isParseStartUser = false;
			}

			// Parsing start user set
			String apiUsername = "";
			String apiNickname = "";
			String changeUrl = "";
			String changeApiKey = "";
			if(START_VENDOR_TITLE.equals("svendor") || START_VENDOR_TITLE.equals("pink")) {
				apiUsername = member.getVendorUsername();
				if(apiUsername==null || "".equals(apiUsername)) {

					int cnt = 0;
					do {
						apiUsername = StringUtils.generateString(12);
						cnt = siteService.checkMemberId(apiUsername);
					} while(cnt != 0);
					member.setVendorUsername(apiUsername);
				}
				apiNickname = site.getSiteId().substring(0, 2) + "_" + member.getMemberId();

				// ##--isTransfer 처리-##
				// Member balance sync
				HashMap balanceMap = new HashMap<>();
				balanceMap.put("siteIdx", member.getSiteIdx());
				balanceMap.put("memberIdx", member.getMemberIdx());
				balanceMap.put("memberId", member.getMemberId());
				balanceMap.put("vendorUsername", member.getVendorUsername());
				int updBalanceResult = siteService.updMemberBalance(balanceMap);
				log.info(LOG_PREFIX+ "updMemberBalance Result : " + updBalanceResult);
			} else {
				apiUsername = site.getSiteIdxHex() + member.getMemberId();
				apiNickname = apiUsername;
			}
			log.info(LOG_PREFIX+ "[PLAY_INFO] Mamber::" + member.toString());
			log.info(LOG_PREFIX+ "[PLAY_INFO] ParseUserMap::" + pum.toString());
			log.info(LOG_PREFIX+ "[PLAY_INFO] isParseStartUser::" + isParseStartUser);
			log.info(LOG_PREFIX+ "[PLAY_INFO] START_VENDOR_IDX::" + START_VENDOR_IDX);
			log.info(LOG_PREFIX+ "[PLAY_INFO] userBalance::" + userBalance);
			log.info(LOG_PREFIX+ "[PLAY_INFO] apiUsername::" + apiUsername);
			log.info(LOG_PREFIX+ "[PLAY_INFO] apiNickname::" + apiNickname);
			log.info(LOG_PREFIX+ "[PLAY_INFO] changeUrl::" + changeUrl);
			log.info(LOG_PREFIX+ "[PLAY_INFO] changeApiKey::" + changeApiKey);

			if(venderInfo.get("vendorCode").toString().contains("org_pragmatic")) {

				String tok = apiUsername;
				log.info(LOG_PREFIX+ "[PLAY_INFO] tok::" + tok);
				venderInfo.put("username", apiUsername);

				if("org_pragmatic_casino".equals(venderInfo.get("vendorCode").toString())) {
					venderInfo.put("game_key", "101");
				}else {
					venderInfo.put("game_key", param.getGameKey());

				}

				lauchURL = pragmaticService.getLaunchUrl(LOG_PREFIX, tok, venderInfo, param, site.getSiteIdxHex() + member.getMemberId());
				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");
				callbackType = "S";

			} else if(venderInfo.get("vendorCode").toString().contains("panda_pragmatic_slot")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAgentId = venderInfo.get("vendorApiId").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String apiVendor = venderInfo.get("vendorGameCode").toString();
				String apiGameCode = param.getGameKey();

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiAgentId", apiAgentId);
				apiInfo.put("apiKey", apiKey);
				apiInfo.put("apiVendor", apiVendor);
				apiInfo.put("apiGameCode", apiGameCode);
				apiInfo.put("apiUsername", site.getSiteIdxHex() + member.getMemberId());
				apiInfo.put("apiNickname", apiNickname);
				log.info(LOG_PREFIX+ "PANDA::API Info::" + apiInfo);

				lauchURL = pandaService.getLaunchUrl(LOG_PREFIX, apiInfo);

				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");
				callbackType = "S";

			} else if(venderInfo.get("vendorCode").toString().contains("pracp_pragmatic_slot")) {

				venderInfo.put("username", site.getSiteIdxHex() + member.getMemberId());
				venderInfo.put("vendor_key","pragmatic_slot");
				venderInfo.put("game_key", param.getGameKey());

				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAgentCode = venderInfo.get("vendorApiId").toString();
				String apiToken = venderInfo.get("vendorApiKey").toString();
				String apiProviderCode = venderInfo.get("vendorGameCode").toString();
				String apiGameCode = param.getGameKey();

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiAgentCode", apiAgentCode);
				apiInfo.put("apiToken", apiToken);
				apiInfo.put("apiProviderCode", apiProviderCode);
				apiInfo.put("apiGameCode", apiGameCode);
				apiInfo.put("apiPlayerCode", site.getSiteIdxHex() + member.getMemberId());
				log.info(LOG_PREFIX+ "PRACP::API Info::" + apiInfo);

				lauchURL = pracpService.getLaunchUrl(LOG_PREFIX, apiInfo);

				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");
				callbackType = "S";

			} else if(venderInfo.get("vendorCode").toString().contains("grand_pragmatic_slot")) {

				venderInfo.put("username", site.getSiteIdxHex() + member.getMemberId());
				venderInfo.put("vendor_key","pragmatic_slot");
				venderInfo.put("game_key", param.getGameKey());
				lauchURL = grandService.getLaunchUrl(venderInfo);
				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");
				callbackType = "S";

			} else if(venderInfo.get("vendorCode").toString().contains("svendor_")
                    || venderInfo.get("vendorCode").toString().contains("pink_")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAgent = venderInfo.get("vendorApiId").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String apiVendor = venderInfo.get("vendorGameCode").toString();
				String apiUserIp = param.getUserIp();
				String apiGameKey = "";
				String apiGameSkin = "";

				String category = venderInfo.get("vendorGameCategory").toString();
				String vendorKey = venderInfo.get("vendorKey").toString();


				if("SLOT".equals(category)) {
					apiGameKey = param.getGameKey();
				} else {
					// Casino
					if(vendorKey.equals("C01")) {
						// Evolution
						apiGameKey = "top_games";
						if(param.getSkinType() != null && !param.getSkinType().equals("")) {
							apiGameSkin = param.getSkinType();
						} else {
							apiGameSkin = venderInfo.get("extrSkinType").toString();
						}
					} else if(vendorKey.equals("C02")) {
						// Pragmatic Live
						apiGameKey = "101";
					} else if(vendorKey.equals("C03")) {
						// Dream Gaming
						apiGameKey = "dgcasino";
					} else if(vendorKey.equals("C04")) {
						// 보타 카지노
						apiGameKey = "bota";
					} else if(vendorKey.equals("C10")) {
						// Asia Gaming
						apiGameKey = "0";
					} else if(vendorKey.equals("C05")) {
						// Microgaming Casino
						apiGameKey = "MGL_GRAND_LobbyAll";
					} else if(vendorKey.equals("C08")) {
						// Oriental Gaming
						apiGameKey = "1";
					} else if(vendorKey.equals("C09")) {
						// Ezugi
						apiGameKey = "1008";
					} else if(vendorKey.equals("C32")) {
						// 섹시 바카라
						apiGameKey = "MX-LIVE-001";
					} else {
						apiGameKey = "";
					}
				}

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiAgent", apiAgent);
				apiInfo.put("apiKey", apiKey);
				apiInfo.put("userid", apiUsername);
				apiInfo.put("nickname", apiUsername);
				apiInfo.put("userip", param.getUserIp());
				apiInfo.put("category", category);
				apiInfo.put("vendor", apiVendor);
				apiInfo.put("game", apiGameKey);
				apiInfo.put("skin", apiGameSkin);
				log.info(LOG_PREFIX+ "PINK::API Info::" + apiInfo);

				// Create User
				JSONObject createRes = svendorService.create(LOG_PREFIX, apiInfo);
				log.info(LOG_PREFIX+ "PINK::createRes::" + createRes.toString());
				if(createRes.getInt("code") == 0 || createRes.getInt("code") == 97) {
					lauchURL = svendorService.playUrl(LOG_PREFIX, apiInfo);
				} else {
					throw new ApiException("Svendor User Create ERROR");
				}

				log.info(LOG_PREFIX+ "PINK::lauchURL: " + lauchURL);
				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");
				callbackType = "S";

			} else if((venderInfo.get("vendorCode").toString().startsWith("tower_pragmatic_slot"))) {

				String gameApiUrl = venderInfo.get("vendorApiUrl").toString();
				gameApiUrl += "/v2/play?site_code="+venderInfo.get("vendorApiId").toString();
				gameApiUrl += "&user_id="+site.getSiteIdxHex()+member.getMemberId();
				gameApiUrl += "&nickname="+site.getSiteIdxHex()+member.getMemberId();
				gameApiUrl += "&vendorCode="+venderInfo.get("vendorGameCode").toString();
				gameApiUrl += "&gameCode="+param.getGameKey();
				gameApiUrl += "&user_ip="+param.getUserIp();
				gameApiUrl += "&session_token="+token;

				venderInfo.put("gameApiUrl", gameApiUrl);
				String launchUrl = towerService.getLaunchUrl(venderInfo);

				apiResponse.put("gameurl", launchUrl);
				apiResponse.put("callbackType", "S");

			} else if("splus".equals(venderInfo.get("vendorCode").toString())) {

				lauchURL = venderInfo.get("vendorApiUrl").toString() +"?operatorToken="+URLEncoder.encode(token, "UTF-8");
				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");

			} else if("bti_zeno".equals(venderInfo.get("vendorCode").toString())) {

				lauchURL = venderInfo.get("vendorApiUrl").toString() +"?operatorToken="+URLEncoder.encode(token, "UTF-8");
				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");

			} else if("bet_radar".equals(venderInfo.get("vendorCode").toString())) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String apiDevice = "DESKTOP";

				if(param.getIsMobile().equals("Y")) {
					apiDevice = "MOBILE";
				}

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiKey", apiKey);
				apiInfo.put("apiUsername", apiUsername);
				apiInfo.put("apiNickname", apiNickname);
				apiInfo.put("apiDevice", apiDevice);

				lauchURL = betRadarService.gameLaunch(LOG_PREFIX, apiInfo);
				if(lauchURL == null || lauchURL.equals("")) {
					log.error(LOG_PREFIX+ "BET_RADAR::GAME_LAUNCH:::Error");
					throw new ApiException("P111", "GAME_START_ERROR");
				} else {
					apiResponse.put("gameurl", lauchURL);
					apiResponse.put("callbackType", "S");
					callbackType = "S";
				}

			} else if("tpabomb".equals(venderInfo.get("vendorCode").toString())) {
				log.info(LOG_PREFIX+ "bomb::"+ "###-BOMB API-### param: " + param.toString());
				//String gameUrl ="http://"+venderInfo.get("vendorAuthApiUrl").toString()+"/user/login/"+venderInfo.get("vendorApiId").toString();
				String gameUrl ="http://"+venderInfo.get("vendorAuthApiUrl").toString()+"?";
				String gameUrl2	= "userID="+site.getSiteIdxHex()+member.getMemberId();

				String language = "KR";
				if( !(param.getLanguage() == null || "".equals(param.getLanguage()))) {
					language = param.getLanguage();
				}


				log.info(LOG_PREFIX+ "bomb::"+ "###-BOMB API-### param.getDecYn(): " + param.getDecYn());


				if("Y".equals(param.getDecYn())) {
					gameUrl2 += "&cc=tbomb2";
				} else {
					gameUrl2 += "&cc=" + venderInfo.get("vendorApiId").toString();
					param.setDecYn("N");
				}


				HashMap decimalParam = new HashMap();
				decimalParam.put("bombAccId", member.getBombAccId());
				decimalParam.put("decYn",param.getDecYn());
				siteService.updateDecimal(decimalParam);


				gameUrl2 += "&nation=" + language;
				gameUrl2 += "&loginToken=" + member.getBombAccId();


				log.info(LOG_PREFIX+ "bomb::"+ "###-BOMB API-### gameUrl2 : " + gameUrl2);
				lauchURL = gameUrl + AesUtil.getAES128_Encode(gameUrl2);
				log.info(LOG_PREFIX+ "bomb::"+ "###-BOMB API-### lauchURL : " + lauchURL);
				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");

			} else if(venderInfo.get("vendorCode").toString().contains("pbg_ball")) {
				// PowerBall API 게임실행 Proc
				venderInfo.put("userId", site.getSiteIdxHex()+member.getMemberId());
				lauchURL = powerBallService.getGameUrl(venderInfo, LOG_PREFIX);
				if(lauchURL != null && !lauchURL.equals("")) {
					apiResponse.put("gameurl", lauchURL);
	            	apiResponse.put("callbackType", "S");
					callbackType = "S";
				} else {
					log.error(LOG_PREFIX+ "PowerBall::getLaunchUrl() FAIL");
					throw new ApiException("PowerBall_99", "Game Start Error");
				}
			} else if(venderInfo.get("vendorCode").toString().contains("nexus_")) {
                String vendorGameCategory = venderInfo.get("vendorGameCategory").toString();
                String vendorKey = venderInfo.get("vendorKey").toString();
                String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
                String apiAgentId = venderInfo.get("vendorApiId").toString();
                String apiAgentSecretKey = venderInfo.get("vendorApiKey").toString();
                String apiVendorKey = venderInfo.get("vendorGameCode").toString();
                String apiGameKey = "";
                if("SLOT".equals(vendorGameCategory)) {
                    apiGameKey = param.getGameKey();
                } else {
                    // Casino
                    // apiGameKey = "A";	// 1,000 ~ 1,000,000
                    // apiGameKey = venderInfo.get("nexusSkinType").toString();
                    if(param.getSkinType() != null && !param.getSkinType().equals("")) {
                        apiGameKey = param.getSkinType();
                    } else {
                        apiGameKey = venderInfo.get("nexusSkinType").toString();
                    }
                    log.info(LOG_PREFIX+ "NEXUS::apiSkin::" + apiGameKey);
                }

                String apiUserIp = param.getUserIp();
                String apiLanguage = "ko";
                String apiPlatform = "desktop";

                if(param.getIsMobile().equals("Y")) {
                    apiPlatform = "mobile";
                }

                NexusApiInfo nexusApiInfo = NexusApiInfo.builder()
                        .siteIdx(member.getSiteIdx())
                        .memberIdx(member.getMemberIdx())
                        .apiBaseUrl(apiBaseUrl)
                        .agentId(apiAgentId)
                        .agentSecretKey(apiAgentSecretKey)
                        .vendorKey(apiVendorKey)
                        .gameKey(apiGameKey)
                        .username(apiUsername)
                        .nickname(apiNickname)
                        .siteUsername(apiUsername)
                        .ip(apiUserIp)
                        .language(apiLanguage)
                        .platform(apiPlatform)
                        .build();
                log.info(LOG_PREFIX+ "NEXUS::NexusApiInfo::" + nexusApiInfo);

                // Create User
                JSONObject userObj = nexusService.createUser(LOG_PREFIX, nexusApiInfo);
                if(!(userObj.getInt("code")==0 || userObj.getInt("code")==-500)) {
                    log.error(LOG_PREFIX+ "NEXUS::createUser::error::code::" + userObj.getInt("code"));
                    log.error(LOG_PREFIX+ "NEXUS::createUser::error::message::" + userObj.getString("msg"));
                    throw new ApiException("NX"+userObj.getInt("code"), userObj.getString("msg"));
                }

                // Game url
                lauchURL = nexusService.gameStart(LOG_PREFIX, nexusApiInfo);
                if(lauchURL != null && !lauchURL.equals("")) {
                    apiResponse.put("gameurl", lauchURL);
                    apiResponse.put("callbackType", "S");
                    callbackType = "S";
                } else {
                    log.error(LOG_PREFIX+ "NEXUS::gameStart() FAIL");
                    throw new ApiException("NEXUS_99", "Game Start Error");
                }

            } else if(venderInfo.get("vendorCode").toString().contains("ace2_")) {
				String vendorGameCategory = venderInfo.get("vendorGameCategory").toString();
				String vendorKey = venderInfo.get("vendorKey").toString();
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAgentId = venderInfo.get("vendorApiId").toString();
				String apiAgentSecretKey = venderInfo.get("vendorApiKey").toString();
				String apiVendorKey = venderInfo.get("vendorGameCode").toString();
				String apiGameKey = "";
				if("SLOT".equals(vendorGameCategory)) {
					apiGameKey = param.getGameKey();
				} else {
					// Casino
					// apiGameKey = "A";	// 1,000 ~ 1,000,000
					// apiGameKey = venderInfo.get("nexusSkinType").toString();
					if(param.getSkinType() != null && !param.getSkinType().equals("")) {
						apiGameKey = param.getSkinType();
					} else {
						apiGameKey = venderInfo.get("nexusSkinType").toString();
					}
					log.info(LOG_PREFIX+ "ACE::apiSkin::" + apiGameKey);
				}

				String apiUserIp = param.getUserIp();
				String apiLanguage = "ko";
				String apiPlatform = "desktop";

				if(param.getIsMobile().equals("Y")) {
					apiPlatform = "mobile";
				}

				NexusApiInfo nexusApiInfo = NexusApiInfo.builder()
						.siteIdx(member.getSiteIdx())
						.memberIdx(member.getMemberIdx())
						.apiBaseUrl(apiBaseUrl)
						.agentId(apiAgentId)
						.agentSecretKey(apiAgentSecretKey)
						.vendorKey(apiVendorKey)
						.gameKey(apiGameKey)
						.username(apiUsername)
						.nickname(apiNickname)
						.siteUsername(apiUsername)
						.ip(apiUserIp)
						.language(apiLanguage)
						.platform(apiPlatform)
						.build();
				log.info(LOG_PREFIX+ "ACE::NexusApiInfo::" + nexusApiInfo);

				// Create User
				JSONObject userObj = nexusService.createUser(LOG_PREFIX, nexusApiInfo);
				if(!(userObj.getInt("code")==0 || userObj.getInt("code")==-500)) {
					log.error(LOG_PREFIX+ "ACE::createUser::error::code::" + userObj.getInt("code"));
					log.error(LOG_PREFIX+ "ACE::createUser::error::message::" + userObj.getString("msg"));
					throw new ApiException("AC"+userObj.getInt("code"), userObj.getString("msg"));
				}

				// Game url
				lauchURL = nexusService.gameStart(LOG_PREFIX, nexusApiInfo);
				if(lauchURL != null && !lauchURL.equals("")) {
					apiResponse.put("gameurl", lauchURL);
					apiResponse.put("callbackType", "S");
					callbackType = "S";
				} else {
					log.error(LOG_PREFIX+ "ACE::gameStart() FAIL");
					throw new ApiException("ACE_99", "Game Start Error");
				}

			} else if(venderInfo.get("vendorCode").toString().contains("tplus_")) {
				// 게임실행 API Proc
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
				long reqAtL = System.currentTimeMillis();
				String reqAt = sdf.format(reqAtL);

				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAuthKey = venderInfo.get("vendorApiKey").toString();
				String thirdparty = venderInfo.get("vendorGameCode").toString();
				String uuid = param.getGameKey();
				TplusApiInfo apiInfo = TplusApiInfo.builder()
										.siteIdx(member.getSiteIdx())
										.memberIdx(member.getMemberIdx())
										.apiBaseUrl(apiBaseUrl)
										.clientId(venderInfo.get("vendorApiId").toString())
										.apiAuthKey(apiAuthKey)
										.thirdparty(thirdparty)
										.uuid(uuid)
										.apiUsername(apiUsername)
										.reqAt(reqAt).build();

				log.info(LOG_PREFIX+ "tplus::apiInfo: " + apiInfo);


				/*
				Long tplusBalance = tplusService.getBalanceUser(LOG_PREFIX, apiInfo);
				if(tplusBalance == null) {
					log.error(LOG_PREFIX+ "tplus::Get Balance Error");
					throw new ApiException("H999", "BALANCE_ERROR");
				}
				*/

				lauchURL = tplusService.getGameLink(LOG_PREFIX, apiInfo);
				if(lauchURL == null || lauchURL.equals("")) {
					log.error(LOG_PREFIX+"tplus::tplusService.getGameLink() Error");
					throw new ApiException("99", "GET_GAMELINK_ERROR");
				}
				log.info(LOG_PREFIX+ "tplus::lauchURL: " + lauchURL);
				apiResponse.put("gameurl", lauchURL);
				apiResponse.put("callbackType", "S");
				callbackType = "S";

			} else if(venderInfo.get("vendorCode").toString().contains("prime_")
					|| venderInfo.get("vendorCode").toString().contains("dpcore_")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiHybrdUrl = venderInfo.get("vendorAuthApiUrl").toString();
				String apiKey = "Bearer "+venderInfo.get("vendorApiKey").toString();
				String apiVendor = venderInfo.get("vendorGameCode").toString();	// Evolution
				String apiGameId = venderInfo.get("nexusSkinType").toString();	// Evolution Top Games Lobby
				if(param.getSkinType() != null && !param.getSkinType().equals("")) {
					apiGameId = param.getSkinType();
				}

				HttpHeaders headers = new HttpHeaders();
				headers.setContentType(MediaType.APPLICATION_JSON);
				headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
				headers.set("Authorization", apiKey);
				String gameUrl = "";
				JSONObject playParams = new JSONObject();
				if(venderInfo.get("vendorCode").toString().contains("prime_")) {
					gameUrl = apiBaseUrl + "/aapi/play";
					apiGameId = "evolution_top_games";
					apiVendor = "1";
				} else {
					// dpcore의 경우

					gameUrl = apiBaseUrl + "/dapi/play";

					apiGameId = "evolution_top_games";
				}
				playParams.put("username", apiUsername);
				playParams.put("game_id", apiGameId);
				playParams.put("vendor", apiVendor);
				String apiSkin = "";
				if(param.getSkinType() != null && !param.getSkinType().equals("")) {
					apiSkin = param.getSkinType();
				} else {
					apiSkin = venderInfo.get("nexusSkinType").toString();
				}
				playParams.put("skin", apiSkin);
				playParams.put("domain", apiHybrdUrl);

				if(isParseStartUser) {
					playParams.put("genuine", false);
				} else {
					playParams.put("genuine", true);
				}

				log.info(LOG_PREFIX+ "PRIME::Play Url: " + gameUrl);
				log.info(LOG_PREFIX+ "PRIME::Play Headers: " + headers.toString());
				log.info(LOG_PREFIX+ "PRIME::Play Params: " + playParams.toString());

				ResponseEntity<String> playRes = null;
				JSONObject playJson = null;
				try {
					playRes = webClient.post()
							.uri(gameUrl)
							.headers(h -> h.addAll(headers))
							.bodyValue(playParams.toString())
							.retrieve()
							.toEntity(String.class)
							.block();
				} catch(WebClientResponseException e) {
					log.error(LOG_PREFIX+ "PRIME::Play HttpClientErrorException : " + e.getMessage());
					log.error(LOG_PREFIX+ "PRIME::Play status code : " + e.getStatusCode().value());
					log.error(LOG_PREFIX+ "PRIME::Play response body : " + e.getResponseBodyAsString());
					playJson = new JSONObject(e.getResponseBodyAsString());
					log.error(LOG_PREFIX+ "PRIME::Play error : " + playJson.getString("message"));
					throw new ApiException("P109", playJson.getString("message"));
				} catch (WebClientRequestException rae) {
		            if(rae.getCause() instanceof io.netty.channel.ConnectTimeoutException) {
		            	log.error(LOG_PREFIX+ "PRIME::Play ConnectTimeoutException::"+rae.getMessage());
		            }
		            if(rae.getCause() instanceof io.netty.handler.timeout.ReadTimeoutException) {
		            	log.error(LOG_PREFIX+ "PRIME::Play SocketTimeoutException::"+rae.getMessage());
		            }
		            if(rae.getCause() instanceof InterruptedException) {
		            	log.error(LOG_PREFIX+ "PRIME::Play InterruptedException::"+rae.getMessage());
		            }
		            throw new ApiException("P110", rae.getMessage());
				} catch (Exception e) {
					log.error(LOG_PREFIX+ "PRIME::Play Exception : " + e.getMessage());
					throw new ApiException("P111", e.getMessage());
				}

				if(playRes != null && playRes.getStatusCode() == HttpStatus.OK) {
					log.info(LOG_PREFIX+ "PRIME::Play response : " + playRes.getBody());
					playJson = new JSONObject(playRes.getBody());

					if(playJson.has("entry") && !apiHybrdUrl.equals("")) {
						log.info(LOG_PREFIX+ "수정도메인 실행");
						lauchURL = "https://"+apiHybrdUrl+playJson.getString("entry");
					} else {
						log.info(LOG_PREFIX+ "기본도메인 실행");
						lauchURL = playJson.getString("url");
					}
					log.info(LOG_PREFIX+ "PRIME::Play lauchURL : " + lauchURL);

					apiResponse.put("gameurl", lauchURL);
					apiResponse.put("callbackType", "S");
					callbackType = "S";
				}

			} else if(venderInfo.get("vendorCode").toString().contains("onix_")) {
				// Onix API 게임실행 Proc
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiId = venderInfo.get("vendorApiId").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String apiGameCode = venderInfo.get("vendorGameCode").toString();
				String apiGameLobby = "evolution";  // Evolution 로비 (항상 evolution 고정)

				// if(param.getSkinType() != null && !param.getSkinType().equals("")) {
				// 	apiGameLobby = param.getSkinType();
				// }

				log.info(LOG_PREFIX+ "[ONIX] API Base URL: " + apiBaseUrl);
				log.info(LOG_PREFIX+ "[ONIX] API ID: " + apiId);
				log.info(LOG_PREFIX+ "[ONIX] Game Code: " + apiGameCode);

				// Step 1: 사용자 생성
				HttpHeaders createHeaders = new HttpHeaders();
				createHeaders.setContentType(MediaType.APPLICATION_JSON);
				createHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
				createHeaders.set("Authorization", "Bearer " + apiKey);

				String createUrl = apiBaseUrl + "/csapi/create";
				JSONObject createBody = new JSONObject();
				createBody.put("userid", apiUsername);

				log.info(LOG_PREFIX+ "ONIX::Create Url: " + createUrl);
				log.info(LOG_PREFIX+ "ONIX::Create Body: " + createBody.toString());

				ResponseEntity<String> createRes = null;
				try {
					createRes = webClient.post()
							.uri(createUrl)
							.headers(h -> h.addAll(createHeaders))
							.bodyValue(createBody.toString())
							.retrieve()
							.toEntity(String.class)
							.block();
					if(createRes != null && createRes.getStatusCode() == HttpStatus.OK) {
						JSONObject createJson = new JSONObject(createRes.getBody());
						log.info(LOG_PREFIX+ "ONIX::Create Response: " + createJson.toString());
						int createResult = createJson.getInt("result");
						if(createResult != 1) {
							log.error(LOG_PREFIX+ "ONIX::User Create Failed: " + createJson.getString("msg"));
							throw new ApiException("ONX01", "User Create Error");
						}
					} else {
						throw new ApiException("ONX01", "User Create Response Error");
					}
				} catch(ApiException e) {
					throw e;
				} catch(WebClientResponseException e) {
					log.error(LOG_PREFIX+ "ONIX::Create HttpClientErrorException : " + e.getMessage());
					log.error(LOG_PREFIX+ "ONIX::Create status code : " + e.getStatusCode().value());
					log.error(LOG_PREFIX+ "ONIX::Create response body : " + e.getResponseBodyAsString());
					JSONObject errorJson = new JSONObject(e.getResponseBodyAsString());
					String errorMsg = errorJson.has("msg") ? errorJson.getString("msg") : "User Create Error";
					log.error(LOG_PREFIX+ "ONIX::Create error : " + errorMsg);
					throw new ApiException("ONX01", errorMsg);
				} catch (WebClientRequestException rae) {
					if(rae.getCause() instanceof io.netty.channel.ConnectTimeoutException) {
						log.error(LOG_PREFIX+ "ONIX::Create ConnectTimeoutException::" + rae.getMessage());
					}
					if(rae.getCause() instanceof io.netty.handler.timeout.ReadTimeoutException) {
						log.error(LOG_PREFIX+ "ONIX::Create ReadTimeoutException::" + rae.getMessage());
					}
					if(rae.getCause() instanceof InterruptedException) {
						log.error(LOG_PREFIX+ "ONIX::Create InterruptedException::" + rae.getMessage());
					}
					throw new ApiException("ONX01", rae.getMessage());
				} catch(Exception e) {
					log.error(LOG_PREFIX+ "ONIX::Create Exception : " + e.getMessage());
					throw new ApiException("ONX01", e.getMessage());
				}

				// Step 2: 게임 URL 생성
				String gameUrl = apiBaseUrl + "/csapi/getGameUrl";
				JSONObject gameUrlBody = new JSONObject();
				gameUrlBody.put("code", apiGameCode);
				gameUrlBody.put("subcode", apiGameLobby);
				gameUrlBody.put("userid", apiUsername);
				gameUrlBody.put("platform", param.getIsMobile().equals("Y") ? "mobile" : "pc");
				gameUrlBody.put("lobby", "0");

				log.info(LOG_PREFIX+ "ONIX::GameUrl Url: " + gameUrl);
				log.info(LOG_PREFIX+ "ONIX::GameUrl Body: " + gameUrlBody.toString());

				ResponseEntity<String> gameUrlRes = null;
				try {
					gameUrlRes = webClient.post()
							.uri(gameUrl)
							.headers(h -> h.addAll(createHeaders))
							.bodyValue(gameUrlBody.toString())
							.retrieve()
							.toEntity(String.class)
							.block();
					if(gameUrlRes != null && gameUrlRes.getStatusCode() == HttpStatus.OK) {
						JSONObject gameUrlJson = new JSONObject(gameUrlRes.getBody());
						log.info(LOG_PREFIX+ "ONIX::GameUrl Response: " + gameUrlJson.toString());
						int gameUrlResult = gameUrlJson.getInt("result");
						if(gameUrlResult == 1) {
							lauchURL = gameUrlJson.getString("link");
							log.info(LOG_PREFIX+ "ONIX::Game Launch URL: " + lauchURL);
							apiResponse.put("gameurl", lauchURL);
							apiResponse.put("callbackType", "S");
							callbackType = "S";
						} else {
							log.error(LOG_PREFIX+ "ONIX::Game URL Generation Failed: " + gameUrlJson.getString("msg"));
							throw new ApiException("ONX02", gameUrlJson.getString("msg"));
						}
					} else {
						throw new ApiException("ONX02", "Game URL Response Error");
					}
				} catch(ApiException e) {
					throw e;
				} catch(WebClientResponseException e) {
					log.error(LOG_PREFIX+ "ONIX::GameUrl HttpClientErrorException : " + e.getMessage());
					log.error(LOG_PREFIX+ "ONIX::GameUrl status code : " + e.getStatusCode().value());
					log.error(LOG_PREFIX+ "ONIX::GameUrl response body : " + e.getResponseBodyAsString());
					JSONObject errorJson = new JSONObject(e.getResponseBodyAsString());
					String errorMsg = errorJson.has("msg") ? errorJson.getString("msg") : "Game URL Generation Error";
					log.error(LOG_PREFIX+ "ONIX::GameUrl error : " + errorMsg);
					throw new ApiException("ONX02", errorMsg);
				} catch (WebClientRequestException rae) {
					if(rae.getCause() instanceof io.netty.channel.ConnectTimeoutException) {
						log.error(LOG_PREFIX+ "ONIX::GameUrl ConnectTimeoutException::" + rae.getMessage());
					}
					if(rae.getCause() instanceof io.netty.handler.timeout.ReadTimeoutException) {
						log.error(LOG_PREFIX+ "ONIX::GameUrl ReadTimeoutException::" + rae.getMessage());
					}
					if(rae.getCause() instanceof InterruptedException) {
						log.error(LOG_PREFIX+ "ONIX::GameUrl InterruptedException::" + rae.getMessage());
					}
					throw new ApiException("ONX02", rae.getMessage());
				} catch(Exception e) {
					log.error(LOG_PREFIX+ "ONIX::GameUrl Exception : " + e.getMessage());
					throw new ApiException("ONX02", e.getMessage());
				}
			} else if(venderInfo.get("vendorCode").toString().contains("thenut_")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String vendorGameCategory = venderInfo.get("vendorGameCategory").toString();
				String vendorKey = venderInfo.get("vendorKey").toString();
				String apiVendor = venderInfo.get("vendorGameCode").toString();
				String apiGameCode = "";
				if("SLOT".equals(vendorGameCategory)) {
					apiGameCode = param.getGameKey();
				} else {
					if(vendorKey.equals("C01")) {
						apiGameCode = "evolution_top_games";
					}
				}

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiKey", apiKey);
				apiInfo.put("apiVendor", apiVendor);
				apiInfo.put("apiGameCode", apiGameCode);
				apiInfo.put("apiUsername", apiUsername);
				apiInfo.put("apiNickname", apiNickname);
				log.info(LOG_PREFIX+ "THENUT::API Info::" + apiInfo);

				// Get User Info
				JSONObject userObj = thenutService.getUser(LOG_PREFIX, apiInfo);
				if(userObj == null) {
					// Create User
					userObj = thenutService.createUser(LOG_PREFIX, apiInfo);
					if(userObj == null) {
						log.error(LOG_PREFIX+ "THENUT_01::Create User Error");
						throw new ApiException("THENUT_01", "Create User Error");
					}
				}

				// Get User Token
				String userToken = thenutService.createUserToken(LOG_PREFIX, apiInfo);
				if(userToken == null || userToken.equals("")) {
					log.error(LOG_PREFIX+ "THENUT_02::Get User Token Error");
					throw new ApiException("THENUT_02", "Get User Token Error");
				} else {
					apiInfo.put("apiUserToken", userToken);
				}

				// Get Game Url
				lauchURL = thenutService.getGameUrl(LOG_PREFIX, apiInfo);
				if(lauchURL != null && !lauchURL.equals("")) {
					apiResponse.put("gameurl", lauchURL);
	            	apiResponse.put("callbackType", "S");
					callbackType = "S";
				} else {
					log.error(LOG_PREFIX+ "getGameUrl() FAIL");
					throw new ApiException("THENUT_99", "Game Start Error");
				}
			}

			HashMap memParam = new HashMap<>();
			memParam.put("siteIdx", member.getSiteIdx());
			memParam.put("memberIdx", member.getMemberIdx());
			memParam.put("vendorPrefix", null);

			long lastInVendorIdx = START_VENDOR_IDX;
			memParam.put("lastInVendorIdx", lastInVendorIdx);
			int updResult = siteService.updMemLastVendorIdx(memParam);
			log.info(LOG_PREFIX+ "updMemLastVendorIdx Result : " + updResult);
			if(updResult >= 0) {
				apiResponse.success();
				log.info(LOG_PREFIX+ "apiResponse : " + apiResponse.toString());
				log.info("#START_PLAY::"+venderInfo.get("vendorCode").toString()+"::"+param.getUserId());
			} else {
				throw new ApiException("2000", "not setting");
			}

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

            if(rae.getCause() instanceof SocketTimeoutException) {
            	log.error(LOG_PREFIX+ "[SocketTimeoutException]"+rae.getMessage());
    			apiResponse.setResultCode("TE99");
    			apiResponse.setResultMessage("SocketTimeoutException");
    			apiResponse.fail();
            }

            if(rae.getCause() instanceof InterruptedException) {
            	log.error(LOG_PREFIX+ "[InterruptedException]"+rae.getMessage());
    			apiResponse.setResultCode("TE99");
    			apiResponse.setResultMessage("InterruptedException");
    			apiResponse.fail();
            }
		} catch(ApiException e) {
			log.error(LOG_PREFIX+ "[ApiException]"+e.getApiResponse());
			apiResponse = e.getApiResponse();
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			log.error(LOG_PREFIX+ "[Exception]"+e.getLocalizedMessage());
			log.error(LOG_PREFIX+ "[Exception]"+e.getMessage());
			log.error(LOG_PREFIX+ "[Exception]"+e.getStackTrace().toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}

	@ResponseBody
	@PostMapping(value="/games")
	public ApiResponse games(HttpServletRequest request, @RequestBody PlayParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {
			final String LOG_PREFIX = "#-games::"+param.getUserId()+"::::";

			//apiKey 체크
			Site site  = siteService.getSiteInfo(request);
			if(site==null) {
				log.error(LOG_PREFIX+ "G1001:Access denied Key");
				throw new ApiException("G1001", "Access denied Key");
			}

			ObjectMapper objectMapper = new ObjectMapper();
			String reqJsonStr = objectMapper.writeValueAsString(param);
			JSONObject reqObj = new JSONObject(reqJsonStr);
			log.info(LOG_PREFIX+ "Request {}", reqObj);

			CommonParam commonParam = new CommonParam();
			commonParam.setSiteIdx(site.getSiteIdx());
			commonParam.setPlayParam(param);
			log.info("#-games::"+param.getUserId()+"::::"+ commonParam.toString());
			HashMap venderInfo = siteService.getOnlyVenderInfo(commonParam);
			if(venderInfo ==null) {
				log.error(LOG_PREFIX+ "G1002:Vendor not setting");
				throw new ApiException("G1002", "Vendor not setting");
			}

			List<Game> gameList = new ArrayList<>();

			if(venderInfo.get("vendorCode").toString().contains("org_pragmatic")) {

					gameList = pragmaticService.getGameList(LOG_PREFIX, venderInfo);

			} else if(venderInfo.get("vendorCode").toString().contains("panda_pragmatic_slot")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAgentId = venderInfo.get("vendorApiId").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String apiVendor = venderInfo.get("vendorGameCode").toString();

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiAgentId", apiAgentId);
				apiInfo.put("apiKey", apiKey);
				apiInfo.put("apiVendor", apiVendor);
				log.info(LOG_PREFIX+ "PANDA::API Info::" + apiInfo);

				gameList = pandaService.getGameList(LOG_PREFIX, apiInfo);

			} else if(venderInfo.get("vendorCode").toString().contains("pracp_pragmatic_slot")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAgentCode = venderInfo.get("vendorApiId").toString();
				String apiToken = venderInfo.get("vendorApiKey").toString();
				String apiProviderCode = venderInfo.get("vendorGameCode").toString();

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiAgentCode", apiAgentCode);
				apiInfo.put("apiToken", apiToken);
				apiInfo.put("apiProviderCode", apiProviderCode);
				log.info(LOG_PREFIX+ "PRACP::API Info::" + apiInfo);

				gameList = pracpService.getGameList(LOG_PREFIX, apiInfo);

			} else if(venderInfo.get("vendorCode").toString().contains("grand_pragmatic_slot")) {
				venderInfo.put("vendor_key", "pragmatic_slot");
				gameList = grandService.getGameList(venderInfo);

			} else if((venderInfo.get("vendorCode").toString().startsWith("tower_pragmatic_slot"))) {
				gameList = towerService.getGameList(venderInfo);

			} else if(venderInfo.get("vendorCode").toString().contains("nexus_")
                    || venderInfo.get("vendorCode").toString().contains("ace2_")) {
                String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
                String apiAgentId = venderInfo.get("vendorApiId").toString();
                String apiAgentSecretKey = venderInfo.get("vendorApiKey").toString();
                String apiVendorKey = venderInfo.get("vendorGameCode").toString();

                NexusApiInfo apiInfo = NexusApiInfo.builder()
                                            .apiBaseUrl(apiBaseUrl)
                                            .agentId(apiAgentId)
                                            .agentSecretKey(apiAgentSecretKey)
                                            .vendorKey(apiVendorKey)
                                            .skin("SLOT")
                                            .type("Slot")
                                            .build();

                gameList = nexusService.gameList(LOG_PREFIX, apiInfo);

            } else if(venderInfo.get("vendorCode").toString().contains("tplus_")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAuthKey = venderInfo.get("vendorApiKey").toString();
				String thirdparty = venderInfo.get("vendorGameCode").toString();

				TplusApiInfo apiInfo = TplusApiInfo.builder()
										.apiBaseUrl(apiBaseUrl)
										.apiAuthKey(apiAuthKey)
										.thirdparty(thirdparty)
										.build();

				log.info(LOG_PREFIX+ "TplusApiInfo : " + apiInfo);

				gameList = tplusService.gameList(LOG_PREFIX, apiInfo);

			} else if(venderInfo.get("vendorCode").toString().contains("svendor_")
                    || venderInfo.get("vendorCode").toString().contains("pink_")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiAgent = venderInfo.get("vendorApiId").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String apiVendor = venderInfo.get("vendorGameCode").toString();

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiAgent", apiAgent);
				apiInfo.put("apiKey", apiKey);
				apiInfo.put("vendor", apiVendor);

				gameList = svendorService.gameList(LOG_PREFIX, apiInfo);

			} else if(venderInfo.get("vendorCode").toString().contains("onix_")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiId = venderInfo.get("vendorApiId").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String apiGameCode = venderInfo.get("vendorGameCode").toString();

				HttpHeaders headers = new HttpHeaders();
				headers.setContentType(MediaType.APPLICATION_JSON);
				headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));

				String listUrl = apiBaseUrl + "/csapi/Provider";
				JSONObject listBody = new JSONObject();
				listBody.put("type", "2");
				listBody.put("gameid", apiGameCode);
				listBody.put("code", apiGameCode);
				listBody.put("gametype", "live");

				log.info(LOG_PREFIX+ "ONIX::Game List Url: " + listUrl);
				log.info(LOG_PREFIX+ "ONIX::Game List Body: " + listBody.toString());

				try {
					ResponseEntity<String> res = webClient.post()
							.uri(listUrl)
							.headers(h -> h.addAll(headers))
							.bodyValue(listBody.toString())
							.retrieve()
							.toEntity(String.class)
							.block();
					if(res != null && res.getBody() != null) {
						JSONObject resObj = new JSONObject(res.getBody());
						int result = resObj.getInt("result");
						if(result == 1) {
							JSONArray games = resObj.getJSONArray("data");
							int length = games.length();
							for(int i=0; i<length; i++) {
								JSONObject objGame = games.getJSONObject(i);
								String gameId = objGame.getString("code");
								String gameName = objGame.getString("name");
								String gameType = objGame.getString("type");

								Game game = new Game();
								game.setGameIdx(i);
								game.setGameKey(gameId);
								game.setGameName(gameName);
								game.setGameNameEn(gameName);
								game.setGameType(gameType);
								gameList.add(game);
							}
							log.info(LOG_PREFIX+ "ONIX::Game List loaded: " + gameList.size());
						} else {
							String errorMsg = resObj.has("msg") ? resObj.getString("msg") : "Unknown error";
							log.error(LOG_PREFIX+ "ONIX::Game List Error: " + errorMsg);
						}
					} else {
						log.error(LOG_PREFIX+ "ONIX::Game List Response is null");
					}
				} catch(Exception e) {
					log.error(LOG_PREFIX+ "ONIX::Game List Exception : " + e.getMessage());
				}

			} else if(venderInfo.get("vendorCode").toString().contains("thenut")) {
				String apiBaseUrl = venderInfo.get("vendorApiUrl").toString();
				String apiKey = venderInfo.get("vendorApiKey").toString();
				String vendorCode = venderInfo.get("vendorGameCode").toString();

				HashMap<String, String> apiInfo = new HashMap<>();
				apiInfo.put("apiBaseUrl", apiBaseUrl);
				apiInfo.put("apiKey", apiKey);
				apiInfo.put("apiVendor", vendorCode);
				log.info(LOG_PREFIX+ "THENUT::API Info::" + apiInfo);

				gameList = thenutService.getGameList(LOG_PREFIX, apiInfo);
				apiResponse.put("list", gameList);

			} else {
				gameList = siteService.getGameList(venderInfo);
			}

			String banGameId = venderInfo.get("banGameId").toString().replaceAll(" ", "").toLowerCase();
			log.info(LOG_PREFIX+ "banGameIds : " + banGameId);
			List<Game> list = new ArrayList<>();
			for(Game item : gameList) {
				String gameId = item.getGameKey().toLowerCase();
				if(!"".equals(gameId) && !"".equals(banGameId) && banGameId.contains(gameId+"|")) {
					log.info(LOG_PREFIX+ "banGameId: " + gameId + ", banGame: " + item.getGameName());
				} else {
					list.add(item);
				}
			}

			apiResponse.put("list", list);

			apiResponse.success();
		} catch (Exception e) {
			log.info(e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}


	@ResponseBody
	@PostMapping(value="/members")
	public ApiResponse members(@RequestHeader String token, HttpServletRequest request, @RequestBody MemberParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {

			apiResponse.success();

		} catch (Exception e) {
			log.info(e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}
		return apiResponse;
	}


	@ResponseBody
	@PostMapping(value="/creditInfo")
	public ApiResponse creditInfo(HttpServletRequest request) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site ==null) {
				throw new ApiException("1000", "accessDinied");
			}

			long targetSiteCredit = siteService.getSiteCredit(site.getSiteId());
			apiResponse.put("credit", targetSiteCredit);

			long targetSitePoint = siteService.getSitePoint(site.getSiteId());
			apiResponse.put("point", targetSitePoint);

			apiResponse.success();

		} catch (Exception e) {
			log.info(e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}

	@ResponseBody
	@PostMapping(value="/betInfo")
	public ApiResponse betinfo(HttpServletRequest request, @RequestBody BetParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		final String LOG_PREFIX = "#-BET-DETAIL::"+param.getBetId()+":::";
		JSONObject saveObj = null;
		JSONArray saveObjs = null;
		boolean isSaveEvoDetail = false;

		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site ==null) {
				throw new ApiException("1000", "accessDinied");
			}

			// 결과보내기
			apiResponse.put("betId", param.getBetId());
			String dType = "detail_0";

			HashMap betInfo = transService.getBetInfoByBetId(param);
			log.info(LOG_PREFIX+ "betInfo {}", betInfo);

			if(betInfo == null) betInfo = transService.getSiteBetByRefIdFromOld(param);
			log.info(LOG_PREFIX+ "betInfo_OLD {}", betInfo);

			if(betInfo != null) {
				param.setBetId(betInfo.get("refId").toString());

				String category = betInfo.get("vendorCetegory").toString();
				log.info(LOG_PREFIX+ "Game category : " + category);

				if("pragmatic".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);

					String detailURL = pragmaticService.getDetails(LOG_PREFIX, betInfo);
					if(detailURL != null && !detailURL.equals("")) {
						HashMap url = new HashMap();
						url.put("url", detailURL);
						apiResponse.put("detail", url);
					} else {
						apiResponse.put("detail", "betId is not exist");
					}

				} else if("svendor".equals(betInfo.get("vendorCode").toString())
                        || "pink".equals(betInfo.get("vendorCode").toString())) {
					String jsonStr = siteService.getOrgDetailData(param);
					log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
						dType = "detail_6";
						apiResponse.put("dType", dType);
						HashMap<String, Object> detailMap = new Gson().fromJson(jsonStr, HashMap.class);
						apiResponse.put("detail", detailMap);
					} else {
						isSaveEvoDetail = true;
						JSONObject dataObj = svendorService.getDetail(LOG_PREFIX, betInfo);
						if(dataObj == null) {
							apiResponse.put("detail", "betId is not exist");
						} else {
							if(dataObj.has("gameDetail") && !dataObj.isNull("gameDetail")) {
								String detailType = StringUtils.getType(dataObj.getString("gameDetail"));
								if(detailType.equals("URL")) {
									dType = "detail_2";
									apiResponse.put("dType", dType);
									String detailUrl = dataObj.getString("gameDetail");
									HashMap url = new HashMap();
									url.put("url", detailUrl);
									apiResponse.put("detail", url);
								} else if(detailType.equals("JSON")) {
									dType = "detail_6";
									apiResponse.put("dType", dType);
									JSONObject detailObj = dataObj.getJSONObject("gameDetail");
									saveObj = detailObj;
									HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
									apiResponse.put("detail", detailMap);
								} else {
									apiResponse.put("detail", "betId is not exist");
								}
							} else {
								apiResponse.put("detail", "betId is not exist");
							}
						}
					}

				} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==17 && "tower".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);
					String detail_url =
							betInfo.get("vendorApiUrl").toString()
							+"/v2/detail_url"
							+ "?site_code="+betInfo.get("vendorApiId").toString()
							+ "&user_id="+betInfo.get("siteHex").toString()+betInfo.get("memberId").toString()
							+ "&transaction_id="+betInfo.get("transactionId").toString()
							+ "&vendorCode=pp"
							+ "&gameCode="+betInfo.get("gameIdx").toString();
					log.info(LOG_PREFIX+"  {}" ,detail_url);
					OkHttpClient client = new OkHttpClient().newBuilder().build();
					okhttp3.Request requestok2 = new okhttp3.Request.Builder()
													.url(detail_url)
													.method("GET", null)//
													.addHeader("User-agent", "Mozilla")
													.addHeader("Authorization", betInfo.get("vendorApiKey").toString())
													.build();
					okhttp3.Response response = client.newCall(requestok2).execute();

					if(response.isSuccessful()){
						okhttp3.ResponseBody body =  response.body();
						String responseString = body.string();
						log.info(LOG_PREFIX+ "responseString::"+responseString);
						log.info(LOG_PREFIX+ "url::"+StringUtils.extractLink(responseString));
						String detailUrl = StringUtils.extractLink(responseString);
						String detailType = StringUtils.getType(detailUrl);
						if(detailType.equals("URL")) {
							dType = "detail_2";
							apiResponse.put("dType", dType);
							HashMap url = new HashMap();
							url.put("url", detailUrl);
							apiResponse.put("detail", url);
						} else {
							apiResponse.put("detail", "betId is not exist");
						}
						/*
						HashMap url = new HashMap();
						url.put("url", StringUtils.extractLink(responseString));
						apiResponse.put("detail", url);
						*/
					} else {
						apiResponse.put("detail", "betId is not exist");
					}

				} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==83 && "splus".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_1";
					apiResponse.put("dType", dType);
					HashMap d = new HashMap();
					List<HashMap> splusDetail = siteService.getSplusDetailData(param);
					for(HashMap ttype : splusDetail) {
						log.info(LOG_PREFIX+ "{}, {}", ttype.get("tranType").toString(), ttype.get("data").toString());
						HashMap<String,Object> detailMap = new Gson().fromJson(ttype.get("data").toString(), HashMap.class);
						d.put(ttype.get("tranType").toString(), detailMap);
					}

					apiResponse.put("detail", d);

				} else if("nexus".equals(betInfo.get("vendorCode").toString()) || "ace2".equals(betInfo.get("vendorCode").toString())) {
					String jsonStr = siteService.getOrgDetailData(param);
					log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
						dType = "detail_3";
						apiResponse.put("dType", dType);

						JSONObject detailObj = new JSONObject(jsonStr);
						String gameType = detailObj.optString("gameType");
						JSONArray participants = detailObj.optJSONArray("participants");
						if (participants != null) {
						    for (int i = 0; i < participants.length(); i++) {
						    	// result 객체 가져오기 (없으면 생성)
						    	JSONObject participant = participants.optJSONObject(i);
							    JSONObject result = participant.optJSONObject("result");
							    if (result == null) {
							        result = new JSONObject();
							        participant.put("result", result);
							    }

							    // link 값 세팅
							    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
							    String encrypted = AesUtil.encrypt(raw);
							    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
						    }
						}
						HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
						apiResponse.put("detail", detailMap);
					} else {
						isSaveEvoDetail = true;
						long siteIdx = (long) betInfo.get("siteIdx");
						long memberIdx = (long) betInfo.get("memberIdx");
						String trxId = betInfo.get("vendorTranKey").toString();
						String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
						String apiAgentId = betInfo.get("vendorApiId").toString();
						String apiSecretKey = betInfo.get("vendorApiKey").toString();
						NexusApiInfo nexusApiInfo = NexusApiInfo.builder()
														.siteIdx(siteIdx)
														.memberIdx(memberIdx)
														.apiBaseUrl(apiBaseUrl)
														.agentId(apiAgentId)
														.agentSecretKey(apiSecretKey)
														.build();
						if("CASINO".equals(betInfo.get("vendorCetegory").toString())) {
							dType = "detail_3";
							apiResponse.put("dType", dType);
							JSONObject detailObj = nexusService.getDetail(LOG_PREFIX, nexusApiInfo, trxId);
							if(detailObj == null) {
								apiResponse.put("detail", "betId is not exist");
							} else {
								int resultCode = detailObj.getInt("code");
								if(resultCode == 0) {
									JSONArray transactions = detailObj.getJSONArray("transactions");
									if(transactions.length() > 0) {
										List<HashMap<String, Object>> detailMap = new Gson().fromJson(transactions.toString(), List.class);
										saveObjs = transactions;
										apiResponse.put("detail", detailMap);
									} else {
										apiResponse.put("detail", "betId is not exist");
									}
								} else {
									apiResponse.put("detail", "betId is not exist");
								}
							}
						} else {
							dType = "detail_2";
							apiResponse.put("dType", dType);
							JSONObject detailObj = nexusService.getDetailUrl(LOG_PREFIX, nexusApiInfo, trxId);
							if(detailObj == null) {
								apiResponse.put("detail", "betId is not exist");
							} else {
								log.info(LOG_PREFIX+ "detailObj::"+detailObj.toString());
								int resultCode = detailObj.getInt("code");
								if(resultCode == 0) {
									HashMap<String, String> url = new HashMap<>();
									url.put("url", detailObj.getString("url"));
									apiResponse.put("detail", url);
								} else {
									apiResponse.put("detail", "betId is not exist");
								}
							}
						}
					}
				} else if("prime".equals(betInfo.get("vendorCode").toString())
						|| "dpcore".equals(betInfo.get("vendorCode").toString())) {
					log.info(LOG_PREFIX+ "========== BetDetail::PRIME ==========");
					dType = "detail_9";
					apiResponse.put("dType", dType);

					String jsonStr = siteService.getOrgDetailData(param);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {

						JSONObject detailObj = new JSONObject(jsonStr);
						String gameType = detailObj.optString("gameType");
						JSONArray participants = detailObj.optJSONArray("participants");
						if (participants != null) {
						    for (int i = 0; i < participants.length(); i++) {
						    	// result 객체 가져오기 (없으면 생성)
						    	JSONObject participant = participants.optJSONObject(i);
							    JSONObject result = participant.optJSONObject("result");
							    if (result == null) {
							        result = new JSONObject();
							        participant.put("result", result);
							    }

							    // link 값 세팅
							    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
							    String encrypted = AesUtil.encrypt(raw);
							    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
						    }
						}
						HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
						apiResponse.put("detail", detailMap);
					} else {
						HttpHeaders headers = new HttpHeaders();
						headers.setContentType(MediaType.APPLICATION_JSON);
						headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
						String apiKey = "Bearer "+betInfo.get("vendorApiKey").toString();
						headers.set("Authorization", apiKey);
						String detailUrl = betInfo.get("vendorApiUrl").toString() + "/api/getBettingDetails5?";
						String detailParam = "referer="+param.getBetId();
						detailUrl = detailUrl + detailParam;

						log.info(LOG_PREFIX+ "PRIME::Detail Url: " + detailUrl);
						log.info(LOG_PREFIX+ "PRIME::Detail Headers: " + headers.toString());
						ResponseEntity<String> response = null;
						JSONObject resJson = null;
						try {
							response = webClient.get()
									.uri(detailUrl)
									.headers(h -> h.addAll(headers))
									.retrieve()
									.toEntity(String.class)
									.block();
						} catch(WebClientResponseException e) {
							log.error(LOG_PREFIX+ "PRIME::Detail HttpClientErrorException : " + e.getMessage());
							log.error(LOG_PREFIX+ "PRIME::Detail status code : " + e.getStatusCode().value());
							log.error(LOG_PREFIX+ "PRIME::Detail response body : " + e.getResponseBodyAsString());
							resJson = new JSONObject(e.getResponseBodyAsString());
							log.error(LOG_PREFIX+ "PRIME::Detail error : " + resJson.getString("message"));
							throw new ApiException("P209", resJson.getString("message"));
						} catch (WebClientRequestException rae) {
				            if(rae.getCause() instanceof io.netty.channel.ConnectTimeoutException) {
				            	log.error(LOG_PREFIX+ "PRIME::Detail ConnectTimeoutException::"+rae.getMessage());
				            }
				            if(rae.getCause() instanceof io.netty.handler.timeout.ReadTimeoutException) {
				            	log.error(LOG_PREFIX+ "PRIME::Detail SocketTimeoutException::"+rae.getMessage());
				            }
				            if(rae.getCause() instanceof InterruptedException) {
				            	log.error(LOG_PREFIX+ "PRIME::Detail InterruptedException::"+rae.getMessage());
				            }
				            throw new ApiException("P210", rae.getMessage());
						} catch (Exception e) {
							log.error(LOG_PREFIX+ "PRIME::Detail Exception : " + e.getMessage());
							throw new ApiException("P211", e.getMessage());
						}

						if(response != null && response.getStatusCode() == HttpStatus.OK) {
							isSaveEvoDetail = true;
							log.info(LOG_PREFIX+ "PRIME::Detail response : " + response.getBody());
							resJson = new JSONObject(response.getBody());

							JSONObject dataObj = resJson;
							String gameType = dataObj.optString("gameType");
							JSONArray participants = dataObj.optJSONArray("participants");
							if (participants != null) {
							    for (int i = 0; i < participants.length(); i++) {
							    	// result 객체 가져오기 (없으면 생성)
							    	JSONObject participant = participants.optJSONObject(i);
								    JSONObject result = participant.optJSONObject("result");
								    if (result == null) {
								        result = new JSONObject();
								        participant.put("result", result);
								    }

								    // link 값 세팅
								    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
								    String encrypted = AesUtil.encrypt(raw);
								    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
							    }
							}
							saveObj = dataObj;
							HashMap<String, Object> detailMap = new Gson().fromJson(dataObj.toString(), HashMap.class);
							apiResponse.put("detail", detailMap);
						} else {
							apiResponse.put("detail", "betId is not exist");
						}
					}
				} else if("grand".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);
					String detailUrl =
							betInfo.get("vendorApiUrl").toString()
							+"/transactions/detail"
							+ "?round_id="+betInfo.get("vendorTranKey").toString();
					log.info(LOG_PREFIX+"Request Url : {}" ,detailUrl);
					OkHttpClient client = new OkHttpClient().newBuilder().build();
					okhttp3.Request requestok2 = new okhttp3.Request.Builder()
													.url(detailUrl)
													.method("GET", null)//
													.addHeader("User-agent", "Mozilla")
													.addHeader("Authorization", "Bearer "+betInfo.get("vendorApiKey").toString())
													.build();
					okhttp3.Response response = client.newCall(requestok2).execute();

					if(response.isSuccessful()){
						okhttp3.ResponseBody body = response.body();
						String responseString = body.string();
						log.info(LOG_PREFIX+ "responseString : " , responseString);
						JSONObject responseObj = new JSONObject(responseString);
						JSONObject dataObj = responseObj.getJSONObject("data");
						if(dataObj != null) {
							HashMap url = new HashMap();
							url.put("url", dataObj.getString("url"));
							apiResponse.put("detail", url);
						} else {
							apiResponse.put("detail", "betId is not exist");
						}
					} else {
						apiResponse.put("detail", "betId is not exist");
					}
				} else if("pracp".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);

					HashMap<String, String> apiInfo = new HashMap<>();
					apiInfo.put("apiAgentCode", betInfo.get("vendorApiId").toString());
					apiInfo.put("apiToken", betInfo.get("vendorApiKey").toString());
					apiInfo.put("apiBaseUrl", betInfo.get("vendorApiUrl").toString());
					apiInfo.put("uuid", betInfo.get("uuid").toString());
					apiInfo.put("round_id", betInfo.get("roundId").toString());

					JSONObject dataObj = pracpService.getDetail(LOG_PREFIX, apiInfo);
					log.info(LOG_PREFIX+ "dataObj : " + dataObj.toString());
					if(dataObj != null) {
						HashMap url = new HashMap();
						url.put("url", dataObj.getString("url"));
						apiResponse.put("detail", url);
					} else {
						apiResponse.put("detail", "betId is not exist");
					}
				} else if("tplus".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);

					String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
					String apiAuthKey = betInfo.get("vendorApiKey").toString();
					String pairTransactionKey = betInfo.get("refId").toString();
					TplusApiInfo apiInfo = TplusApiInfo.builder()
											.apiBaseUrl(apiBaseUrl)
											.apiAuthKey(apiAuthKey)
											.pairTransactionKey(pairTransactionKey).build();

					log.info(LOG_PREFIX+ "tplus::apiInfo: " + apiInfo);

					String detailURL = tplusService.getDetails(LOG_PREFIX, apiInfo);
					if(detailURL != null && !detailURL.equals("")) {
						HashMap url = new HashMap();
						url.put("url", detailURL);
						apiResponse.put("detail", url);
					} else {
						apiResponse.put("detail", "betId is not exist");
					}
				} else if("onix".equals(betInfo.get("vendorCode").toString())) {
					log.info(LOG_PREFIX+ "========== BetDetail::ONIX ==========");
					dType = "detail_8";
					apiResponse.put("dType", dType);

					String jsonStr = siteService.getOrgDetailData(param);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
						JSONObject detailObj = new JSONObject(jsonStr);
						String gameType = detailObj.optString("gameType");
						JSONArray participants = detailObj.optJSONArray("participants");
						if (participants != null) {
						    for (int i = 0; i < participants.length(); i++) {
						    	// result 객체 가져오기 (없으면 생성)
						    	JSONObject participant = participants.optJSONObject(i);
							    JSONObject result = participant.optJSONObject("result");
							    if (result == null) {
							        result = new JSONObject();
							        participant.put("result", result);
							    }

							    // link 값 세팅
							    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
							    String encrypted = AesUtil.encrypt(raw);
							    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
						    }
						}
						HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
						apiResponse.put("detail", detailMap);
					} else {
						String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
						String fullDateTime = betInfo.get("betTime").toString();
						String dateOnly = fullDateTime.substring(0, 10);

						// Step 1: Get round info from /csapi/getBetting
						String bettingUrl = apiBaseUrl + "/csapi/getBetting";
						JSONObject bettingBody = new JSONObject();
						bettingBody.put("userid", betInfo.get("siteIdxHex").toString()+betInfo.get("memberId").toString());
						bettingBody.put("sdate", dateOnly+" 00:00:00.000");
						bettingBody.put("edate", dateOnly+" 23:59:59.999");
						bettingBody.put("pagesize", "1");
						bettingBody.put("gameid", "0");
						bettingBody.put("roundcode", betInfo.get("uuid").toString());

						HttpHeaders headers = new HttpHeaders();
						headers.setContentType(MediaType.APPLICATION_JSON);
						headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
						headers.set("Authorization", "Bearer " + betInfo.get("vendorApiKey").toString());

						log.info(LOG_PREFIX+ "ONIX::Step1 getBetting Url: " + bettingUrl);
						log.info(LOG_PREFIX+ "ONIX::Step1 getBetting Body: " + bettingBody.toString());

						try {
							ResponseEntity<String> bettingRes = webClient.post()
									.uri(bettingUrl)
									.headers(h -> h.addAll(headers))
									.bodyValue(bettingBody.toString())
									.retrieve()
									.toEntity(String.class)
									.block();
							if(bettingRes != null && bettingRes.getBody() != null) {
								JSONObject bettingObj = new JSONObject(bettingRes.getBody());
								log.info(LOG_PREFIX+ "ONIX::Step1 getBetting Response: " + bettingObj.toString());

								int bettingResult = bettingObj.getInt("result");
								if(bettingResult == 1 && bettingObj.has("data")) {
									JSONArray dataArray = bettingObj.getJSONArray("data");
									if(dataArray.length() > 0) {
										isSaveEvoDetail = true;
										JSONObject dataObj = dataArray.getJSONObject(0);
										JSONObject detailObj = dataObj.getJSONArray("details").getJSONObject(0);
										String gameType = detailObj.optString("gameType");
										JSONArray participants = detailObj.optJSONArray("participants");
										if (participants != null) {
										    for (int i = 0; i < participants.length(); i++) {
										    	// result 객체 가져오기 (없으면 생성)
										    	JSONObject participant = participants.optJSONObject(i);
											    JSONObject result = participant.optJSONObject("result");
											    if (result == null) {
											        result = new JSONObject();
											        participant.put("result", result);
											    }

											    // link 값 세팅
											    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
											    String encrypted = AesUtil.encrypt(raw);
											    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
										    }
										}
										HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
										apiResponse.put("detail", detailMap);
										saveObj = detailObj;

									} else {
										apiResponse.put("detail", "betId is not exist");
									}
								} else {
									apiResponse.put("detail", "betId is not exist");
									String errorMsg = bettingObj.has("msg") ? bettingObj.getString("msg") : "Unknown error";
									log.error(LOG_PREFIX+ "ONIX::Betting Error: " + errorMsg);
								}
							} else {
								apiResponse.put("detail", "betId is not exist");
								log.error(LOG_PREFIX+ "ONIX::Betting Response is null");
							}
						} catch(WebClientResponseException e) {
							log.error(LOG_PREFIX+ "ONIX::Betting HttpClientErrorException : " + e.getMessage());
							log.error(LOG_PREFIX+ "ONIX::Betting status code : " + e.getStatusCode().value());
							log.error(LOG_PREFIX+ "ONIX::Betting response body : " + e.getResponseBodyAsString());
							JSONObject errorJson = new JSONObject(e.getResponseBodyAsString());
							String errorMsg = errorJson.has("msg") ? errorJson.getString("msg") : "Betting API error";
							log.error(LOG_PREFIX+ "ONIX::Betting error : " + errorMsg);
							throw new ApiException("P209", errorMsg);
						} catch (WebClientRequestException rae) {
							if(rae.getCause() instanceof io.netty.channel.ConnectTimeoutException) {
								log.error(LOG_PREFIX+ "ONIX::Betting ConnectTimeoutException::" + rae.getMessage());
							}
							if(rae.getCause() instanceof io.netty.handler.timeout.ReadTimeoutException) {
								log.error(LOG_PREFIX+ "ONIX::Betting ReadTimeoutException::" + rae.getMessage());
							}
							if(rae.getCause() instanceof InterruptedException) {
								log.error(LOG_PREFIX+ "ONIX::Betting InterruptedException::" + rae.getMessage());
							}
							throw new ApiException("P210", rae.getMessage());
						} catch(Exception e) {
							log.error(LOG_PREFIX+ "ONIX::Betting Exception : " + e.getMessage());
							throw new ApiException("P211", e.getMessage());
						}
					}
				} else if("bet_radar".equals(betInfo.get("vendorCode").toString())) {
					log.info(LOG_PREFIX+ "========== BetDetail::BET_RADAR ==========");
					// TODO: 베팅상세 내려주기
					/*
					 * param.getBetId() 기준으로 bet_radar_log 테이블 조회시 베팅과 결과 두가지가 나옴.
					 * 결과가 없는경우 베팅만 한가지 있을 수 있음.
					 * param.getBetId() 기준 bet_radar_log에서 jsonstr(베팅상세 json 원본)을 가져와서
					 * 하나의 json으로 합쳐서 내려주기 구현.
					 */
					// bet_radar_log 테이블에서 조회

					String radarLogJson = siteService.getMergedBetRadarLog(param.getBetId());
					if(radarLogJson != null && !radarLogJson.isEmpty()) {
						log.info(LOG_PREFIX+ "bet_radar_log found, returning merged json");
						dType = "detail_radar";
						apiResponse.put("dType", dType);
						HashMap<String, Object> detailMap = new Gson().fromJson(radarLogJson, HashMap.class);
						String detailUrl = "https://sportsbook.spokok.com/bets/detail/"+param.getBetId();
						detailMap.put("dLink", detailUrl);
						apiResponse.put("detail", detailMap);
						apiResponse.success();
						return apiResponse;
					}

				} else if("thenut".equals(betInfo.get("vendorCode").toString())) {
					log.info(LOG_PREFIX+ "========== BetDetail::THENUT ==========");
					String jsonStr = siteService.getOrgDetailData(param);
					log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
						String detailType = StringUtils.getType(jsonStr);
						if(detailType.equals("URL")) {
							dType = "detail_2";
							apiResponse.put("dType", dType);
							String detailUrl = jsonStr;
							HashMap url = new HashMap();
							url.put("url", detailUrl);
							apiResponse.put("detail", url);
						} else if(detailType.equals("JSON")) {
							dType = "detail_3";
							apiResponse.put("dType", dType);

							JSONObject detailObj = new JSONObject(jsonStr);
							String gameType = detailObj.optString("gameType");
							JSONArray participants = detailObj.optJSONArray("participants");
							if (participants != null) {
							    for (int i = 0; i < participants.length(); i++) {
							    	// result 객체 가져오기 (없으면 생성)
							    	JSONObject participant = participants.optJSONObject(i);
								    JSONObject result = participant.optJSONObject("result");
								    if (result == null) {
								        result = new JSONObject();
								        participant.put("result", result);
								    }

								    // link 값 세팅
								    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
								    String encrypted = AesUtil.encrypt(raw);
								    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
							    }
							}
							HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
							apiResponse.put("detail", detailMap);
						} else {
							apiResponse.put("dType", dType);
							apiResponse.put("detail", "betId is not exist");
						}
					} else {
						String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
						String apiKey = betInfo.get("vendorApiKey").toString();
						HashMap<String, String> apiInfo = new HashMap<>();
						apiInfo.put("apiBaseUrl", apiBaseUrl);
						apiInfo.put("apiKey", apiKey);
						apiInfo.put("betId", param.getBetId());

						if("CASINO".equals(betInfo.get("vendorCetegory").toString())) {
							isSaveEvoDetail = true;
							dType = "detail_3";
							apiResponse.put("dType", dType);
							JSONObject detailObj = thenutService.getBetDetail(LOG_PREFIX, apiInfo);
							if(detailObj == null) {
								apiResponse.put("detail", "betId is not exist");
							} else {
								String gameType = detailObj.optString("gameType");
								JSONArray participants = detailObj.optJSONArray("participants");
								if (participants != null) {
								    for (int i = 0; i < participants.length(); i++) {
								    	// result 객체 가져오기 (없으면 생성)
								    	JSONObject participant = participants.optJSONObject(i);
									    JSONObject result = participant.optJSONObject("result");
									    if (result == null) {
									        result = new JSONObject();
									        participant.put("result", result);
									    }

									    // link 값 세팅
									    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
									    String encrypted = AesUtil.encrypt(raw);
									    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
								    }
								}
								HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
								apiResponse.put("detail", detailMap);
								saveObj = detailObj;
							}
						} else {
							dType = "detail_2";
							apiResponse.put("dType", dType);
							String detailUrl = thenutService.getBetDetailUrl(LOG_PREFIX, apiInfo);
							if(detailUrl == null) {
								apiResponse.put("detail", "betId is not exist");
							} else {
								log.info(LOG_PREFIX+ "detailUrl::"+detailUrl);
								HashMap<String, String> url = new HashMap<>();
								url.put("url", detailUrl);
								apiResponse.put("detail", url);
							}
						}
					}
				} else if("panda".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);
					String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
					String apiKey = betInfo.get("vendorApiKey").toString();
					String tranId = betInfo.get("uuid").toString();

					HashMap<String, String> apiInfo = new HashMap<>();
					apiInfo.put("apiBaseUrl", apiBaseUrl);
					apiInfo.put("apiKey", apiKey);
					apiInfo.put("tranId", tranId);
					String detailUrl = pandaService.getDetail(LOG_PREFIX, apiInfo);
					if(detailUrl == null) {
						apiResponse.put("detail", "betId is not exist");
					} else {
						log.info(LOG_PREFIX+ "detailUrl::"+detailUrl);
						HashMap<String, String> url = new HashMap<>();
						url.put("url", detailUrl);
						apiResponse.put("detail", url);
					}
				} else {
					apiResponse.put("dType", dType);
					apiResponse.put("detail", "betId is not exist");
				}
			} else {
				apiResponse.put("dType", dType);
				apiResponse.put("detail", "betId is not exist");
			}

			if(saveObj != null || saveObjs != null) {
				// check detail data
				if(isSaveEvoDetail) {
					asyncSiteService.insertEvoDetailProc(LOG_PREFIX, param.getBetId(), saveObj, saveObjs);
				}
			} else {
				log.info(LOG_PREFIX+ "saved detail is null");
			}

			apiResponse.success();
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}


	@ResponseBody
	@PostMapping(value="/betList")
	public ApiResponse betList(HttpServletRequest request, @RequestBody BetParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site ==null) {
				throw new ApiException("1000", "accessDinied");
			}
			param.setSiteIdx(site.getSiteIdx());
			// 결과보내기
			List<HashMap> betList = new ArrayList<>();
			String nextStartDate = "";
			if(betList.size()>0) {
				nextStartDate = betList.get(betList.size()-1).get("regDate").toString();
			} else {

			}

			apiResponse.put("list", betList);
			apiResponse.put("nextStartDate", nextStartDate);

			apiResponse.success();

		} catch (Exception e) {
			log.info(e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}


	@ResponseBody
	public ApiResponse parseUseUpdate(HttpServletRequest request, @RequestBody PlayParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {
			//apiKey 체크
			Site site  = siteService.getSiteInfo(request);
			if(site==null) {
				throw new ApiException("1000", "accessDinied");
			}

			if(param.getParseUseYn()!=null && !"".equals(param.getParseUseYn())) {
				HashMap parseParam  = new HashMap();
				parseParam.put("siteIdx", site.getSiteIdx());
				parseParam.put("siteId", site.getSiteId());
				parseParam.put("useYn", param.getParseUseYn());
				siteService.insertParseUse(parseParam);
			}
			HashMap ParseUseYn = siteService.getParseUseYn(site.getSiteIdx());
			if(ParseUseYn==null || "N".equals(ParseUseYn.get("useYn").toString())) {
				apiResponse.put("currentUseYn","N");
			}else {
				apiResponse.put("currentUseYn","Y");
			}



			apiResponse.success();
		} catch (Exception e) {
			log.info(e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}


	/**
	 * 파싱 API 사용하기 위한 토큰 발급
	 * @param request
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping(value="/getToken")
	public synchronized ApiResponse getToken(HttpServletRequest request) throws Exception {
		ApiResponse apiResponse = new ApiResponse();

		// apiKey 체크
		String Authorization = request.getHeader("Authorization").toString();
		String LOG_PREFIX = "#-getToken::"+Authorization+"::::";
		Site site = siteService.getSiteInfo(request);
		if(site == null) {
			log.error(LOG_PREFIX+ "T1001:Access denied Key");
			apiResponse.setResultCode("T1001");
			apiResponse.setResultMessage("Access denied Key");
			return apiResponse;
		}
		LOG_PREFIX = "#-getToken::"+Authorization+"::"+site.getSiteId()+"::::";
//
//		if(!(
//			site.getSiteIp() == null ||
//				"".equals(site.getSiteIp()) ||
//					"3.3.3.3".equals(site.getSiteIp()) ||
//						site.getSiteIp().indexOf(IPKit.getIpAddressByRequest(request)) >= 0
//		)) {
//			log.error(LOG_PREFIX+ "site.getSiteIp("+site.getSiteIp()+"), denied" + IPKit.getIpAddressByRequest(request));
//			log.error(LOG_PREFIX+ "T1002:Access denied IP");
//			apiResponse.setResultCode("T1002");
//			apiResponse.setResultMessage("Access denied IP");
//			return apiResponse;
//		}
//
		// 토큰생성
		JwtClame jwtinfo = new JwtClame();
		jwtinfo.setSiteId(site.getSiteId());
		jwtinfo.setMemId("");
		String tok = jwtManager.generateToken(jwtinfo);

		// 결과보내기
		apiResponse.put("token", tok);
		apiResponse.success();

		return apiResponse;
	}

	/**
	 * 파싱 API - 보유중인 알
	 * @param request
	 * @param token
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping(value="/p1/agentBalance")
	public ApiResponse agentBalance(HttpServletRequest request, @RequestHeader String token) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error("#-p1::agentBalance::"+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			//토큰체크
			if(token == null) {
				log.error("#-p1::agentBalance::"+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);
			final String LOG_PREFIX = "#-p1::agentBalance::"+tokenInfo.getSid()+"::::";


			long targetSiteCredit = siteService.getSiteCredit(tokenInfo.getSid());
			log.info(LOG_PREFIX + "targetSiteCredit: " + targetSiteCredit);
			apiResponse.put("credit", targetSiteCredit);

			long targetSitePoint = siteService.getSitePoint(tokenInfo.getSid());
			log.info(LOG_PREFIX + "targetSitePoint: " + targetSitePoint);
			apiResponse.put("point", targetSitePoint);

			apiResponse.success();

		} catch (Exception e) {
			log.error("#-p1::agentBalance::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}


	@ResponseBody
	@PostMapping(value="/p1/balance")
	public ApiResponse apiBalance(HttpServletRequest request, @RequestHeader String token, @RequestBody CashParam param) {
		ApiResponse apiResponse = new ApiResponse();
		final String LOG_PREFIX = "#-apiBalance::"+param.getMemberId()+"::::";

		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error(LOG_PREFIX+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			//토큰체크
			if(token == null) {
				log.error(LOG_PREFIX+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);

			ObjectMapper objectMapper = new ObjectMapper();
			String reqJsonStr = objectMapper.writeValueAsString(param);
			JSONObject reqObj = new JSONObject(reqJsonStr);
			log.info(LOG_PREFIX+ "Request {}", reqObj);

			final String IS_TRANSFER = site.getIsTransfer();
			if(IS_TRANSFER.equals("N")) {
				log.error(LOG_PREFIX+ "T1008:NOT_TRANSFER_SITE");
				apiResponse.setResultCode("T1008");
				apiResponse.setResultMessage("NOT_TRANSFER_SITE");
				return apiResponse;
			}

			//회원정보
			AuthParam authParam = new AuthParam();
			authParam.setUserId(param.getMemberId());
			CommonParam commonParam = new CommonParam();
			commonParam.setSiteIdx(site.getSiteIdx());
			commonParam.setAuthParam(authParam);
			log.info(LOG_PREFIX+ "CommonParam::"+commonParam.toString());
			Member member = siteService.getMember(commonParam);
			if(member == null) {
				// Fail
				log.error(LOG_PREFIX+ "T1004:NOT_FOUND_USER");
				apiResponse.setResultCode("T1004");
				apiResponse.setResultMessage("NOT_FOUND_USER");
				return apiResponse;
			}
			log.info(LOG_PREFIX+ "Member::"+member.toString());

			apiResponse.put("balance", member.getUserBalance());

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "Exception::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		apiResponse.success();

		return apiResponse;
	}


	@ResponseBody
	@PostMapping(value="/p1/deposit")
	public ApiResponse apiDeposit(HttpServletRequest request, @RequestHeader String token, @RequestBody CashParam param) {
		ApiResponse apiResponse = new ApiResponse();
		final String LOG_PREFIX = "#-apiDeposit::"+param.getMemberId()+"::::";

		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error(LOG_PREFIX+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			//토큰체크
			if(token == null) {
				log.error(LOG_PREFIX+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);

			ObjectMapper objectMapper = new ObjectMapper();
			String reqJsonStr = objectMapper.writeValueAsString(param);
			JSONObject reqObj = new JSONObject(reqJsonStr);
			log.info(LOG_PREFIX+ "Request {}", reqObj);

			final String IS_TRANSFER = site.getIsTransfer();
			if(IS_TRANSFER.equals("N")) {
				log.error(LOG_PREFIX+ "T1008:NOT_TRANSFER_SITE");
				apiResponse.setResultCode("T1008");
				apiResponse.setResultMessage("NOT_TRANSFER_SITE");
				return apiResponse;
			}

			//회원정보
			param.setSiteIdx(site.getSiteIdx());
			Member member = siteService.getMember2(param);
			if(member == null) {
				// Fail
				log.error(LOG_PREFIX+ "T1004:NOT_FOUND_USER");
				apiResponse.setResultCode("T1004");
				apiResponse.setResultMessage("NOT_FOUND_USER");
				return apiResponse;
			}
			log.info(LOG_PREFIX+ "Member::"+member.toString());

			long amount = Long.parseLong(param.getAmount());
			long userBalance = member.getUserBalance();
			long siteCredit = site.getCredit();
			if(siteCredit < amount) {
				log.error(LOG_PREFIX+ "T1006:사이트 크레딧 부족");
				apiResponse.setResultCode("T1006");
				apiResponse.setResultMessage("사이트 크레딧 부족");
				return apiResponse;
			}

			// Insert credit_info : MAI, IN
			Credit credit = new Credit();
			credit.setSiteIdx(site.getSiteIdx());
			credit.setSiteId(site.getSiteId());
			credit.setTargetSiteIdx(member.getMemberIdx());
			credit.setTargetSiteId(member.getMemberId());
			credit.setCreditType("MAI");
			credit.setCreditRate(null);
			credit.setInOut("IN");
			credit.setTranId("회원 충전");
			credit.setCreditAmt(Long.parseLong(param.getAmount()));
			credit.setUserAmt(Long.parseLong(param.getAmount()));
			credit.setCreditStatus(1);
			credit.setWaitTime(-10);	// 재요청 대기시간 10초
			int cnt = creditService.checkRequestTime(credit);
			if(cnt > 0) {
				// Fail
				log.error(LOG_PREFIX+ "T1005:재요청 대기시간 10초");
				apiResponse.setResultCode("T1005");
				apiResponse.setResultMessage("재요청 대기시간 10초");
				return apiResponse;
			}
			int result = creditService.insertCreditByCash(credit);
			if(result < 0) {
				// Fail
				log.error(LOG_PREFIX+ "T1009:DEPOSIT_FAIL");
				apiResponse.setResultCode("T1009");
				apiResponse.setResultMessage("DEPOSIT_FAIL");
				return apiResponse;
			} else {
				// TODO: 성공 시 잔액 담아서 내려주기
				apiResponse.put("bfBalance", userBalance);
				apiResponse.put("balance", userBalance+amount);
			}

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "Exception::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		apiResponse.success();

		return apiResponse;
	}


	@ResponseBody
	@PostMapping(value="/p1/withdraw")
	public ApiResponse apiWithdraw(HttpServletRequest request, @RequestHeader String token, @RequestBody CashParam param) {
		ApiResponse apiResponse = new ApiResponse();
		final String LOG_PREFIX = "#-apiWithdraw::"+param.getMemberId()+"::::";

		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error(LOG_PREFIX+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			//토큰체크
			if(token == null) {
				log.error(LOG_PREFIX+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);

			ObjectMapper objectMapper = new ObjectMapper();
			String reqJsonStr = objectMapper.writeValueAsString(param);
			JSONObject reqObj = new JSONObject(reqJsonStr);
			log.info(LOG_PREFIX+ "Request {}", reqObj);

			final String IS_TRANSFER = site.getIsTransfer();
			if(IS_TRANSFER.equals("N")) {
				log.error(LOG_PREFIX+ "T1008:NOT_TRANSFER_SITE");
				apiResponse.setResultCode("T1008");
				apiResponse.setResultMessage("NOT_TRANSFER_SITE");
				return apiResponse;
			}

			//회원정보
			param.setSiteIdx(site.getSiteIdx());
			Member member = siteService.getMember2(param);
			if(member == null) {
				// Fail
				log.error(LOG_PREFIX+ "T1004:NOT_FOUND_USER");
				apiResponse.setResultCode("T1004");
				apiResponse.setResultMessage("NOT_FOUND_USER");
				return apiResponse;
			}
			log.info(LOG_PREFIX+ "Member::"+member.toString());

			long amount = Long.parseLong(param.getAmount());
			long userBalance = member.getUserBalance();

			if(param.getWithAll() != null && param.getWithAll().equals("Y")) {
				amount = userBalance;
				param.setAmount(Long.toString(amount));
			}

			if(userBalance < amount) {
				log.error(LOG_PREFIX+ "T1007:유저 잔액 부족");
				apiResponse.setResultCode("T1007");
				apiResponse.setResultMessage("유저 잔액 부족");
				return apiResponse;
			}

			// Insert credit_info : MAO, OUT
			Credit credit = new Credit();
			credit.setSiteIdx(site.getSiteIdx());
			credit.setSiteId(site.getSiteId());
			credit.setTargetSiteIdx(member.getMemberIdx());
			credit.setTargetSiteId(member.getMemberId());
			credit.setCreditType("MAO");
			credit.setCreditRate(null);
			credit.setInOut("OUT");
			credit.setTranId("회원 환전");
			credit.setCreditAmt(Long.parseLong(param.getAmount()));
			credit.setUserAmt(Long.parseLong(param.getAmount()));
			credit.setCreditStatus(1);
			credit.setWaitTime(-10);	// 재요청 대기시간 10초
			int cnt = creditService.checkRequestTime(credit);
			if(cnt > 0) {
				// Fail
				log.error(LOG_PREFIX+ "T1005:재요청 대기시간 10초");
				apiResponse.setResultCode("T1005");
				apiResponse.setResultMessage("재요청 대기시간 10초");
				return apiResponse;
			}
			int result = creditService.insertCreditByCash(credit);
			if(result < 0) {
				// Fail
				log.error(LOG_PREFIX+ "T1009:WITHDRAW_FAIL");
				apiResponse.setResultCode("T1009");
				apiResponse.setResultMessage("WITHDRAW_FAIL");
				return apiResponse;
			} else {
				// TODO: 성공 시 잔액 담아서 내려주기
				apiResponse.put("bfBalance", userBalance);
				apiResponse.put("balance", userBalance-amount);
			}

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "Exception::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		apiResponse.success();

		return apiResponse;
	}


	/**
	 * 파싱 API - 충환전내역 가져오기
	 * @param request
	 * @param token
	 * @param param
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping(value="/p1/cashInOut")
	public ApiResponse apiCashInOut(HttpServletRequest request, @RequestHeader String token, @RequestBody CashParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		JwtManager.TokenInfo tokenInfo = null;

		try {
			//토큰체크
			if(token == null) {
				log.error("#-p1::apiCashInOut::TOKEN_ERROR::"+request.getHeader("Authorization").toString()+"::"+ "access denied token");
				log.error("#-p1::apiCashInOut::"+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			tokenInfo = jwtManager.getTokenInfo(token);

		} catch (Exception e) {
			log.error("#-p1::apiCashInOut::TOKEN_ERROR::"+request.getHeader("Authorization").toString()+"::"+ "[Exception]"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		final String LOG_PREFIX = "#-p1::apiCashInOut::"+tokenInfo.getSid()+"::::";

		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error(LOG_PREFIX+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			param.setSiteIdx(site.getSiteIdx());
			if(param.getCount() == null || param.getCount().equals("")) {
				param.setCount("100");
			}

			int count = Integer.parseInt(param.getCount());
			if(count > 2000) {
				param.setCount("2000");
			}

			if(param.getStartDate() == null || param.getStartDate().equals("")) {
				log.error(LOG_PREFIX+ "T1011:StartDate is not null");
				apiResponse.setResultCode("T1011");
				apiResponse.setResultMessage("StartDate is not null");
				return apiResponse;
			}
			param.setStartDate(param.getStartDate().replace("T", " "));

			if(param.getEndDate() != null && !"".equals(param.getEndDate())) {
				param.setEndDate(param.getEndDate().replace("T", " "));
			}

			ObjectMapper objectMapper = new ObjectMapper();
			String reqJsonStr = objectMapper.writeValueAsString(param);
			JSONObject reqObj = new JSONObject(reqJsonStr);
			log.info(LOG_PREFIX+ "Request {}", reqObj);

			final String IS_TRANSFER = site.getIsTransfer();
			if(IS_TRANSFER.equals("N")) {
				log.error(LOG_PREFIX+ "T1008:NOT_TRANSFER_SITE");
				apiResponse.setResultCode("T1008");
				apiResponse.setResultMessage("NOT_TRANSFER_SITE");
				return apiResponse;
			}

			// TODO: 입출금 내역 API 구현..
			int totalCnt = creditService.getCashInOutListCnt(param);
			List<HashMap> list = creditService.getCashInOutList(param);
			String nextStartDate = "";
			if(list.size() > 0) {
				nextStartDate = list.get(list.size()-1).get("regDate").toString();
			} else {
				nextStartDate = param.getStartDate();
			}

			apiResponse.put("total", totalCnt);
			apiResponse.put("list", list);
			apiResponse.put("nextStartDate", nextStartDate);

			apiResponse.success();

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}



	/**
	 * 파싱 API - 사용가능한 벤더사 리스트
	 * @param request
	 * @param token
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping(value="/p1/vendorList")
	public ApiResponse betVendorList(HttpServletRequest request, @RequestHeader String token) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error("#-p1::vendorList::"+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			//토큰체크
			if(token == null) {
				log.error("#-p1::vendorList::"+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);
			final String LOG_PREFIX = "#-p1::betVendorList::"+tokenInfo.getSid()+"::::";

			// 결과보내기
			List<HashMap> vendorList = siteService.getApiBetVendorList(site.getSiteIdx());
			log.info(LOG_PREFIX + "vendorList size: " + vendorList.size());
			apiResponse.put("list", vendorList);
			apiResponse.success();

		} catch (Exception e) {
			log.error("#-p1::betVendorList::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}

	/**
	 * 파싱 API - 배팅내역 가져오기
	 * @param request
	 * @param token
	 * @param param
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping(value="/p1/transaction")
	public ApiResponse betTransactionList(HttpServletRequest request, @RequestHeader String token, @RequestBody BetParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		JwtManager.TokenInfo tokenInfo = null;

		try {
			//토큰체크
			if(token == null) {
				log.error("#-p1::betTransactionList::TOKEN_ERROR::"+request.getHeader("Authorization").toString()+"::"+ "access denied token");
				log.error("#-p1::betTransactionList::"+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			tokenInfo = jwtManager.getTokenInfo(token);

		} catch (Exception e) {
			log.error("#-p1::betTransactionList::TOKEN_ERROR::"+request.getHeader("Authorization").toString()+"::"+ "[Exception]"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		final String LOG_PREFIX = "#-p1::betTransactionList::"+tokenInfo.getSid()+"::::";

		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error(LOG_PREFIX+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			param.setSiteIdx(site.getSiteIdx());
			if(param.getCount() == null || param.getCount().equals("")) {
				param.setCount("100");
			}

			int count = Integer.parseInt(param.getCount());
			if(count > 2000) {
				param.setCount("3000");
			}

			if(param.getIsDetail() == null || param.getIsDetail().equals("")) param.setIsDetail("N");
			log.info(LOG_PREFIX + "BetParam: " + param);

			// 결과보내기
		 	int betCnt = siteService.getApiBetTransactionCnt(param);
		 	log.info(LOG_PREFIX + "total: " + betCnt);
			List<HashMap> betList = siteService.getApiBetTransactionList(param);
			if(param.getIsDetail().equals("Y")) {
				for(HashMap item : betList) {
					if(item.get("detail") != null && !"".equals(item.get("detail").toString())) {
						JSONObject detailObj = new JSONObject(item.get("detail").toString());
						HashMap<String,Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
						item.put("detail", detailMap);
					}
				}
			}
		 	log.info(LOG_PREFIX + "list size: " + betList.size());
			String nextStartDate = "";
			if(betList.size() > 0) {
				nextStartDate = betList.get(betList.size()-1).get("regDate").toString();
			} else {
				nextStartDate = param.getStartDate();
			}

			apiResponse.put("total", betCnt);
			apiResponse.put("list", betList);
			apiResponse.put("nextStartDate", nextStartDate);

			apiResponse.success();

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}

	/**
	 * 파싱 API - 배팅내역 가져오기
	 * @param request
	 * @param token
	 * @param param
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping(value="/p1/transactionPage")
	public ApiResponse transactionPage(HttpServletRequest request, @RequestHeader String token, @RequestBody BetParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		JwtManager.TokenInfo tokenInfo = null;

		try {
			//토큰체크
			if(token == null) {
				log.error("#-p1::transactionPage::TOKEN_ERROR::"+request.getHeader("Authorization").toString()+"::"+ "access denied token");
				log.error("#-p1::transactionPage::"+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			tokenInfo = jwtManager.getTokenInfo(token);

		} catch (Exception e) {
			log.error("#-p1::transactionPage::TOKEN_ERROR::"+request.getHeader("Authorization").toString()+"::"+ "[Exception]"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		final String LOG_PREFIX = "#-p1::transactionPage::"+tokenInfo.getSid()+"::::";

		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error(LOG_PREFIX+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			param.setSiteIdx(site.getSiteIdx());
			if(param.getCount() == null || param.getCount().equals("")) {
				param.setCount("100");
			}

			int count = Integer.parseInt(param.getCount());
			if(count > 2000) {
				param.setCount("3000");
			}

			if(param.getIsDetail() == null || param.getIsDetail().equals("")) param.setIsDetail("N");

			log.info(LOG_PREFIX + "BetParam: " + param);

			// 결과보내기
		 	int betCnt = siteService.getApiBetTransactionCnt(param);
		 	log.info(LOG_PREFIX + "total: " + betCnt);
			List<HashMap> betList = siteService.getApiBetTransactionList(param);
			if(param.getIsDetail().equals("Y")) {
				for(HashMap item : betList) {
					if(item.get("detail") != null && !"".equals(item.get("detail").toString())) {
						JSONObject detailObj = new JSONObject(item.get("detail").toString());
						HashMap<String,Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
						item.put("detail", detailMap);
					}
				}
			}
		 	log.info(LOG_PREFIX + "list size: " + betList.size());
			String nextStartDate = "";
			if(betList.size() > 0) {
				nextStartDate = betList.get(betList.size()-1).get("regDate").toString();
			} else {
				nextStartDate = param.getStartDate();
			}

			apiResponse.put("total", betCnt);
			apiResponse.put("list", betList);
			apiResponse.put("nextStartDate", nextStartDate);

			apiResponse.success();

		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}


	/**
	 * 파싱 API - 배팅내역 가져오기 (에볼루션전용-상세내용 포함)
	 * @param request
	 * @param token
	 * @param param
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping(value="/p1/betListDetail")
	public ApiResponse betListDetail(HttpServletRequest request, @RequestHeader String token, @RequestBody BetParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error("#-p1::betTransactionList::"+ "access denied Key");
				throw new ApiException("999", "access denied Key");
			}

			//토큰체크
			if(token == null) {
				log.error("#-p1::betTransactionList::"+ "access denied token");
				throw new ApiException("1000", "access denied token");
			}
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);
			final String LOG_PREFIX = "#-p1::betListDetail::"+tokenInfo.getSid()+"::::";
			log.info(LOG_PREFIX + "Request Body: " + param.toString());

			long siteIdx = site.getSiteIdx();
			String startDateStr = param.getStartDate();
			String endDateStr = param.getEndDate();
			String memberId = param.getMemberId();
			int count = Integer.parseInt(param.getCount());

			if(count < 100) {
				count = 100;
			}
			if(count > 2000) {
				count = 2000;
			}

			NexusApiInfo nexusApiInfo = NexusApiInfo.builder()
					.siteIdx(siteIdx)
					.build();
			JSONObject detailObj = nexusService.getTransaction(LOG_PREFIX, nexusApiInfo);

			apiResponse.success();

		} catch (Exception e) {
			log.error("#-p1::betTransactionList::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}


	/**
	 * 파싱 API - 배팅상세 가져오기
	 * @param request
	 * @param token
	 * @param param
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@PostMapping(value="/p1/betDetail")
	public ApiResponse betDetail(HttpServletRequest request, @RequestHeader String token, @RequestBody BetParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		JSONObject saveObj = null;
		JSONArray saveObjs = null;
		boolean isSaveEvoDetail = false;

		try {
			// apiKey 체크
			Site site = siteService.getSiteInfo(request);
			if(site == null) {
				log.error("#-p1::betDetail::"+ "T1001:Access denied Key");
				apiResponse.setResultCode("T1001");
				apiResponse.setResultMessage("Access denied Key");
				return apiResponse;
			}

			//토큰체크
			if(token == null) {
				log.error("#-p1::betDetail::"+ "T1003:Access token check");
				apiResponse.setResultCode("T1003");
				apiResponse.setResultMessage("Access token check");
				return apiResponse;
			}
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);
			final String LOG_PREFIX = "#-p1::betDetail::"+tokenInfo.getSid()+"::"+param.getBetId()+"::::";

			ObjectMapper objectMapper = new ObjectMapper();
			String reqJsonStr = objectMapper.writeValueAsString(param);
			JSONObject reqObj = new JSONObject(reqJsonStr);
			log.info(LOG_PREFIX+ "Request {}", reqObj);

			// 결과보내기
			apiResponse.put("betId", param.getBetId());
			String dType = "detail_0";

			HashMap betInfo = transService.getBetInfoByBetId(param);
			log.info(LOG_PREFIX+ "betInfo {}", betInfo);

			if(betInfo == null) betInfo = transService.getSiteBetByRefIdFromOld(param);
			log.info(LOG_PREFIX+ "betInfo_OLD {}", betInfo);

			if(betInfo != null) {
				param.setBetId(betInfo.get("refId").toString());

				String category = betInfo.get("vendorCetegory").toString();
				log.info(LOG_PREFIX+ "Game category : " + category);

				if("pragmatic".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);

					String detailURL = pragmaticService.getDetails(LOG_PREFIX, betInfo);
					if(detailURL != null && !detailURL.equals("")) {
						HashMap url = new HashMap();
						url.put("url", detailURL);
						apiResponse.put("detail", url);
					} else {
						apiResponse.put("detail", "betId is not exist");
					}

				} else if("svendor".equals(betInfo.get("vendorCode").toString())
                        || "pink".equals(betInfo.get("vendorCode").toString())) {
					String jsonStr = siteService.getOrgDetailData(param);
					log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
						dType = "detail_6";
						apiResponse.put("dType", dType);
						HashMap<String, Object> detailMap = new Gson().fromJson(jsonStr, HashMap.class);
						apiResponse.put("detail", detailMap);
					} else {
						isSaveEvoDetail = true;
						JSONObject dataObj = svendorService.getDetail(LOG_PREFIX, betInfo);
						if(dataObj == null) {
							apiResponse.put("detail", "betId is not exist");
						} else {
							if(dataObj.has("gameDetail") && !dataObj.isNull("gameDetail")) {
								String detailType = StringUtils.getType(dataObj.getString("gameDetail"));
								if(detailType.equals("URL")) {
									dType = "detail_2";
									apiResponse.put("dType", dType);
									String detailUrl = dataObj.getString("gameDetail");
									HashMap url = new HashMap();
									url.put("url", detailUrl);
									apiResponse.put("detail", url);
								} else if(detailType.equals("JSON")) {
									dType = "detail_6";
									apiResponse.put("dType", dType);
									JSONObject detailObj = dataObj.getJSONObject("gameDetail");
									saveObj = detailObj;
									HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
									apiResponse.put("detail", detailMap);
								} else {
									apiResponse.put("detail", "betId is not exist");
								}
							} else {
								apiResponse.put("detail", "betId is not exist");
							}
						}
					}

				} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==17 && "tower".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);
					String detail_url =
							betInfo.get("vendorApiUrl").toString()
							+"/v2/detail_url"
							+ "?site_code="+betInfo.get("vendorApiId").toString()
							+ "&user_id="+betInfo.get("siteHex").toString()+betInfo.get("memberId").toString()
							+ "&transaction_id="+betInfo.get("transactionId").toString()
							+ "&vendorCode=pp"
							+ "&gameCode="+betInfo.get("gameIdx").toString();
					log.info(LOG_PREFIX+"  {}" ,detail_url);
					OkHttpClient client = new OkHttpClient().newBuilder().build();
					okhttp3.Request requestok2 = new okhttp3.Request.Builder()
													.url(detail_url)
													.method("GET", null)//
													.addHeader("User-agent", "Mozilla")
													.addHeader("Authorization", betInfo.get("vendorApiKey").toString())
													.build();
					okhttp3.Response response = client.newCall(requestok2).execute();

					if(response.isSuccessful()){
						okhttp3.ResponseBody body =  response.body();
						String responseString = body.string();
						log.info(LOG_PREFIX+ "responseString::"+responseString);
						log.info(LOG_PREFIX+ "url::"+StringUtils.extractLink(responseString));
						String detailUrl = StringUtils.extractLink(responseString);
						String detailType = StringUtils.getType(detailUrl);
						if(detailType.equals("URL")) {
							dType = "detail_2";
							apiResponse.put("dType", dType);
							HashMap url = new HashMap();
							url.put("url", detailUrl);
							apiResponse.put("detail", url);
						} else {
							apiResponse.put("detail", "betId is not exist");
						}
						/*
						HashMap url = new HashMap();
						url.put("url", StringUtils.extractLink(responseString));
						apiResponse.put("detail", url);
						*/
					} else {
						apiResponse.put("detail", "betId is not exist");
					}

				} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==83 && "splus".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_1";
					apiResponse.put("dType", dType);
					HashMap d = new HashMap();
					List<HashMap> splusDetail = siteService.getSplusDetailData(param);
					for(HashMap ttype : splusDetail) {
						log.info(LOG_PREFIX+ "{}, {}", ttype.get("tranType").toString(), ttype.get("data").toString());
						HashMap<String,Object> detailMap = new Gson().fromJson(ttype.get("data").toString(), HashMap.class);
						d.put(ttype.get("tranType").toString(), detailMap);
					}

					apiResponse.put("detail", d);

				} else if("nexus".equals(betInfo.get("vendorCode").toString()) || "ace2".equals(betInfo.get("vendorCode").toString())) {
					String jsonStr = siteService.getOrgDetailData(param);
					log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
						dType = "detail_3";
						apiResponse.put("dType", dType);

						JSONObject detailObj = new JSONObject(jsonStr);
						String gameType = detailObj.optString("gameType");
						JSONArray participants = detailObj.optJSONArray("participants");
						if (participants != null) {
						    for (int i = 0; i < participants.length(); i++) {
						    	// result 객체 가져오기 (없으면 생성)
						    	JSONObject participant = participants.optJSONObject(i);
							    JSONObject result = participant.optJSONObject("result");
							    if (result == null) {
							        result = new JSONObject();
							        participant.put("result", result);
							    }

							    // link 값 세팅
							    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
							    String encrypted = AesUtil.encrypt(raw);
							    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
						    }
						}
						HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
						apiResponse.put("detail", detailMap);
					} else {
						isSaveEvoDetail = true;
						long siteIdx = (long) betInfo.get("siteIdx");
						long memberIdx = (long) betInfo.get("memberIdx");
						String trxId = betInfo.get("vendorTranKey").toString();
						String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
						String apiAgentId = betInfo.get("vendorApiId").toString();
						String apiSecretKey = betInfo.get("vendorApiKey").toString();
						NexusApiInfo nexusApiInfo = NexusApiInfo.builder()
														.siteIdx(siteIdx)
														.memberIdx(memberIdx)
														.apiBaseUrl(apiBaseUrl)
														.agentId(apiAgentId)
														.agentSecretKey(apiSecretKey)
														.build();
						if("CASINO".equals(betInfo.get("vendorCetegory").toString())) {
							dType = "detail_3";
							apiResponse.put("dType", dType);
							JSONObject detailObj = nexusService.getDetail(LOG_PREFIX, nexusApiInfo, trxId);
							if(detailObj == null) {
								apiResponse.put("detail", "betId is not exist");
							} else {
								int resultCode = detailObj.getInt("code");
								if(resultCode == 0) {
									JSONArray transactions = detailObj.getJSONArray("transactions");
									if(transactions.length() > 0) {
										List<HashMap<String, Object>> detailMap = new Gson().fromJson(transactions.toString(), List.class);
										saveObjs = transactions;
										apiResponse.put("detail", detailMap);
									} else {
										apiResponse.put("detail", "betId is not exist");
									}
								} else {
									apiResponse.put("detail", "betId is not exist");
								}
							}
						} else {
							dType = "detail_2";
							apiResponse.put("dType", dType);
							JSONObject detailObj = nexusService.getDetailUrl(LOG_PREFIX, nexusApiInfo, trxId);
							if(detailObj == null) {
								apiResponse.put("detail", "betId is not exist");
							} else {
								log.info(LOG_PREFIX+ "detailObj::"+detailObj.toString());
								int resultCode = detailObj.getInt("code");
								if(resultCode == 0) {
									HashMap<String, String> url = new HashMap<>();
									url.put("url", detailObj.getString("url"));
									apiResponse.put("detail", url);
								} else {
									apiResponse.put("detail", "betId is not exist");
								}
							}
						}
					}
				} else if("prime".equals(betInfo.get("vendorCode").toString())
						|| "dpcore".equals(betInfo.get("vendorCode").toString())) {
					log.info(LOG_PREFIX+ "========== BetDetail::PRIME ==========");
					dType = "detail_9";
					apiResponse.put("dType", dType);

					String jsonStr = siteService.getOrgDetailData(param);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {

						JSONObject detailObj = new JSONObject(jsonStr);
						String gameType = detailObj.optString("gameType");
						JSONArray participants = detailObj.optJSONArray("participants");
						if (participants != null) {
						    for (int i = 0; i < participants.length(); i++) {
						    	// result 객체 가져오기 (없으면 생성)
						    	JSONObject participant = participants.optJSONObject(i);
							    JSONObject result = participant.optJSONObject("result");
							    if (result == null) {
							        result = new JSONObject();
							        participant.put("result", result);
							    }

							    // link 값 세팅
							    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
							    String encrypted = AesUtil.encrypt(raw);
							    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
						    }
						}
						HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
						apiResponse.put("detail", detailMap);
					} else {
						HttpHeaders headers = new HttpHeaders();
						headers.setContentType(MediaType.APPLICATION_JSON);
						headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
						String apiKey = "Bearer "+betInfo.get("vendorApiKey").toString();
						headers.set("Authorization", apiKey);
						String detailUrl = betInfo.get("vendorApiUrl").toString() + "/api/getBettingDetails5?";
						String detailParam = "referer="+param.getBetId();
						detailUrl = detailUrl + detailParam;

						log.info(LOG_PREFIX+ "PRIME::Detail Url: " + detailUrl);
						log.info(LOG_PREFIX+ "PRIME::Detail Headers: " + headers.toString());
						ResponseEntity<String> response = null;
						JSONObject resJson = null;
						try {
							response = webClient.get()
									.uri(detailUrl)
									.headers(h -> h.addAll(headers))
									.retrieve()
									.toEntity(String.class)
									.block();
						} catch(WebClientResponseException e) {
							log.error(LOG_PREFIX+ "PRIME::Detail HttpClientErrorException : " + e.getMessage());
							log.error(LOG_PREFIX+ "PRIME::Detail status code : " + e.getStatusCode().value());
							log.error(LOG_PREFIX+ "PRIME::Detail response body : " + e.getResponseBodyAsString());
							resJson = new JSONObject(e.getResponseBodyAsString());
							log.error(LOG_PREFIX+ "PRIME::Detail error : " + resJson.getString("message"));
							throw new ApiException("P209", resJson.getString("message"));
						} catch (WebClientRequestException rae) {
				            if(rae.getCause() instanceof io.netty.channel.ConnectTimeoutException) {
				            	log.error(LOG_PREFIX+ "PRIME::Detail ConnectTimeoutException::"+rae.getMessage());
				            }
				            if(rae.getCause() instanceof io.netty.handler.timeout.ReadTimeoutException) {
				            	log.error(LOG_PREFIX+ "PRIME::Detail SocketTimeoutException::"+rae.getMessage());
				            }
				            if(rae.getCause() instanceof InterruptedException) {
				            	log.error(LOG_PREFIX+ "PRIME::Detail InterruptedException::"+rae.getMessage());
				            }
				            throw new ApiException("P210", rae.getMessage());
						} catch (Exception e) {
							log.error(LOG_PREFIX+ "PRIME::Detail Exception : " + e.getMessage());
							throw new ApiException("P211", e.getMessage());
						}

						if(response != null && response.getStatusCode() == HttpStatus.OK) {
							isSaveEvoDetail = true;
							log.info(LOG_PREFIX+ "PRIME::Detail response : " + response.getBody());
							resJson = new JSONObject(response.getBody());

							JSONObject dataObj = resJson;
							String gameType = dataObj.optString("gameType");
							JSONArray participants = dataObj.optJSONArray("participants");
							if (participants != null) {
							    for (int i = 0; i < participants.length(); i++) {
							    	// result 객체 가져오기 (없으면 생성)
							    	JSONObject participant = participants.optJSONObject(i);
								    JSONObject result = participant.optJSONObject("result");
								    if (result == null) {
								        result = new JSONObject();
								        participant.put("result", result);
								    }

								    // link 값 세팅
								    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
								    String encrypted = AesUtil.encrypt(raw);
								    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
							    }
							}
							saveObj = dataObj;
							HashMap<String, Object> detailMap = new Gson().fromJson(dataObj.toString(), HashMap.class);
							apiResponse.put("detail", detailMap);
						} else {
							apiResponse.put("detail", "betId is not exist");
						}
					}
				} else if("grand".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);
					String detailUrl =
							betInfo.get("vendorApiUrl").toString()
							+"/transactions/detail"
							+ "?round_id="+betInfo.get("vendorTranKey").toString();
					log.info(LOG_PREFIX+"Request Url : {}" ,detailUrl);
					OkHttpClient client = new OkHttpClient().newBuilder().build();
					okhttp3.Request requestok2 = new okhttp3.Request.Builder()
													.url(detailUrl)
													.method("GET", null)//
													.addHeader("User-agent", "Mozilla")
													.addHeader("Authorization", "Bearer "+betInfo.get("vendorApiKey").toString())
													.build();
					okhttp3.Response response = client.newCall(requestok2).execute();

					if(response.isSuccessful()){
						okhttp3.ResponseBody body = response.body();
						String responseString = body.string();
						log.info(LOG_PREFIX+ "responseString : " , responseString);
						JSONObject responseObj = new JSONObject(responseString);
						JSONObject dataObj = responseObj.getJSONObject("data");
						if(dataObj != null) {
							HashMap url = new HashMap();
							url.put("url", dataObj.getString("url"));
							apiResponse.put("detail", url);
						} else {
							apiResponse.put("detail", "betId is not exist");
						}
					} else {
						apiResponse.put("detail", "betId is not exist");
					}
				} else if("pracp".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);

					HashMap<String, String> apiInfo = new HashMap<>();
					apiInfo.put("apiAgentCode", betInfo.get("vendorApiId").toString());
					apiInfo.put("apiToken", betInfo.get("vendorApiKey").toString());
					apiInfo.put("apiBaseUrl", betInfo.get("vendorApiUrl").toString());
					apiInfo.put("uuid", betInfo.get("uuid").toString());
					apiInfo.put("round_id", betInfo.get("roundId").toString());

					JSONObject dataObj = pracpService.getDetail(LOG_PREFIX, apiInfo);
					log.info(LOG_PREFIX+ "dataObj : " + dataObj.toString());
					if(dataObj != null) {
						HashMap url = new HashMap();
						url.put("url", dataObj.getString("url"));
						apiResponse.put("detail", url);
					} else {
						apiResponse.put("detail", "betId is not exist");
					}
				} else if("tplus".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);

					String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
					String apiAuthKey = betInfo.get("vendorApiKey").toString();
					String pairTransactionKey = betInfo.get("refId").toString();
					TplusApiInfo apiInfo = TplusApiInfo.builder()
											.apiBaseUrl(apiBaseUrl)
											.apiAuthKey(apiAuthKey)
											.pairTransactionKey(pairTransactionKey).build();

					log.info(LOG_PREFIX+ "tplus::apiInfo: " + apiInfo);

					String detailURL = tplusService.getDetails(LOG_PREFIX, apiInfo);
					if(detailURL != null && !detailURL.equals("")) {
						HashMap url = new HashMap();
						url.put("url", detailURL);
						apiResponse.put("detail", url);
					} else {
						apiResponse.put("detail", "betId is not exist");
					}
				} else if("onix".equals(betInfo.get("vendorCode").toString())) {
					log.info(LOG_PREFIX+ "========== BetDetail::ONIX ==========");
					dType = "detail_8";
					apiResponse.put("dType", dType);

					String jsonStr = siteService.getOrgDetailData(param);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
						JSONObject detailObj = new JSONObject(jsonStr);
						String gameType = detailObj.optString("gameType");
						JSONArray participants = detailObj.optJSONArray("participants");
						if (participants != null) {
						    for (int i = 0; i < participants.length(); i++) {
						    	// result 객체 가져오기 (없으면 생성)
						    	JSONObject participant = participants.optJSONObject(i);
							    JSONObject result = participant.optJSONObject("result");
							    if (result == null) {
							        result = new JSONObject();
							        participant.put("result", result);
							    }

							    // link 값 세팅
							    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
							    String encrypted = AesUtil.encrypt(raw);
							    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
						    }
						}
						HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
						apiResponse.put("detail", detailMap);
					} else {
						String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
						String fullDateTime = betInfo.get("betTime").toString();
						String dateOnly = fullDateTime.substring(0, 10);

						// Step 1: Get round info from /csapi/getBetting
						String bettingUrl = apiBaseUrl + "/csapi/getBetting";
						JSONObject bettingBody = new JSONObject();
						bettingBody.put("userid", betInfo.get("siteIdxHex").toString()+betInfo.get("memberId").toString());
						bettingBody.put("sdate", dateOnly+" 00:00:00.000");
						bettingBody.put("edate", dateOnly+" 23:59:59.999");
						bettingBody.put("pagesize", "1");
						bettingBody.put("gameid", "0");
						bettingBody.put("roundcode", betInfo.get("uuid").toString());

						HttpHeaders headers = new HttpHeaders();
						headers.setContentType(MediaType.APPLICATION_JSON);
						headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
						headers.set("Authorization", "Bearer " + betInfo.get("vendorApiKey").toString());

						log.info(LOG_PREFIX+ "ONIX::Step1 getBetting Url: " + bettingUrl);
						log.info(LOG_PREFIX+ "ONIX::Step1 getBetting Body: " + bettingBody.toString());

						try {
							ResponseEntity<String> bettingRes = webClient.post()
									.uri(bettingUrl)
									.headers(h -> h.addAll(headers))
									.bodyValue(bettingBody.toString())
									.retrieve()
									.toEntity(String.class)
									.block();
							if(bettingRes != null && bettingRes.getBody() != null) {
								JSONObject bettingObj = new JSONObject(bettingRes.getBody());
								log.info(LOG_PREFIX+ "ONIX::Step1 getBetting Response: " + bettingObj.toString());

								int bettingResult = bettingObj.getInt("result");
								if(bettingResult == 1 && bettingObj.has("data")) {
									JSONArray dataArray = bettingObj.getJSONArray("data");
									if(dataArray.length() > 0) {
										isSaveEvoDetail = true;
										JSONObject dataObj = dataArray.getJSONObject(0);
										JSONObject detailObj = dataObj.getJSONArray("details").getJSONObject(0);
										String gameType = detailObj.optString("gameType");
										JSONArray participants = detailObj.optJSONArray("participants");
										if (participants != null) {
										    for (int i = 0; i < participants.length(); i++) {
										    	// result 객체 가져오기 (없으면 생성)
										    	JSONObject participant = participants.optJSONObject(i);
											    JSONObject result = participant.optJSONObject("result");
											    if (result == null) {
											        result = new JSONObject();
											        participant.put("result", result);
											    }

											    // link 값 세팅
											    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
											    String encrypted = AesUtil.encrypt(raw);
											    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
										    }
										}
										HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
										apiResponse.put("detail", detailMap);
										saveObj = detailObj;

									} else {
										apiResponse.put("detail", "betId is not exist");
									}
								} else {
									apiResponse.put("detail", "betId is not exist");
									String errorMsg = bettingObj.has("msg") ? bettingObj.getString("msg") : "Unknown error";
									log.error(LOG_PREFIX+ "ONIX::Betting Error: " + errorMsg);
								}
							} else {
								apiResponse.put("detail", "betId is not exist");
								log.error(LOG_PREFIX+ "ONIX::Betting Response is null");
							}
						} catch(WebClientResponseException e) {
							log.error(LOG_PREFIX+ "ONIX::Betting HttpClientErrorException : " + e.getMessage());
							log.error(LOG_PREFIX+ "ONIX::Betting status code : " + e.getStatusCode().value());
							log.error(LOG_PREFIX+ "ONIX::Betting response body : " + e.getResponseBodyAsString());
							JSONObject errorJson = new JSONObject(e.getResponseBodyAsString());
							String errorMsg = errorJson.has("msg") ? errorJson.getString("msg") : "Betting API error";
							log.error(LOG_PREFIX+ "ONIX::Betting error : " + errorMsg);
							throw new ApiException("P209", errorMsg);
						} catch (WebClientRequestException rae) {
							if(rae.getCause() instanceof io.netty.channel.ConnectTimeoutException) {
								log.error(LOG_PREFIX+ "ONIX::Betting ConnectTimeoutException::" + rae.getMessage());
							}
							if(rae.getCause() instanceof io.netty.handler.timeout.ReadTimeoutException) {
								log.error(LOG_PREFIX+ "ONIX::Betting ReadTimeoutException::" + rae.getMessage());
							}
							if(rae.getCause() instanceof InterruptedException) {
								log.error(LOG_PREFIX+ "ONIX::Betting InterruptedException::" + rae.getMessage());
							}
							throw new ApiException("P210", rae.getMessage());
						} catch(Exception e) {
							log.error(LOG_PREFIX+ "ONIX::Betting Exception : " + e.getMessage());
							throw new ApiException("P211", e.getMessage());
						}
					}
				} else if("bet_radar".equals(betInfo.get("vendorCode").toString())) {
					log.info(LOG_PREFIX+ "========== BetDetail::BET_RADAR ==========");
					// TODO: 베팅상세 내려주기
					/*
					 * param.getBetId() 기준으로 bet_radar_log 테이블 조회시 베팅과 결과 두가지가 나옴.
					 * 결과가 없는경우 베팅만 한가지 있을 수 있음.
					 * param.getBetId() 기준 bet_radar_log에서 jsonstr(베팅상세 json 원본)을 가져와서
					 * 하나의 json으로 합쳐서 내려주기 구현.
					 */
					// bet_radar_log 테이블에서 조회
					String radarLogJson = siteService.getMergedBetRadarLog(param.getBetId());
					if(radarLogJson != null && !radarLogJson.isEmpty()) {
						log.info(LOG_PREFIX+ "bet_radar_log found, returning merged json");
						dType = "detail_radar";
						apiResponse.put("dType", dType);
						HashMap<String, Object> detailMap = new Gson().fromJson(radarLogJson, HashMap.class);
						String detailUrl = "https://sportsbook.spokok.com/bets/detail/"+param.getBetId();
						detailMap.put("dLink", detailUrl);
						apiResponse.put("detail", detailMap);
						apiResponse.success();
						return apiResponse;
					}
				} else if("thenut".equals(betInfo.get("vendorCode").toString())) {
					log.info(LOG_PREFIX+ "========== BetDetail::THENUT ==========");
					String jsonStr = siteService.getOrgDetailData(param);
					log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
					if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
						String detailType = StringUtils.getType(jsonStr);
						if(detailType.equals("URL")) {
							dType = "detail_2";
							apiResponse.put("dType", dType);
							String detailUrl = jsonStr;
							HashMap url = new HashMap();
							url.put("url", detailUrl);
							apiResponse.put("detail", url);
						} else if(detailType.equals("JSON")) {
							dType = "detail_3";
							apiResponse.put("dType", dType);

							JSONObject detailObj = new JSONObject(jsonStr);
							String gameType = detailObj.optString("gameType");
							JSONArray participants = detailObj.optJSONArray("participants");
							if (participants != null) {
							    for (int i = 0; i < participants.length(); i++) {
							    	// result 객체 가져오기 (없으면 생성)
							    	JSONObject participant = participants.optJSONObject(i);
								    JSONObject result = participant.optJSONObject("result");
								    if (result == null) {
								        result = new JSONObject();
								        participant.put("result", result);
								    }

								    // link 값 세팅
								    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
								    String encrypted = AesUtil.encrypt(raw);
								    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
							    }
							}
							HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
							apiResponse.put("detail", detailMap);
						} else {
							apiResponse.put("dType", dType);
							apiResponse.put("detail", "betId is not exist");
						}
					} else {
						String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
						String apiKey = betInfo.get("vendorApiKey").toString();
						HashMap<String, String> apiInfo = new HashMap<>();
						apiInfo.put("apiBaseUrl", apiBaseUrl);
						apiInfo.put("apiKey", apiKey);
						apiInfo.put("betId", param.getBetId());

						if("CASINO".equals(betInfo.get("vendorCetegory").toString())) {
							isSaveEvoDetail = true;
							dType = "detail_3";
							apiResponse.put("dType", dType);
							JSONObject detailObj = thenutService.getBetDetail(LOG_PREFIX, apiInfo);
							if(detailObj == null) {
								apiResponse.put("detail", "betId is not exist");
							} else {
								String gameType = detailObj.optString("gameType");
								JSONArray participants = detailObj.optJSONArray("participants");
								if (participants != null) {
								    for (int i = 0; i < participants.length(); i++) {
								    	// result 객체 가져오기 (없으면 생성)
								    	JSONObject participant = participants.optJSONObject(i);
									    JSONObject result = participant.optJSONObject("result");
									    if (result == null) {
									        result = new JSONObject();
									        participant.put("result", result);
									    }

									    // link 값 세팅
									    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
									    String encrypted = AesUtil.encrypt(raw);
									    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
								    }
								}
								HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
								apiResponse.put("detail", detailMap);
								saveObj = detailObj;
							}
						} else {
							dType = "detail_2";
							apiResponse.put("dType", dType);
							String detailUrl = thenutService.getBetDetailUrl(LOG_PREFIX, apiInfo);
							if(detailUrl == null) {
								apiResponse.put("detail", "betId is not exist");
							} else {
								log.info(LOG_PREFIX+ "detailUrl::"+detailUrl);
								HashMap<String, String> url = new HashMap<>();
								url.put("url", detailUrl);
								apiResponse.put("detail", url);
							}
						}
					}
				} else if("panda".equals(betInfo.get("vendorCode").toString())) {
					dType = "detail_2";
					apiResponse.put("dType", dType);
					String apiBaseUrl = betInfo.get("vendorApiUrl").toString();
					String apiKey = betInfo.get("vendorApiKey").toString();
					String tranId = betInfo.get("uuid").toString();

					HashMap<String, String> apiInfo = new HashMap<>();
					apiInfo.put("apiBaseUrl", apiBaseUrl);
					apiInfo.put("apiKey", apiKey);
					apiInfo.put("tranId", tranId);
					String detailUrl = pandaService.getDetail(LOG_PREFIX, apiInfo);
					if(detailUrl == null) {
						apiResponse.put("detail", "betId is not exist");
					} else {
						log.info(LOG_PREFIX+ "detailUrl::"+detailUrl);
						HashMap<String, String> url = new HashMap<>();
						url.put("url", detailUrl);
						apiResponse.put("detail", url);
					}
				} else {
					apiResponse.put("dType", dType);
					apiResponse.put("detail", "betId is not exist");
				}
			} else {
				apiResponse.put("dType", dType);
				apiResponse.put("detail", "betId is not exist");
			}

			if(saveObj != null || saveObjs != null) {
				// check detail data
				if(isSaveEvoDetail) {
					asyncSiteService.insertEvoDetailProc(LOG_PREFIX, param.getBetId(), saveObj, saveObjs);
				}
			} else {
				log.info(LOG_PREFIX+ "saved detail is null");
			}

			apiResponse.success();
		} catch (Exception e) {
			log.info(e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}

	/**
	 * 파싱 API - 파싱설정
	 * @param request
	 * @param token
	 * @param param
	 * @return
	 * @throws Exception
	 */

	@ResponseBody
	@PostMapping(value="/p1/parseUseUpdate")
	public ApiResponse parseUseUpdate(HttpServletRequest request, @RequestHeader String token, @RequestBody PlayParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		try {
			//apiKey 체크
			Site site  = siteService.getSiteInfo(request);
			if(site == null) {
				log.error("#-p1::parseUseUpdate::"+ "access denied Key");
				throw new ApiException("999", "access denied Key");
			}

			//토큰체크
			if(token == null) {
				log.error("#-p1::parseUseUpdate::"+ "access denied token");
				throw new ApiException("1000", "access denied token");
			}
			JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);
			final String LOG_PREFIX = "#-p1::parseUseUpdate::"+tokenInfo.getSid()+"::::";


			if(param.getParseUseYn()!=null && !"".equals(param.getParseUseYn())) {
				HashMap parseParam  = new HashMap();
				parseParam.put("siteIdx", site.getSiteIdx());
				parseParam.put("siteId", site.getSiteId());
				parseParam.put("useYn", param.getParseUseYn());
				siteService.insertParseUse(parseParam);
				log.info(LOG_PREFIX + "insertParseUse SUCC");
			}

			HashMap ParseUseYn = siteService.getParseUseYn(site.getSiteIdx());
			if(ParseUseYn==null || "N".equals(ParseUseYn.get("useYn").toString())) {
				apiResponse.put("currentUseYn","N");
			} else {
				apiResponse.put("currentUseYn","Y");
			}
			log.info(LOG_PREFIX + "ParseUseYn: " + apiResponse.toString());

			apiResponse.success();
		} catch (Exception e) {
			log.error("#-p1::parseUseUpdate::"+e.toString());
			e.printStackTrace();
			apiResponse.fail();
		}

		return apiResponse;
	}

	/**
     * url과 파라미터 및 헤더정보를 가지고 그대로 해당 url에 바이패스하는 함수
     */
    @PostMapping("/bypass")
    public ResponseEntity<String> bypass(
            @RequestParam String targetUrl,
            @RequestParam String targetMethod,
            @RequestParam(required = false) String targetParameter,
            @RequestParam(defaultValue = "JSON") String parameterType,
            @RequestParam(required = false) String targetContentType,
            HttpServletRequest request) {

        log.info("=== Bypass Request Started ===");
        log.info("Target URL: {}", targetUrl);
        log.info("Target Method: {}", targetMethod);
        log.info("Parameter Type: {}", parameterType);

        try {
            HttpMethod httpMethod = HttpMethod.valueOf(targetMethod.toUpperCase());

            // 헤더 설정
            HttpHeaders headers = createHeaders(request, targetContentType, parameterType);

            // URL 및 요청 엔티티 준비
            String finalUrl;

            if (isGetOrDelete(httpMethod)) {
                // GET/DELETE: 쿼리 파라미터로 추가
                finalUrl = buildUrlWithQueryParams(targetUrl, targetParameter, parameterType);
                log.info("GET/DELETE request to: {}", finalUrl);
            } else {
                // POST/PUT: Body에 데이터 포함
                finalUrl = targetUrl;
            }

            WebClient.RequestBodySpec requestSpec = webClient.method(httpMethod).uri(finalUrl);
            requestSpec.headers(h -> h.addAll(headers));

            if (!isGetOrDelete(httpMethod)) {
                String body = hasParameter(targetParameter) ? targetParameter : null;
                if (body != null) {
                    requestSpec.bodyValue(body);
                }
                log.info("POST/PUT request to: {} with body length: {}",
                    finalUrl, body != null ? body.length() : 0);
            }

            ResponseEntity<String> response = requestSpec.retrieve()
                    .toEntity(String.class)
                    .block();

            log.info("Response received - Status: {}, Body length: {}",
                response.getStatusCode(),
                response.getBody() != null ? response.getBody().length() : 0);

            // 응답 헤더 복사 (일부 시스템 헤더 제외)
            HttpHeaders responseHeaders = new HttpHeaders();
            response.getHeaders().forEach((key, value) -> {
                if (!isSystemHeader(key)) {
                    responseHeaders.put(key, value);
                }
            });

            return ResponseEntity.status(response.getStatusCode())
                .headers(responseHeaders)
                .body(response.getBody());

        } catch (WebClientResponseException e) {
            // 4xx 에러 (클라이언트 에러)
            log.error("Client error - Status: {}, Body: {}", e.getStatusCode().value(), e.getResponseBodyAsString());
            return ResponseEntity.status(e.getStatusCode().value())
                .body(e.getResponseBodyAsString());

        } catch (WebClientRequestException e) {
            // 타임아웃이나 연결 에러
            log.error("Connection error: {}", e.getMessage());
            return ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT)
                .body(createErrorResponse("연결 타임아웃 또는 네트워크 에러: " + e.getMessage()));

        } catch (IllegalArgumentException e) {
            log.error("Invalid HTTP method: {}", targetMethod);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(createErrorResponse("잘못된 HTTP 메소드: " + targetMethod));

        } catch (Exception e) {
            log.error("Bypass failed: ", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(createErrorResponse("요청 처리 중 오류 발생: " + e.getMessage()));
        }
    }


    /**
	 * 에볼루션 상세 콜백
	 * @param request
	 * @param requestBody
	 * @return
	 * @throws Exception
	 */
	@ResponseBody
	@RequestMapping(path = "/evoDetail")
	public ApiResponse evoDetail(HttpServletRequest request, @RequestBody BetParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();

		final String LOG_PREFIX = "#-API::evoDetail::"+param.getBetId()+"::::";

		try {
			String jsonStr = siteService.getOrgDetailData(param);
			log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
			if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {

				JSONObject detailObj = new JSONObject(jsonStr);
				String gameType = detailObj.optString("gameType");
				JSONArray participants = detailObj.optJSONArray("participants");
				if (participants != null) {
				    for (int i = 0; i < participants.length(); i++) {
				    	// result 객체 가져오기 (없으면 생성)
				    	JSONObject participant = participants.optJSONObject(i);
					    JSONObject result = participant.optJSONObject("result");
					    if (result == null) {
					        result = new JSONObject();
					        participant.put("result", result);
					    }

					    // link 값 세팅
					    String raw = "betId="+param.getBetId()+"&gameType="+gameType;
					    String encrypted = AesUtil.encrypt(raw);
					    result.put("link", "https://fst.tx-direction.cc/detailsPage/"+encrypted);
				    }
				}

				HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
				apiResponse.put("detail", detailMap);
			}

			apiResponse.success();
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
			apiResponse.fail();
		}

		return apiResponse;
	}


    /**
     * 헤더 생성
     */
    private HttpHeaders createHeaders(HttpServletRequest request, String targetContentType, String parameterType) {
        HttpHeaders headers = new HttpHeaders();

        // 원본 요청의 주요 헤더들 복사
        copyImportantHeaders(request, headers);

        // Content-Type 설정
        setContentType(headers, targetContentType, parameterType);

        // 브라우저와 유사한 추가 헤더들
        setBrowserLikeHeaders(headers);

        log.debug("Request headers: {}", headers);
        return headers;
    }

    /**
     * 중요한 헤더들 복사
     */
    private void copyImportantHeaders(HttpServletRequest request, HttpHeaders headers) {
        // Authorization 헤더 (인증 정보)
        String authorization = request.getHeader("Authorization");
        if (authorization != null) {
            headers.add("Authorization", authorization);
            log.debug("Copied Authorization header");
        }

        // API Key 관련 헤더들
        String apiKey = request.getHeader("X-API-Key");
        if (apiKey != null) {
            headers.add("X-API-Key", apiKey);
        }

        String apiToken = request.getHeader("X-API-Token");
        if (apiToken != null) {
            headers.add("X-API-Token", apiToken);
        }

        // 커스텀 헤더들 (X-로 시작하는 것들)
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            if (headerName.startsWith("X-") && !headerName.equals("X-Forwarded-For")) {
                headers.add(headerName, request.getHeader(headerName));
                log.debug("Copied custom header: {}", headerName);
            }
        }
    }

    /**
     * Content-Type 설정
     */
    private void setContentType(HttpHeaders headers, String targetContentType, String parameterType) {
        if (targetContentType != null && !targetContentType.trim().isEmpty()) {
            // 명시적으로 지정된 Content-Type 사용
            try {
                headers.setContentType(MediaType.parseMediaType(targetContentType));
                log.debug("Set explicit Content-Type: {}", targetContentType);
            } catch (Exception e) {
                log.warn("Invalid targetContentType: {}, using parameterType instead", targetContentType);
                setContentTypeByParameterType(headers, parameterType);
            }
        } else {
            // parameterType을 기반으로 Content-Type 설정
            setContentTypeByParameterType(headers, parameterType);
        }
    }

    /**
     * 파라미터 타입에 따른 Content-Type 설정
     */
    private void setContentTypeByParameterType(HttpHeaders headers, String parameterType) {
        String upperParamType = parameterType.toUpperCase();
        switch (upperParamType) {
            case "FORM":
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                break;
            case "XML":
                headers.setContentType(MediaType.APPLICATION_XML);
                break;
            case "TEXT":
                headers.setContentType(MediaType.TEXT_PLAIN);
                break;
            case "HTML":
                headers.setContentType(MediaType.TEXT_HTML);
                break;
            default:
                headers.setContentType(MediaType.APPLICATION_JSON);
                break;
        }
        log.debug("Set Content-Type by parameter type: {}", headers.getContentType());
    }

    /**
     * 브라우저와 유사한 헤더 설정
     */
    private void setBrowserLikeHeaders(HttpHeaders headers) {
        // User-Agent가 없으면 설정
        if (!headers.containsKey("User-Agent")) {
            headers.add("User-Agent",
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
        }

        // Accept 헤더
        if (!headers.containsKey("Accept")) {
            headers.add("Accept", "*/*");
        }

        // 기타 브라우저 헤더들
        headers.add("Accept-Language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7");
        headers.add("Accept-Encoding", "gzip, deflate, br");
        headers.add("Cache-Control", "no-cache");
        headers.add("Pragma", "no-cache");
    }

    /**
     * GET/DELETE 메소드 확인
     */
    private boolean isGetOrDelete(HttpMethod method) {
        return method == HttpMethod.GET || method == HttpMethod.DELETE;
    }

    /**
     * 파라미터 존재 확인
     */
    private boolean hasParameter(String parameter) {
        return parameter != null && !parameter.trim().isEmpty();
    }

    /**
     * 쿼리 파라미터가 포함된 URL 생성
     */
    private String buildUrlWithQueryParams(String baseUrl, String parameter, String parameterType) {
        if (!hasParameter(parameter)) {
            return baseUrl;
        }

        try {
            UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(baseUrl);

            String upperParamType = parameterType.toUpperCase();
            if ("FORM".equals(upperParamType)) {
                // Form 데이터를 쿼리 파라미터로 파싱
                String[] pairs = parameter.split("&");
                for (String pair : pairs) {
                    String[] keyValue = pair.split("=", 2);
                    if (keyValue.length == 2) {
                        builder.queryParam(keyValue[0], keyValue[1]);
                    }
                }
            } else {
                // JSON이나 기타 형식은 data 파라미터로
                builder.queryParam("data", URLEncoder.encode(parameter, "UTF-8"));
            }

            return builder.toUriString();
        } catch (Exception e) {
            log.warn("Failed to build URL with query params: {}", e.getMessage());
            String separator = baseUrl.contains("?") ? "&" : "?";
            return baseUrl + separator + "data=" + parameter;
        }
    }

    /**
     * 시스템 헤더 확인 (응답에서 제외할 헤더들)
     */
    private boolean isSystemHeader(String headerName) {
        String lowerName = headerName.toLowerCase();
        return lowerName.equals("transfer-encoding") ||
               lowerName.equals("connection") ||
               lowerName.equals("server") ||
               lowerName.equals("date") ||
               lowerName.startsWith("access-control-");
    }

    /**
     * 에러 응답 생성
     */
    private String createErrorResponse(String message) {
        return String.format("{\"success\":false,\"error\":\"%s\",\"timestamp\":%d}",
            message.replace("\"", "\\\""), System.currentTimeMillis());
    }

}
