package com.bb.service;

import java.time.Duration;
import java.util.HashMap;

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.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientRequestException;

import com.bb.dao.CallBackDao;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class AsyncSiteService {
	
	@Autowired
	SiteService siteService;
	
	@Autowired
	WebClient webClient;
	
	@Autowired
	CallBackDao callBackDao;

	@Async
	public void insertEvoDetailProc(String LOG_PREFIX, String betId, JSONObject saveObj, JSONArray saveObjs) {
		try {
			HashMap detailMap = new HashMap<>();
			detailMap.put("refId", betId);
			if(saveObj != null) {
				detailMap.put("orgDetail", saveObj.toString());
			} else {
				detailMap.put("orgDetail", saveObjs.toString());
			}
			detailMap.put("parDetail", null);
			log.info(LOG_PREFIX+ "insertEvoDetailProc START");
			int result = siteService.insertEvoDetail(detailMap);
			log.info(LOG_PREFIX+ "insertEvoDetailProc END result::" + result);
			
		} catch(DataIntegrityViolationException de) {
			log.error(LOG_PREFIX+ "Duplicate refId");
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "[Exception]"+e.toString());
		}
		
	}

	@Async
	public void asyncBalance(String LOG_PREFIX, HashMap siteApiInfo, JSONObject param) {
		HttpHeaders headers = new HttpHeaders();
		headers.set("Connection", "close");
		headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
		headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
		headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
				+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");

		final String baseCbUrl = siteApiInfo.get("siteCbUrl").toString().trim();
		log.info(LOG_PREFIX+ "Callback URL: " + baseCbUrl + "/balance");
		log.info(LOG_PREFIX+ "Body: " + param.toString());
		
		try {
			param.put("requestAt", Long.toString(System.currentTimeMillis()));
			
			webClient.post()
				.uri(baseCbUrl + "/balance")
	            .headers(h -> h.addAll(headers))
	            .bodyValue(param.toString())
	            .exchangeToMono(resp -> resp.toEntity(String.class))
	            .timeout(Duration.ofSeconds(2))
	            .doOnSuccess(response -> {
	                if (response != null) {
	                	log.info("{}Response HTTP Status: {}", LOG_PREFIX, response.getStatusCode().value());
	                    log.info("{}Response Body: {}", LOG_PREFIX, response.getBody());
	                }
	            })
	            .doOnError(e -> {
	                if (e instanceof WebClientRequestException wcre) {
	                    Throwable cause = wcre.getCause();

	                    if (hasCause(cause, io.netty.channel.ConnectTimeoutException.class)
	                            || hasCause(cause, java.net.ConnectException.class)) {

	                        log.error("{}CONNECT_TIMEOUT", LOG_PREFIX);
	                        log.error("{}{}", LOG_PREFIX, wcre.toString());

	                    } else if (hasCause(cause, io.netty.handler.timeout.ReadTimeoutException.class)
	                            || hasCause(cause, java.net.SocketTimeoutException.class)) {

	                        log.error("{}READ_TIMEOUT", LOG_PREFIX);
	                        log.error("{}{}", LOG_PREFIX, wcre.toString());

	                    } else {
	                        log.error("{}WebClientRequestException", LOG_PREFIX);
	                        log.error("{}{}", LOG_PREFIX, wcre.toString());
	                    }

	                } else {
	                    log.error("{}Exception: {}", LOG_PREFIX, e.toString());
	                }
	            })
	            // ❗ 실제 비동기 실행 트리거
	            .subscribe();
			
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "Exception e: {}", e.toString());
		}
	}

	@Async
	public void asyncChangeBalance(String LOG_PREFIX, HashMap<String, String> siteApiInfo, JSONObject data) {
		String tranId = "";
		try {
			tranId = data.getString("tranId");
			log.info(LOG_PREFIX+ "data.has(\"isData\"): " + data.has("isData"));
			log.info(LOG_PREFIX+ "data.has(\"betKey\"): " + data.has("betKey"));
			if(!data.has("isData")) {
				data.put("isData", "N");
			}
			if(!data.has("betKey")) {
				data.put("betKey", tranId);
			}
		} catch(Exception e) {}
		
		HttpHeaders header = new HttpHeaders();
		header.add("Content-Type", "application/json");
		header.add("Accept", "application/json");
		header.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
				+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
		header.set("Connection", "close");
		
		final String baseCbUrl = siteApiInfo.get("siteCbUrl").toString().trim();
		log.info(LOG_PREFIX+ "Callback URL: " + baseCbUrl + "/changebalance");
		log.info(LOG_PREFIX+ "Body: " + data.toString());

		try {
			webClient.post()
			.uri(baseCbUrl + "/changebalance")
            .headers(h -> h.addAll(header))
            .bodyValue(data.toString())
            .exchangeToMono(resp -> resp.toEntity(String.class))
            .timeout(Duration.ofSeconds(2))
            .doOnSuccess(response -> {
                if (response != null) {
                	log.info("{}Response HTTP Status: {}", LOG_PREFIX, response.getStatusCode().value());
                    log.info("{}Response Body: {}", LOG_PREFIX, response.getBody());
                }
            })
            .doOnError(e -> {
                if (e instanceof WebClientRequestException wcre) {
                    Throwable cause = wcre.getCause();

                    if (hasCause(cause, io.netty.channel.ConnectTimeoutException.class)
                            || hasCause(cause, java.net.ConnectException.class)) {

                        log.error("{}CONNECT_TIMEOUT", LOG_PREFIX);
                        log.error("{}{}", LOG_PREFIX, wcre.toString());

                    } else if (hasCause(cause, io.netty.handler.timeout.ReadTimeoutException.class)
                            || hasCause(cause, java.net.SocketTimeoutException.class)) {

                        log.error("{}READ_TIMEOUT", LOG_PREFIX);
                        log.error("{}{}", LOG_PREFIX, wcre.toString());

                    } else {
                        log.error("{}WebClientRequestException", LOG_PREFIX);
                        log.error("{}{}", LOG_PREFIX, wcre.toString());
                    }

                } else {
                    log.error("{}Exception: {}", LOG_PREFIX, e.toString());
                }
            })
            // ❗ 실제 비동기 실행 트리거
            .subscribe();
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "Exception e: {}", e.toString());
		}
	}

	@Async
	public void asyncChangeBalanceSlot(String LOG_PREFIX, HashMap<String, String> siteApiInfo,	JSONObject data) {
		String tranId = "";
		try {
			tranId = data.getString("tranId");
			log.info(LOG_PREFIX+ "data.has(\"isData\"): " + data.has("isData"));
			log.info(LOG_PREFIX+ "data.has(\"betKey\"): " + data.has("betKey"));
			if(!data.has("isData")) {
				data.put("isData", "N");
			}
			if(!data.has("betKey")) {
				data.put("betKey", tranId);
			}
		} catch(Exception e) {}
		
		HttpHeaders header = new HttpHeaders();
		header.add("Content-Type", "application/json");
		header.add("Accept", "application/json");
		header.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
				+ " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
		header.set("Connection", "close");
		
		final String baseCbUrl = siteApiInfo.get("siteCbUrl").toString().trim();
		log.info(LOG_PREFIX+ "Callback URL: " + baseCbUrl + "/changebalance/slot");
		log.info(LOG_PREFIX+ "Body: " + data.toString());

		try {
			webClient.post()
			.uri(baseCbUrl + "/changebalance/slot")
            .headers(h -> h.addAll(header))
            .bodyValue(data.toString())
            .exchangeToMono(resp -> resp.toEntity(String.class))
            .timeout(Duration.ofSeconds(2))
            .doOnSuccess(response -> {
                if (response != null) {
                	log.info("{}Response HTTP Status: {}", LOG_PREFIX, response.getStatusCode().value());
                    log.info("{}Response Body: {}", LOG_PREFIX, response.getBody());
                }
            })
            .doOnError(e -> {
                if (e instanceof WebClientRequestException wcre) {
                    Throwable cause = wcre.getCause();

                    if (hasCause(cause, io.netty.channel.ConnectTimeoutException.class)
                            || hasCause(cause, java.net.ConnectException.class)) {

                        log.error("{}CONNECT_TIMEOUT", LOG_PREFIX);
                        log.error("{}{}", LOG_PREFIX, wcre.toString());

                    } else if (hasCause(cause, io.netty.handler.timeout.ReadTimeoutException.class)
                            || hasCause(cause, java.net.SocketTimeoutException.class)) {

                        log.error("{}READ_TIMEOUT", LOG_PREFIX);
                        log.error("{}{}", LOG_PREFIX, wcre.toString());

                    } else {
                        log.error("{}WebClientRequestException", LOG_PREFIX);
                        log.error("{}{}", LOG_PREFIX, wcre.toString());
                    }

                } else {
                    log.error("{}Exception: {}", LOG_PREFIX, e.toString());
                }
            })
            // ❗ 실제 비동기 실행 트리거
            .subscribe();
		} catch (Exception e) {
			log.error(LOG_PREFIX+ "Exception e: {}", e.toString());
		}
	}
	
	@Async
	public void asyncSportsRadarProc(String LOG_PREFIX, HashMap tranParam, net.sf.json.JSONObject data) {
		log.info(LOG_PREFIX + "----- asyncSportsRadarProc START -----");
		HashMap sData = new HashMap<>();
		sData.put("tranId", tranParam.get("tranId").toString());
		sData.put("betId", tranParam.get("refId").toString());
		sData.put("jsonstr", data.toString());
		callBackDao.insertBetRadarLog(sData);
		log.info(LOG_PREFIX + "----- asyncSportsRadarProc End -----");
	}
	
	private boolean hasCause(Throwable e, Class<? extends Throwable> clazz) {
	    Throwable cause = e;
	    while (cause != null) {
	        if (clazz.isInstance(cause)) {
	            return true;
	        }
	        cause = cause.getCause();
	    }
	    return false;
	}

	@Async
	public void asyncIssueLogSave(String LOG_PREFIX, net.sf.json.JSONObject requestBody, HashMap tranParam) {
		log.info(LOG_PREFIX + "----- asyncIssueLogSave START -----");
		HashMap sData = new HashMap<>();
		sData.put("siteIdx", tranParam.get("siteIdx"));
		sData.put("memberIdx", tranParam.get("memberIdx"));
		sData.put("vendorIdx", tranParam.get("vendorIdx"));
		sData.put("tranId", tranParam.get("tranId").toString());
		sData.put("refId", tranParam.get("refId").toString());
		sData.put("callBackType", tranParam.get("tranType").toString());
		sData.put("callBackReqBody", requestBody.toString());
		sData.put("callBackResBody", "");
		siteService.insertSiteCallBackLog(sData);
		log.info(LOG_PREFIX + "----- asyncIssueLogSave End -----");
	}
}
