package com.bb.api;

import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
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.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.JwtManager;
import com.bb.model.ApiResponse;
import com.bb.model.BetParam;
import com.bb.model.NexusApiInfo;
import com.bb.model.PageFormVO;
import com.bb.model.Site;
import com.bb.model.TransSearchVO;
import com.bb.service.AsyncSiteService;
import com.bb.service.PandaService;
import com.bb.service.SiteService;
import com.bb.service.ThenutService;
import com.bb.service.TransService;
import com.bb.util.AesUtil;
import com.bb.util.IPKit;
import com.bb.util.PagingUtil;
import com.bb.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;

@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping(value = "/api/v1/trans")
public class ApiTransController {

	@Autowired
	SiteService siteService;

	@Autowired
	TransService transService;

	@Autowired
	ThenutService thenutService;

	@Autowired
	PandaService pandaService;

	@Autowired
	WebClient webClient;

	@Autowired
	private final JwtManager jwtManager;

	@Autowired
	private AsyncSiteService asyncSiteService;


	@ResponseBody
	@PostMapping("/betList")
    public ApiResponse betList(@RequestHeader String token, HttpServletRequest request, @Valid @RequestBody TransSearchVO search) 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");
				return apiResponse;
			}

			// TO DO 토큰체크
			Map responseData = new HashMap();

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

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

				// 여기서 부터 로직
		    	PageFormVO pageVo = new PageFormVO();
		    	search.setSiteId(site.getSiteId());
		    	search.setSiteIdx((long) site.getSiteIdx());

		    	if(search.getOrderStr() == null || search.getOrderStr().equals("")) {
		    		search.setOrderStr("BETDATE_DESC");
		    	}

		    	if(search.getBetWinAmt() == null || search.getBetWinAmt().equals("")) {
		    		search.setBetWinAmt("0");
		    	}

		    	search.setTopId(tokenInfo.getSid());


		    	search.setStartDatePartition(StringUtils.convertToDateOnly(search.getStartDate()));
		    	search.setEndDatePartition(StringUtils.convertToDateOnly(search.getEndDate()));

		    	log.info(LOG_PREFIX+ "TransSearchVO::"+search.toString());

		    	if(search.getBetWinAmt().equals("0") && search.getOldYN().equals("N") && search.getWithBottomYN().equals("N")
		    				&& (search.getResultInfo()==null || search.getResultInfo().equals(""))) {
		    		// Fast Search
		    		log.info(LOG_PREFIX+ "BetList Fast Search!!!");
		    		int totalCount = transService.getTransBetFastListCnt(search);
					if (totalCount != 0) {
						PageFormVO commonForm = new PageFormVO();
						commonForm.setFunction_name("goPage");
						commonForm.setPage(search.getPage());
						commonForm.setCount_per_page(20);
						if(search.getCount_per_list()==0) {
							commonForm.setCount_per_list(30);
						} else {
							commonForm.setCount_per_list(search.getCount_per_list());
						}
						commonForm.setTatal_list_count(totalCount);
						pageVo = PagingUtil.setPageUtil(commonForm);
						search.setLimit(pageVo.getLimit());
						search.setOffset(pageVo.getOffset());
						search.setTatal_list_count(totalCount);

						responseData.put("pageInfo", pageVo);
					}

					List<HashMap<String, String>> fastList = transService.getTransBetFastList(search);
					List<HashMap<String, Object>> list = new ArrayList<HashMap<String,Object>>();
					for(HashMap<String, String> item : fastList) {
						String refId = item.get("betId");
						search.setRefId(refId);
						HashMap<String, Object> betItem = transService.getBetListItem(search);
						list.add(betItem);
					}

					responseData.put("list", list);
					HashMap<String, String> listSum = transService.getTransBetFastListSum(search);
					responseData.put("listSum", listSum);

					log.info(LOG_PREFIX+ "BetList Total count::"+totalCount);
					log.info(LOG_PREFIX+ "BetList list count::"+list.size());
					log.info(LOG_PREFIX+ "BetList listSum::"+listSum);
		    	} else {
		    		// ORG Search
		    		log.info(LOG_PREFIX+ "BetList ORG Search!!!");
		    		int totalCount = transService.getTransBetListCnt(search);
					if (totalCount != 0) {
						PageFormVO commonForm = new PageFormVO();
						commonForm.setFunction_name("goPage");
						commonForm.setPage(search.getPage());
						commonForm.setCount_per_page(20);
						if(search.getCount_per_list()==0) {
							commonForm.setCount_per_list(30);
						} else {
							commonForm.setCount_per_list(search.getCount_per_list());
						}
						commonForm.setTatal_list_count(totalCount);
						pageVo = PagingUtil.setPageUtil(commonForm);
						search.setLimit(pageVo.getLimit());
						search.setOffset(pageVo.getOffset());
						search.setTatal_list_count(totalCount);

						responseData.put("pageInfo", pageVo);
					}

					List<HashMap<String, Object>> list = transService.getTransBetList(search);
					responseData.put("list", list);
					HashMap<String, Object> listSum = transService.getTransBetListSum(search);
					responseData.put("listSum", listSum);

					log.info(LOG_PREFIX+ "BetList Total count::"+totalCount);
					log.info(LOG_PREFIX+ "BetList list count::"+list.size());
					log.info(LOG_PREFIX+ "BetList listSum::"+listSum);
		    	}

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

			apiResponse.setData(responseData);
			apiResponse.success();

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

		return apiResponse;
	}


	@ResponseBody
	@PostMapping(value="/betDetail")
	public ApiResponse betDetail(@RequestHeader String token, HttpServletRequest request, @RequestBody BetParam param) throws Exception {
		ApiResponse apiResponse = new ApiResponse();
		JSONObject saveObj = null;
		JSONArray saveObjs = null;
		boolean isSaveEvoDetail = false;

		try {
			// apiKey 체크
			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");
				return apiResponse;
			}

			// TO DO 토큰체크
			Map responseData = new HashMap();

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

				// 여기서 부터 로직
		    	log.info(LOG_PREFIX+ "BetParam::"+param.toString());
		    	responseData.put("betId", param.getBetId());
				String dType = "detail_0";

				HashMap betInfo = transService.getBetInfoByBetId(param);

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

				log.info(LOG_PREFIX+ "betINfp {}", 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";
						responseData.put("dType", dType);

						String detailURL = transService.getPragmaticDetails(LOG_PREFIX, betInfo);
						if(detailURL != null && !detailURL.equals("")) {
							HashMap url = new HashMap();
							url.put("url", detailURL);
							responseData.put("detail", url);
						} else {
							responseData.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";
							responseData.put("dType", dType);
							HashMap<String, Object> detailMap = new Gson().fromJson(jsonStr, HashMap.class);
							responseData.put("detail", detailMap);
						} else {
							isSaveEvoDetail = true;
							JSONObject dataObj = transService.getSvendorDetail(LOG_PREFIX, betInfo);
							if(dataObj == null) {
								responseData.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";
										responseData.put("dType", dType);
										String detailUrl = dataObj.getString("gameDetail");
										HashMap url = new HashMap();
										url.put("url", detailUrl);
										responseData.put("detail", url);
									} else if(detailType.equals("JSON")) {
										dType = "detail_6";
										responseData.put("dType", dType);
										JSONObject detailObj = dataObj.getJSONObject("gameDetail");
										saveObj = detailObj;
										HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
										responseData.put("detail", detailMap);
									} else {
										apiResponse.put("detail", "betId is not exist");
									}
								} else {
									responseData.put("detail", "betId is not exist");
								}
							}
						}
					} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==17 && "tower".equals(betInfo.get("vendorCode").toString())) {
						dType = "detail_2";
						responseData.put("dType", dType);
						String detailUrl =
								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+"  {}" ,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", 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+" {}" ,StringUtils.extractLink(responseString));

							HashMap url = new HashMap();
							url.put("url", StringUtils.extractLink(responseString));
							responseData.put("detail", url);
						} else {
							responseData.put("detail", "betId is not exist");
						}

					} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==83 && "splus".equals(betInfo.get("vendorCode").toString())) {
						log.info(LOG_PREFIX+ "========== BetDetail::Splus ==========");
						dType = "detail_1";
						responseData.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);
						}

						responseData.put("detail", d);

					} else if("nexus".equals(betInfo.get("vendorCode").toString()) || "ace2".equals(betInfo.get("vendorCode").toString())) {
						log.info(LOG_PREFIX+ "========== BetDetail::Hlink or Xtreem ==========");

						String jsonStr = siteService.getOrgDetailData(param);
						log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
						if(jsonStr != null && !"".equals(jsonStr) && !"{}".equals(jsonStr)) {
							dType = "detail_3";
							responseData.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);
							responseData.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";
								responseData.put("dType", dType);
								JSONObject detailObj = transService.getNexusBetDetail(LOG_PREFIX, nexusApiInfo, trxId);
								int resultCode = detailObj.getInt("code");
								if(resultCode == 0) {
									JSONArray transactions = detailObj.getJSONArray("transactions");
									if(transactions.length() > 0) {
										saveObjs = transactions;
										List<HashMap<String, Object>> detailMap = new Gson().fromJson(transactions.toString(), List.class);
										responseData.put("detail", detailMap);
									} else {
										responseData.put("detail", "betId is not exist");
									}
								} else {
									responseData.put("detail", "betId is not exist");
								}
							} else {
								dType = "detail_2";
								responseData.put("dType", dType);
								JSONObject detailObj = transService.getNexusBetDetailUrl(LOG_PREFIX, nexusApiInfo, trxId);
								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"));
									responseData.put("detail", url);
								} else {
									responseData.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";
						responseData.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);
							responseData.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 WebClientResponseException : " + 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::Play 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);
								responseData.put("detail", detailMap);
							} else {
								responseData.put("detail", "betId is not exist");
							}
						}
					} else if("grand".equals(betInfo.get("vendorCode").toString())) {
						dType = "detail_2";
						responseData.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"));
								responseData.put("detail", url);
							} else {
								responseData.put("detail", "betId is not exist");
							}
						} else {
							responseData.put("detail", "betId is not exist");
						}
					} else if("pracp".equals(betInfo.get("vendorCode").toString())) {
						dType = "detail_2";
						responseData.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 = transService.getPracpDetail(LOG_PREFIX, apiInfo);
						log.info(LOG_PREFIX+ "dataObj : " + dataObj.toString());
						if(dataObj != null) {
							HashMap url = new HashMap();
							url.put("url", dataObj.getString("url"));
							responseData.put("detail", url);
						} else {
							responseData.put("detail", "betId is not exist");
						}
					} else if("tplus".equals(betInfo.get("vendorCode").toString())) {
						dType = "detail_2";
						responseData.put("dType", dType);

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

						HashMap<String, String> apiInfo = new HashMap<>();
						apiInfo.put("apiBaseUrl", apiBaseUrl);
						apiInfo.put("apiAuthKey", apiAuthKey);
						apiInfo.put("pairTransactionKey", pairTransactionKey);

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

						String detailURL = transService.getTplusDetails(LOG_PREFIX, apiInfo);
						if(detailURL != null && !detailURL.equals("")) {
							HashMap url = new HashMap();
							url.put("url", detailURL);
							responseData.put("detail", url);
						} else {
							responseData.put("detail", "betId is not exist");
						}
					} else if("onix".equals(betInfo.get("vendorCode").toString())) {
						log.info(LOG_PREFIX+ "========== BetDetail::ONIX ==========");
						dType = "detail_8";
						responseData.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);
							responseData.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);
											responseData.put("detail", detailMap);
											saveObj = detailObj;
										} else {
											responseData.put("detail", "betId is not exist");
										}
									} else {
										responseData.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 {
									responseData.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";
				    		responseData.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);
				    		responseData.put("detail", detailMap);
				    		apiResponse.setData(responseData);
				    		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";
								responseData.put("dType", dType);
								String detailUrl = jsonStr;
								HashMap<String, String> url = new HashMap<>();
								url.put("url", detailUrl);
								responseData.put("detail", url);
							} else if(detailType.equals("JSON")) {
								dType = "detail_3";
								responseData.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);
								responseData.put("detail", detailMap);
							} else {
								responseData.put("dType", dType);
								responseData.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";
								responseData.put("dType", dType);
								JSONObject detailObj = thenutService.getBetDetail(LOG_PREFIX, apiInfo);
								if(detailObj == null) {
									responseData.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);
									responseData.put("detail", detailMap);
									saveObj = detailObj;
								}
							} else {
								dType = "detail_2";
								responseData.put("dType", dType);
								String detailUrl = thenutService.getBetDetailUrl(LOG_PREFIX, apiInfo);
								if(detailUrl == null) {
									responseData.put("detail", "betId is not exist");
								} else {
									log.info(LOG_PREFIX+ "detailUrl::"+detailUrl);
									HashMap<String, String> url = new HashMap<>();
									url.put("url", detailUrl);
									responseData.put("detail", url);
								}
							}
						}
					} else if("panda".equals(betInfo.get("vendorCode").toString())) {
						dType = "detail_2";
						responseData.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) {
							responseData.put("detail", "betId is not exist");
						} else {
							log.info(LOG_PREFIX+ "detailUrl::"+detailUrl);
							HashMap<String, String> url = new HashMap<>();
							url.put("url", detailUrl);
							responseData.put("detail", url);
						}
					} else {
						responseData.put("dType", dType);
						responseData.put("detail", "betId is not exist");
					}
				} else {
					responseData.put("dType", dType);
					responseData.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");
				}

			} catch(Exception e) {
				e.printStackTrace();
				log.error("#-API::TRANS::betDetail::Exception1::" +e.toString());
				log.error("#-API::TRANS::betDetail::Exception1::" +e.getMessage());
				apiResponse.setResultCode("99995");
				apiResponse.setResultMessage("token is no valid");
				return apiResponse;
			}

			apiResponse.setData(responseData);
			apiResponse.success();

		} catch (Exception e) {
			e.printStackTrace();
			log.error("#-API::TRANS::betDetail::Exception2::" +e.toString());
			log.error("#-API::TRANS::betDetail::Exception2::" +e.getMessage());
			apiResponse.fail();
		}

		return apiResponse;
	}


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

		try {
			// apiKey 체크
			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");
				return apiResponse;
			}

			// TO DO 토큰체크
			Map responseData = new HashMap();

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

				// 여기서 부터 로직
		    	log.info(LOG_PREFIX+ "BetParam::"+param.toString());
		    	responseData.put("betId", param.getBetId());

		    	HashMap betInfo = transService.getBetInfoByBetId(param);

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

				log.info(LOG_PREFIX+ "betINfp {}", betInfo);
				if(betInfo != null) {
					if("nexus".equals(betInfo.get("vendorCode").toString()) || "xtreem".equals(betInfo.get("vendorCode").toString())) {
						log.info(LOG_PREFIX+ "========== BetDetail::Hlink or Xtreem ==========");

						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())) {
							JSONObject responseObj = transService.getNexusBetDetail(LOG_PREFIX, nexusApiInfo, trxId);
							log.info(LOG_PREFIX+ "responseObj {}", responseObj);

							int resultCode = responseObj.getInt("code");
							if(resultCode == 0) {
								JSONArray transactions = responseObj.getJSONArray("transactions");

								// 금애기치환
								JSONObject transaction = transactions.getJSONObject(0);
								JSONObject detailObj = transaction.getJSONObject("detail");

								if(detailObj.isNull("result")) {
									// resultObj가 존재하지 않으면 상세가 없다는 뜻.
									HashMap detailMap = new HashMap<>();
									detailMap.put("refId", param.getBetId());
									int result = transService.deleteEvoDetailNull(detailMap);
									log.info(LOG_PREFIX+ "deleteEvoDetailNull result::" + result + ", refId::"+param.getBetId());
								} else {
									HashMap detailMap = new HashMap<>();
									detailMap.put("refId", param.getBetId());
									detailMap.put("orgDetail", detailObj.toString());
									detailMap.put("parDetail", null);
									try {
										int result = siteService.insertEvoDetail(detailMap);
										log.info(LOG_PREFIX+ "insertEvoDetail result::" + result + ", refId::"+param.getBetId());
										// TODO : 베팅상세 파싱 치환 작업
										if(result > 0) {
											changePaseDetail(LOG_PREFIX, param.getBetId(), detailObj);
										}

										result = transService.deleteEvoDetailNull(detailMap);
										log.info(LOG_PREFIX+ "deleteEvoDetailNull result::" + result + ", refId::"+param.getBetId());
									} catch(DataIntegrityViolationException de) {
										log.error(LOG_PREFIX+ "Duplicate refId::"+param.getBetId());
									}
								}

							} else {
								responseData.put("detail", "betId is not exist");
							}
						}
					}
				}
			} catch(Exception e) {
				e.printStackTrace();
				log.error("#-nexusDetail::Exception1::" +e.toString());
				log.error("#-nexusDetail::Exception1::" +e.getMessage());
				apiResponse.setResultCode("99995");
				apiResponse.setResultMessage("token is no valid");
				return apiResponse;
			}

			apiResponse.setData(responseData);
			apiResponse.success();

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

		return apiResponse;
	}


	@ResponseBody
	@GetMapping("/nexusDetails")
    public ApiResponse nexusDetails(HttpServletRequest request, @RequestHeader String token, @RequestParam String startDate, @RequestParam String endDate) 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");
				return apiResponse;
			}
			// TO DO 토큰체크
			Map responseData = new HashMap();

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

				if(startDate == null || "".equals(startDate)) {
					log.error(LOG_PREFIX+ "startDate is not null or empty or blank");
					apiResponse.setResultCode("-1");
					apiResponse.setResultMessage("startDate is not null or empty or blank");
					return apiResponse;
				}

				if(endDate == null || "".equals(endDate)) {
					log.error(LOG_PREFIX+ "endDate is not null or empty or blank");
					apiResponse.setResultCode("-2");
					apiResponse.setResultMessage("endDate is not null or empty or blank");
					return apiResponse;
				}

				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
				// 문자열 -> Date
				Date date1 = sdf.parse(startDate);
				Date date2 = sdf.parse(endDate);

				// Date -> 밀리세컨즈
				long timeMil1 = date1.getTime();
				long timeMil2 = date2.getTime();

				// 비교
				long diff = timeMil2 - timeMil1;

				long diffMin = diff / (1000 * 60);

				if(diffMin > 60) {
					log.error(LOG_PREFIX+ "End_Start_60분_초과");
					apiResponse.setResultCode("-3");
					apiResponse.setResultMessage("End_Start_60분_초과");
					return apiResponse;
				}

				startDate = startDate+".000";
				endDate = endDate+".000";
				responseData.put("BF_START_DATE", startDate);
				responseData.put("BF_END_DATE", endDate);

				startDate = makeDateStrForUTC(true, makeEdate(startDate));
				endDate = makeDateStrForUTC(true, makeEdate(endDate));
				String nextReqSDate = endDate;
				String resultMsg = "";
				int totalCnt = 0;
				int bfDataCnt = 0;
				int afDataCnt = 0;
				int nullDataCnt = 0;
				int duplDataCnt = 0;
				log.info(LOG_PREFIX+ "Start 시각 : " + startDate);
				log.info(LOG_PREFIX+ "End   시각 : " + endDate);
				responseData.put("AF_START_DATE", startDate);
				responseData.put("AF_END_DATE", endDate);

				// 여기서 부터 로직
				HashMap<String, String> apiInfo = transService.getApiInfo("nexus");
				// asyncTransService.getNexusEvoDetail(LOG_PREFIX, apiInfo, startDate, endDate);

				log.info(LOG_PREFIX+ "BATCH_INFO::");
				log.info(LOG_PREFIX+ "BATCH_INFO::");
				log.info(LOG_PREFIX+ "BATCH_INFO:: ============================== S T A R T ==============================");
				log.info(LOG_PREFIX+ "BATCH_INFO::");
				log.info(LOG_PREFIX+ "BATCH_INFO::apiInfo : " + apiInfo.toString());

				JSONObject bodyObj = new JSONObject();
				bodyObj.put("vendorKey", "evolution-n");
				bodyObj.put("sdate", startDate);
				bodyObj.put("eDate", endDate);
				bodyObj.put("username", "");
				bodyObj.put("limit", 2000);
				String bodyJson = bodyObj.toString();
				log.info(LOG_PREFIX+ "BATCH_INFO::bodyJson :"+bodyJson);

				HttpHeaders headers = getHeader(LOG_PREFIX, apiInfo, bodyJson);
				String apiUrl = apiInfo.get("apiBaseUrl").toString() + "/transaction";
				UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(apiInfo.get("apiBaseUrl").toString() + "/transaction");
				MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
				params.add("vendorKey", "evolution-n");
				params.add("sdate", startDate);
				params.add("eDate", endDate);
				params.add("username", "");
				params.add("limit", 2000);
				log.info(LOG_PREFIX+ "BATCH_INFO::apiUrl :"+apiUrl);
				ResponseEntity<String> response = null;
				try {
					response = webClient.post()
							.uri(uriBuilder.toUriString())
							.headers(h -> h.addAll(headers))
							.bodyValue(params)
							.retrieve()
							.toEntity(String.class)
							.block();
				} catch(WebClientResponseException e) {
					log.error(LOG_PREFIX+ "BATCH_INFO::WebClientResponseException : " + e.getMessage());
					log.error(LOG_PREFIX+ "BATCH_INFO::status code : " + e.getStatusCode().value());
					log.error(LOG_PREFIX+ "BATCH_INFO::response body : " + e.getResponseBodyAsString());
					throw new ApiException("NX001", e.getResponseBodyAsString());
				} catch (WebClientRequestException rae) {
					if(rae.getCause() instanceof io.netty.channel.ConnectTimeoutException) {
						log.error(LOG_PREFIX+ "BATCH_INFO::ConnectTimeoutException::"+rae.getMessage());
					}
					if(rae.getCause() instanceof io.netty.handler.timeout.ReadTimeoutException) {
						log.error(LOG_PREFIX+ "BATCH_INFO::SocketTimeoutException::"+rae.getMessage());
					}
					if(rae.getCause() instanceof InterruptedException) {
						log.error(LOG_PREFIX+ "BATCH_INFO::InterruptedException::"+rae.getMessage());
					}
					throw new ApiException("NX002", rae.getMessage());
				} catch (Exception e) {
					log.error(LOG_PREFIX+ "BATCH_INFO::Exception : " + e.getMessage());
					throw new ApiException("NX003", e.getMessage());
				}
				if(response.getStatusCode() == HttpStatus.OK) {
					JSONObject responseObj = new JSONObject(response.getBody());
					JSONArray transactionsArr = responseObj.getJSONArray("transactions");
					log.info(LOG_PREFIX+ "BATCH_INFO::transactionsArr.length() : " + transactionsArr.length());

					totalCnt = transactionsArr.length();

					for(int i=0; i<transactionsArr.length(); i++) {
						JSONObject transactionObj = transactionsArr.getJSONObject(i);
						String refId = transactionObj.getString("refId");
						String type = transactionObj.getString("type");
						JSONObject detailObj = transactionObj.getJSONObject("detail");
						//log.info(LOG_PREFIX+ "detailObj : " + detailObj);
						String createdAt = transactionObj.getString("createdAt");
						String utcCreatedAt = transactionObj.getString("utcCreatedAt");
						//log.info(LOG_PREFIX+ "refId:" + refId + ", utcCreatedAt:" + utcCreatedAt + ", createdAt:" + createdAt);

						if(type.equals("turn_bet")) {
							nextReqSDate = createdAt;
							bfDataCnt++;
							if(detailObj.isNull("result")) {
								// resultObj가 존재하지 않으면 상세가 없다는 뜻.
								HashMap detailMap = new HashMap<>();
								detailMap.put("refId", refId);
								int result = transService.deleteEvoDetailNull(detailMap);
								log.info(LOG_PREFIX+ "deleteEvoDetailNull result::" + result + ", refId::"+refId);

								nullDataCnt++;
							} else {
								HashMap detailMap = new HashMap<>();
								detailMap.put("refId", refId);
								detailMap.put("orgDetail", detailObj.toString());
								detailMap.put("parDetail", null);
								try {
									int result = siteService.insertEvoDetail(detailMap);
									log.info(LOG_PREFIX+ "insertEvoDetail result::" + result + ", refId::"+refId);
									// TODO : 베팅상세 파싱 치환 작업
									if(result > 0) {
										changePaseDetail(LOG_PREFIX, refId, detailObj);
									}

									result = transService.deleteEvoDetailNull(detailMap);
									log.info(LOG_PREFIX+ "deleteEvoDetailNull result::" + result + ", refId::"+refId);

									afDataCnt++;
								} catch(DataIntegrityViolationException de) {
									//log.error(LOG_PREFIX+ "Duplicate refId::"+refId);
									duplDataCnt++;
								}
							}
						}
					}

					resultMsg = "Success";

				} else {
					resultMsg = "Request Fail::";
					log.error(LOG_PREFIX+ "Fail response::"+response.toString());
				}

				responseData.put("resultMsg", resultMsg);
				responseData.put("totalCnt", totalCnt);
				responseData.put("bfDataCnt", bfDataCnt);
				responseData.put("afDataCnt", afDataCnt);
				responseData.put("nullDataCnt", nullDataCnt);
				responseData.put("duplDataCnt", duplDataCnt);
				responseData.put("nextStartDate", nextReqSDate);

				log.info(LOG_PREFIX+ "BATCH_INFO::");
				log.info(LOG_PREFIX+ "BATCH_INFO:: ==============================  E  N  D  ==============================");
				log.info(LOG_PREFIX+ "BATCH_INFO::");
				log.info(LOG_PREFIX+ "BATCH_INFO::");

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

			apiResponse.setData(responseData);
			apiResponse.success();

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

		return apiResponse;
	}


	private void changePaseDetail(String LOG_PREFIX, String refId, JSONObject detailObj) {

	}


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

		try {
			// apiKey 체크
			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");
				return apiResponse;
			}

			// TO DO 토큰체크
			Map responseData = new HashMap();

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

				// 여기서 부터 로직
		    	log.info(LOG_PREFIX+ "BetParam::"+param.toString());
		    	responseData.put("betId", param.getBetId());
				String betDetail = siteService.getBetInfo(param);
				String dType = "detail_0";

				if("".equals(betDetail) || betDetail == null) {
					HashMap betInfo = transService.getBetInfoByBetId(param);

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

					log.info(LOG_PREFIX+ "betINfp {}", betInfo);
					if(betInfo != null) {
						String category = betInfo.get("vendorCetegory").toString();
						log.info(LOG_PREFIX+ "Game category : " + category);

						if("svendor".equals(betInfo.get("vendorCode").toString())) {
							String jsonStr = siteService.getOrgDetailData(param);
							log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
							if(jsonStr != null && !"".equals(jsonStr)) {
								dType = "detail_6";
								responseData.put("dType", dType);
								HashMap<String, Object> detailMap = new Gson().fromJson(jsonStr, HashMap.class);
								responseData.put("detail", detailMap);
							} else {
								JSONObject dataObj = transService.getSvendorDetail(LOG_PREFIX, betInfo);
								if(dataObj == null) {
									responseData.put("detail", "betId is not exist");
								} else {
									if(dataObj.has("gameDetail") && !dataObj.isNull("gameDetail")) {
										String gameType = dataObj.getString("game_type");
										if(gameType.equals("slot")) {
											dType = "detail_2";
											responseData.put("dType", dType);
											String detailUrl = dataObj.getString("gameDetail");
											responseData.put("detail", detailUrl);
										} else {
											dType = "detail_6";
											responseData.put("dType", dType);
											JSONObject detailObj = dataObj.getJSONObject("gameDetail");
											JSONObject parseJsonObj = null;
											detailObj.put("parseData", parseJsonObj);
											HashMap<String, Object> detailMap = new Gson().fromJson(detailObj.toString(), HashMap.class);
											responseData.put("detail", detailMap);
										}
									} else {
										responseData.put("detail", "betId is not exist");
									}
								}
							}
						} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==17 && "tower".equals(betInfo.get("vendorCode").toString())) {
							dType = "detail_2";
							responseData.put("dType", dType);
							String detailUrl =
									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+"  {}" ,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", 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+" {}" ,StringUtils.extractLink(responseString));

								HashMap url = new HashMap();
								url.put("url", StringUtils.extractLink(responseString));

								responseData.put("detail", url);
							} else {
								responseData.put("detail", "betId is not exist");
							}

						} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==83 && "splus".equals(betInfo.get("vendorCode").toString())) {
							log.info(LOG_PREFIX+ "========== BetDetail::Splus ==========");
							dType = "detail_1";
							responseData.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);
							}

							responseData.put("detail", d);

						} else if(Integer.parseInt(betInfo.get("vendorIdx").toString())==17 && "gsoft".equals(betInfo.get("vendorCode").toString())) {
							log.info(LOG_PREFIX+ "========== BetDetail::Gsoft ==========");
							dType = "detail_2";
							responseData.put("dType", dType);

							String betId = betInfo.get("refId").toString();
							String roundId = transService.getGsoftRoundIdByBetId(betId);

							OkHttpClient client = new OkHttpClient().newBuilder().build();
														okhttp3.Request requestok2 = new okhttp3.Request.Builder()
														.url("https://api.iblive.net/api/apigamedetail.aspx?gtype=sxprg&roundId="+roundId)
														.method("GET", null)//
														.addHeader("User-agent", "Mozilla")
														.addHeader("Content-Type", "application/json")
														.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);

								org.codehaus.jettison.json.JSONObject resJson  = new org.codehaus.jettison.json.JSONObject(responseString);

								HashMap url = new HashMap();
								url.put("url", resJson.getString("link"));

								responseData.put("detail", url);
							}
						} else if("nexus".equals(betInfo.get("vendorCode").toString())) {
							log.info(LOG_PREFIX+ "========== BetDetail::Hlink or Xtreem ==========");

							String jsonStr = siteService.getOrgDetailData(param);
							log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
							if(jsonStr != null && !"".equals(jsonStr)) {
								dType = "detail_3";
								responseData.put("dType", dType);
								HashMap<String, Object> detailMap = new Gson().fromJson(jsonStr, HashMap.class);
								responseData.put("detail", detailMap);
							} else {
								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";
									responseData.put("dType", dType);
									JSONObject detailObj = transService.getNexusBetDetail(LOG_PREFIX, nexusApiInfo, trxId);
									log.info(LOG_PREFIX+ "detailObj {}", detailObj);

									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);
											responseData.put("detail", detailMap);
										} else {
											responseData.put("detail", "betId is not exist");
										}
									} else {
										responseData.put("detail", "betId is not exist");
									}
								} else {
									dType = "detail_2";
									responseData.put("dType", dType);
									JSONObject detailObj = transService.getNexusBetDetailUrl(LOG_PREFIX, nexusApiInfo, trxId);
									log.info(LOG_PREFIX+ "detailObj::"+detailObj.toString());
									int resultCode = detailObj.getInt("code");
									if(resultCode == 0) {
										HashMap<String, String> detailMap = new HashMap<>();
										detailMap.put("url", detailObj.getString("url"));
										responseData.put("detail", detailMap);
									} else {
										responseData.put("detail", "betId is not exist");
									}
								}
							}
						} else if("honor".equals(betInfo.get("vendorCode").toString()) || "xhlnk".equals(betInfo.get("vendorCode").toString())) {
							log.info(LOG_PREFIX+ "========== BetDetail::Honor ==========");
							dType = "detail_5";
							responseData.put("dType", dType);
							String jsonStr = siteService.getOrgDetailData(param);
							log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
							if(jsonStr != null && !"".equals(jsonStr)) {
								HashMap<String, Object> detailMap = new Gson().fromJson(jsonStr, HashMap.class);
								responseData.put("detail", detailMap);
							} else {
								responseData.put("detail", "betId is not exist");
							}
						} else if("prime".equals(betInfo.get("vendorCode").toString())) {
							log.info(LOG_PREFIX+ "========== BetDetail::PRIME ==========");
							dType = "detail_9";
							responseData.put("dType", dType);

							String jsonStr = siteService.getOrgDetailData(param);
							log.info(LOG_PREFIX+ "jsonStr : " + jsonStr);
							if(jsonStr != null && !"".equals(jsonStr)) {
								HashMap<String, Object> detailMap = new Gson().fromJson(jsonStr, HashMap.class);
								responseData.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 WebClientResponseException : " + 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) {
									log.info(LOG_PREFIX+ "PRIME::Play response : " + response.getBody());
									resJson = new JSONObject(response.getBody());
									JSONObject dataObj = resJson;
									HashMap<String, Object> detailMap = new Gson().fromJson(dataObj.toString(), HashMap.class);
									responseData.put("detail", detailMap);
								} else {
									responseData.put("detail", "betId is not exist");
								}
							}
						} else {
							responseData.put("dType", dType);
							responseData.put("detail", "betId is not exist");
						}
					} else {
						responseData.put("dType", dType);
						responseData.put("detail", "betId is not exist");
					}
				} else {
					responseData.put("dType", dType);
					log.info(LOG_PREFIX+ "BetDetail::"+betDetail);
					HashMap<String, Object> detailMap = new Gson().fromJson(betDetail, HashMap.class);
					responseData.put("detail", detailMap);
				}

			} catch(Exception e) {
				e.printStackTrace();
				log.error("#-API::TRANS::betDetail::Exception1::" +e.toString());
				log.error("#-API::TRANS::betDetail::Exception1::" +e.getMessage());
				apiResponse.setResultCode("99995");
				apiResponse.setResultMessage("token is no valid");
				return apiResponse;
			}

			apiResponse.setData(responseData);
			apiResponse.success();

		} catch (Exception e) {
			e.printStackTrace();
			log.error("#-API::TRANS::betDetail::Exception2::" +e.toString());
			log.error("#-API::TRANS::betDetail::Exception2::" +e.getMessage());
			apiResponse.fail();
		}

		return apiResponse;
	}



	private HttpHeaders getHeader(String LOG_PREFIX, HashMap<String, String> apiInfo, String bodyJson) {
		LOG_PREFIX = LOG_PREFIX + "getHeader::";
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
		headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
		headers.setAcceptCharset(Arrays.asList(Charset.forName("UTF-8")));

		String hashCode = getHashCode(LOG_PREFIX, bodyJson, apiInfo.get("apiSecretKey").toString());
		headers.set("hash", hashCode);
		headers.set("agent", apiInfo.get("apiAgentId").toString());
		log.info(LOG_PREFIX+ "BATCH_INFO::HttpHeaders::"+headers.toString());

		return headers;
	}

	private String getHashCode(String LOG_PREFIX, String body, String secretKey) {
		String hashCode = "";
		LOG_PREFIX = LOG_PREFIX + "BATCH_INFO::getHashCode::";
		try {
			// Json String Body + Secret Key
			String result = body + secretKey;
			// String result = "{\"username\":\"057007mptest01\",\"nickname\":\"007mptest01\",\"siteUsername\":\"057007mptest01\"}";
			log.info(LOG_PREFIX+ "BATCH_INFO::Body + Secret Key::"+result);
			// SHA-256 Hash make
			MessageDigest digestObj = MessageDigest.getInstance("SHA-256");
			byte[] hash_data = digestObj.digest(result.getBytes("UTF-8"));

			// Base64 encoding
			hashCode = Base64.getEncoder().encodeToString(hash_data);
			log.info(LOG_PREFIX+ "BATCH_INFO::hashCode::"+hashCode);
		} catch(Exception e) {
			log.error(LOG_PREFIX+ "BATCH_INFO::#-NexusService::getHashCode::Exception::"+e.getMessage());
		}

		return hashCode;
	}

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

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

}
