package com.bb.service;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;

import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;

import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.WriteTimeoutException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class CallBackTestServiceImpl implements CallBackTestService {

	@Autowired
    private WebClient webClient;
	
	private HttpHeaders getHeader() {
		HttpHeaders header = new HttpHeaders();
		header.setContentType(MediaType.APPLICATION_JSON);
		header.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
		header.setAcceptCharset(Arrays.asList(Charset.forName("UTF-8")));
		
		return header;
	}

	@Override
	public JSONObject getBalance(String LOG_PREFIX, String siteCallbackUrl, JSONObject param) {
		
		JSONObject response = new JSONObject();
		String methodLogPrefix = LOG_PREFIX + "getBalance::";
		long startTimestamp = System.currentTimeMillis();
		
		try {
			HttpHeaders header = getHeader();
			param.put("requestAt", Long.toString(startTimestamp));
			log.info(methodLogPrefix+ "request: {}", param.toString());

			String responseBody = this.webClient.post()
				.uri(siteCallbackUrl + "/balance")
				.headers(headers -> headers.addAll(header))
				.bodyValue(param.toString())
				.exchangeToMono(clientResponse -> {
					if (clientResponse.statusCode().is2xxSuccessful()) {
						return clientResponse.bodyToMono(String.class);
					} else {
						return clientResponse.bodyToMono(String.class)
							.map(body -> {
								try {
									JSONObject errorResponse = new JSONObject();
									errorResponse.put("result_code", -99);
									errorResponse.put("error_msg", "HTTP_STATUS_CODE_ERR > " + clientResponse.statusCode().value());
									return errorResponse.toString();
								} catch (JSONException e) {
									log.error(methodLogPrefix + "Error parsing error response body: {}", e.getMessage());
									return "{\"result_code\":-99, \"error_msg\":\"HTTP_STATUS_CODE_ERR and parsing error\"}";
								}
							});
					}
				})
				.onErrorResume(ConnectException.class, e -> {
					log.error(methodLogPrefix+ "ConnectException e: CONNECT_TIMEOUT (3000ms)"); // WebClient config has 3000ms connect timeout
					log.error(methodLogPrefix+ "ConnectException e: {}", e.toString());
					return monoErrorResponse("-99", "CONNECT_TIMEOUT (3000ms)");
				})
				.onErrorResume(ReadTimeoutException.class, e -> {
					log.error(methodLogPrefix+ "SocketTimeoutException e: READ_TIMEOUT (5000ms)"); // WebClient config has 5000ms read timeout
					log.error(methodLogPrefix+ "SocketTimeoutException e: {}", e.toString());
					return monoErrorResponse("-99", "READ_TIMEOUT (5000ms)");
				})
				.onErrorResume(SocketTimeoutException.class, e -> { // Fallback for other SocketTimeoutException
					log.error(methodLogPrefix+ "SocketTimeoutException e: READ_TIMEOUT (5000ms)");
					log.error(methodLogPrefix+ "SocketTimeoutException e: {}", e.toString());
					return monoErrorResponse("-99", "READ_TIMEOUT (5000ms)");
				})
				.onErrorResume(Exception.class, e -> {
					log.error(methodLogPrefix+ "Exception e: {}", e.toString());
					return monoErrorResponse("-99", "GENERAL_ERROR");
				})
				.block(); // Synchronous call

			long endTimestamp = System.currentTimeMillis();
			double diffTime = (endTimestamp - startTimestamp)/1000.0; //두 시간에 차 계산
			
			if (responseBody != null) {
				response = new JSONObject(responseBody);

				if (response.has("result_code") == false) {
					try {
						int resultCode = response.getInt("resultCode");
						response.put("result_code", resultCode);
						response.remove("resultCode");
					} catch(Exception e) {
						response.put("result_code", -9);
						response.put("error_msg", "Not found result_code");
					}
				}

				if(diffTime > 0.25) {
					log.error(methodLogPrefix+ "잔액요청 응답시간(250ms) 초과");
					response.put("result_code", -99);
					response.put("error_msg", "RESPONSE_TIME_OUT [잔액요청 응답시간(250ms) 초과]");
				}
			} else {
				response.put("result_code", -99);
				response.put("error_msg", "EMPTY_RESPONSE_BODY");
			}
			
			response.put("timeMs", (endTimestamp - startTimestamp));
			
		} catch (JSONException e) {
			long endTimestamp = System.currentTimeMillis();
			log.error(methodLogPrefix+ "JSONException e: {}", e.toString());
			e.printStackTrace();
			try {
				response.put("result_code", -99);
				response.put("error_msg", "JSON_PARSE_ERROR");
				response.put("timeMs", (endTimestamp - startTimestamp));
			} catch (JSONException e1) {
				e1.printStackTrace();
			}
		} catch (Exception e) { // General catch for any other unexpected exceptions
			long endTimestamp = System.currentTimeMillis();
			log.error(methodLogPrefix+ "Exception e: {}", e.toString());
			e.printStackTrace();
			try {
				response.put("result_code", -99);
				response.put("error_msg", "GENERAL_ERROR");
				response.put("timeMs", (endTimestamp - startTimestamp));
			} catch (JSONException e1) {
				e1.printStackTrace();
			}
		}
		
		log.info(methodLogPrefix+ "response: {}", response.toString());
		return response;
	}

	// Helper method to create error Mono
	private reactor.core.publisher.Mono<String> monoErrorResponse(String resultCode, String errorMessage) {
		JSONObject errorJson = new JSONObject();
		try {
			errorJson.put("result_code", Integer.parseInt(resultCode));
			errorJson.put("error_msg", errorMessage);
		} catch (JSONException e) {
			log.error("Error creating error JSON: {}", e.getMessage());
		}
		return reactor.core.publisher.Mono.just(errorJson.toString());
	}

	@Override
	public JSONObject changeBalance(String LOG_PREFIX, String siteCallbackUrl, JSONObject param) {
		
		JSONObject response = new JSONObject();
		String methodLogPrefix = LOG_PREFIX + "changeBalance::";
		long startTimestamp = System.currentTimeMillis();
		log.info(methodLogPrefix+ "request: {}", param.toString());
		
		try {
			HttpHeaders header = getHeader();

			String responseBody = this.webClient.post()
				.uri(siteCallbackUrl + "/changebalance")
				.headers(headers -> headers.addAll(header))
				.bodyValue(param.toString())
				.exchangeToMono(clientResponse -> {
					if (clientResponse.statusCode().is2xxSuccessful()) {
						return clientResponse.bodyToMono(String.class);
					} else {
						return clientResponse.bodyToMono(String.class)
							.map(body -> {
								try {
									JSONObject errorResponse = new JSONObject();
									errorResponse.put("result_code", -99);
									errorResponse.put("error_msg", "HTTP_STATUS_CODE_ERR > " + clientResponse.statusCode().value());
									return errorResponse.toString();
								} catch (JSONException e) {
									log.error(methodLogPrefix + "Error parsing error response body: {}", e.getMessage());
									return "{\"result_code\":-99, \"error_msg\":\"HTTP_STATUS_CODE_ERR and parsing error\"}";
								}
							});
					}
				})
				.onErrorResume(ConnectException.class, e -> {
					log.error(methodLogPrefix+ "ConnectException e: CONNECT_TIMEOUT (3000ms)");
					log.error(methodLogPrefix+ "ConnectException e: {}", e.toString());
					return monoErrorResponse("-99", "CONNECT_TIMEOUT (3000ms)");
				})
				.onErrorResume(ReadTimeoutException.class, e -> {
					log.error(methodLogPrefix+ "SocketTimeoutException e: READ_TIMEOUT (5000ms)");
					log.error(methodLogPrefix+ "SocketTimeoutException e: {}", e.toString());
					return monoErrorResponse("-99", "READ_TIMEOUT (5000ms)");
				})
				.onErrorResume(SocketTimeoutException.class, e -> {
					log.error(methodLogPrefix+ "SocketTimeoutException e: READ_TIMEOUT (5000ms)");
					log.error(methodLogPrefix+ "SocketTimeoutException e: {}", e.toString());
					return monoErrorResponse("-99", "READ_TIMEOUT (5000ms)");
				})
				.onErrorResume(Exception.class, e -> {
					log.error(methodLogPrefix+ "Exception e: {}", e.toString());
					return monoErrorResponse("-99", "GENERAL_ERROR");
				})
				.block(); // Synchronous call

			long endTimestamp = System.currentTimeMillis();
			double diffTime = (endTimestamp - startTimestamp)/1000.0;
			
			if (responseBody != null) {
				response = new JSONObject(responseBody);

				if (response.has("result_code") == false) {
					try {
						int resultCode = response.getInt("resultCode");
						response.put("result_code", resultCode);
						response.remove("resultCode");
					} catch(Exception e) {
						response.put("result_code", -9);
						response.put("error_msg", "Not found result_code");
					}
				}

				if(diffTime > 0.25) {
					log.error(methodLogPrefix+ "잔액변경 응답시간(250ms) 초과");
					response.put("result_code", -99);
					response.put("error_msg", "RESPONSE_TIME_OUT [잔액변경 응답시간(250ms) 초과]");
				}
			} else {
				response.put("result_code", -99);
				response.put("error_msg", "EMPTY_RESPONSE_BODY");
			}
			
			response.put("timeMs", (endTimestamp - startTimestamp));
			
		} catch (JSONException e) {
			long endTimestamp = System.currentTimeMillis();
			log.error(methodLogPrefix+ "JSONException e: {}", e.toString());
			e.printStackTrace();
			try {
				response.put("result_code", -99);
				response.put("error_msg", "JSON_PARSE_ERROR");
				response.put("timeMs", (endTimestamp - startTimestamp));
			} catch (JSONException e1) {
				e1.printStackTrace();
			}
		} catch (Exception e) {
			long endTimestamp = System.currentTimeMillis();
			log.error(methodLogPrefix+ "Exception e: {}", e.toString());
			e.printStackTrace();
			try {
				response.put("result_code", -99);
				response.put("error_msg", "GENERAL_ERROR");
				response.put("timeMs", (endTimestamp - startTimestamp));
			} catch (JSONException e1) {
				e1.printStackTrace();
			}
		}
		
		log.info(methodLogPrefix+ "response: {}", response.toString());
		return response;
	}

	@Override
	public JSONObject changeBalanceSlot(String LOG_PREFIX, String siteCallbackUrl, JSONObject param) {
		
		JSONObject response = new JSONObject();
		String methodLogPrefix = LOG_PREFIX + "changeBalanceSlot::";
		long startTimestamp = System.currentTimeMillis();
		log.info(methodLogPrefix+ "request: {}", param.toString());
		
		try {
			HttpHeaders header = getHeader();

			String responseBody = this.webClient.post()
				.uri(siteCallbackUrl + "/changebalance/slot")
				.headers(headers -> headers.addAll(header))
				.bodyValue(param.toString())
				.exchangeToMono(clientResponse -> {
					if (clientResponse.statusCode().is2xxSuccessful()) {
						return clientResponse.bodyToMono(String.class);
					} else {
						return clientResponse.bodyToMono(String.class)
							.map(body -> {
								try {
									JSONObject errorResponse = new JSONObject();
									errorResponse.put("result_code", -99);
									errorResponse.put("error_msg", "HTTP_STATUS_CODE_ERR > " + clientResponse.statusCode().value());
									return errorResponse.toString();
								} catch (JSONException e) {
									log.error(methodLogPrefix + "Error parsing error response body: {}", e.getMessage());
									return "{\"result_code\":-99, \"error_msg\":\"HTTP_STATUS_CODE_ERR and parsing error\"}";
								}
							});
					}
				})
				.onErrorResume(ConnectException.class, e -> {
					log.error(methodLogPrefix+ "ConnectException e: CONNECT_TIMEOUT (3000ms)");
					log.error(methodLogPrefix+ "ConnectException e: {}", e.toString());
					return monoErrorResponse("-99", "CONNECT_TIMEOUT (3000ms)");
				})
				.onErrorResume(ReadTimeoutException.class, e -> {
					log.error(methodLogPrefix+ "SocketTimeoutException e: READ_TIMEOUT (5000ms)");
					log.error(methodLogPrefix+ "SocketTimeoutException e: {}", e.toString());
					return monoErrorResponse("-99", "READ_TIMEOUT (5000ms)");
				})
				.onErrorResume(SocketTimeoutException.class, e -> {
					log.error(methodLogPrefix+ "SocketTimeoutException e: READ_TIMEOUT (5000ms)");
					log.error(methodLogPrefix+ "SocketTimeoutException e: {}", e.toString());
					return monoErrorResponse("-99", "READ_TIMEOUT (5000ms)");
				})
				.onErrorResume(Exception.class, e -> {
					log.error(methodLogPrefix+ "Exception e: {}", e.toString());
					return monoErrorResponse("-99", "GENERAL_ERROR");
				})
				.block(); // Synchronous call

			long endTimestamp = System.currentTimeMillis();
			double diffTime = (endTimestamp - startTimestamp)/1000.0;
			
			if (responseBody != null) {
				response = new JSONObject(responseBody);

				if (response.has("result_code") == false) {
					try {
						int resultCode = response.getInt("resultCode");
						response.put("result_code", resultCode);
						response.remove("resultCode");
					} catch(Exception e) {
						response.put("result_code", -9);
						response.put("error_msg", "Not found result_code");
					}
				}

				if(diffTime > 0.25) {
					log.error(methodLogPrefix+ "잔액변경 응답시간(250ms) 초과");
					response.put("result_code", -99);
					response.put("error_msg", "RESPONSE_TIME_OUT [잔액변경 응답시간(250ms) 초과]");
				}
			} else {
				response.put("result_code", -99);
				response.put("error_msg", "EMPTY_RESPONSE_BODY");
			}
			
			response.put("timeMs", (endTimestamp - startTimestamp));
			
		} catch (JSONException e) {
			long endTimestamp = System.currentTimeMillis();
			log.error(methodLogPrefix+ "JSONException e: {}", e.toString());
			e.printStackTrace();
			try {
				response.put("result_code", -99);
				response.put("error_msg", "JSON_PARSE_ERROR");
				response.put("timeMs", (endTimestamp - startTimestamp));
			} catch (JSONException e1) {
				e1.printStackTrace();
			}
		} catch (Exception e) {
			long endTimestamp = System.currentTimeMillis();
			log.error(methodLogPrefix+ "Exception e: {}", e.toString());
			e.printStackTrace();
			try {
				response.put("result_code", -99);
				response.put("error_msg", "GENERAL_ERROR");
				response.put("timeMs", (endTimestamp - startTimestamp));
			} catch (JSONException e1) {
				e1.printStackTrace();
			}
		}
		
		log.info(methodLogPrefix+ "response: {}", response.toString());
		return response;
	}

	@Override
	public JSONObject getTestBalance(String LOG_PREFIX, String memberId, String token, String authorization) {
		
		JSONObject responseObj = null;
		String methodLogPrefix = LOG_PREFIX; // LOG_PREFIX is not modified in this method, so it's effectively final.
		
		try {
			// 1. 요청 URL + 쿼리 파라미터
	        String url = "https://cb.nxapi.cc/api/t1/spdtest/testBalance";
	        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url)
	                .queryParam("memberId", memberId);

	        // 2. 헤더 설정 (토큰 필요 시)
	        HttpHeaders headers = getHeader();
	        headers.set("Authorization", authorization);
	        headers.set("token", token);
	        
	        log.info(methodLogPrefix+ "request: {}", uriBuilder.toUriString());

	        // 3. GET 요청
	        String responseBody = this.webClient.get()
	                .uri(uriBuilder.toUriString())
	                .headers(h -> h.addAll(headers))
	                .retrieve()
	                .bodyToMono(String.class)
	                .onErrorResume(Exception.class, e -> {
						log.error(methodLogPrefix+ "Exception e: {}", e.toString());
						return reactor.core.publisher.Mono.empty(); // Return empty to simulate null body
					})
	                .block(); // Synchronous call
	        
	        if (responseBody != null && !responseBody.isEmpty()) {
	        	log.info(methodLogPrefix+ "response: {}", responseBody);
	        	responseObj = new JSONObject(responseBody);
	        }
		} catch (Exception e) {
			log.error(methodLogPrefix+ "Exception e: {}", e.toString());
		}
        
		return responseObj;
	}

	@Override
	public JSONObject getTestChangeBalance(String LOG_PREFIX, String memberId, String token, String authorization) {

		JSONObject responseObj = null;
		String methodLogPrefix = LOG_PREFIX; // LOG_PREFIX is not modified in this method, so it's effectively final.
		
		try {
			// 1. 요청 URL + 쿼리 파라미터
	        String url = "https://cb.nxapi.cc/api/t1/spdtest/testCasino";
	        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url)
	                .queryParam("memberId", memberId);

	        // 2. 헤더 설정 (토큰 필요 시)
	        HttpHeaders headers = getHeader();
	        headers.set("Authorization", authorization);
	        headers.set("token", token);

	        // 3. GET 요청
	        String responseBody = this.webClient.get()
	                .uri(uriBuilder.toUriString())
	                .headers(h -> h.addAll(headers))
	                .retrieve()
	                .bodyToMono(String.class)
	                .onErrorResume(Exception.class, e -> {
						log.error(methodLogPrefix+ "Exception e: {}", e.toString());
						return reactor.core.publisher.Mono.empty(); // Return empty to simulate null body
					})
	                .block(); // Synchronous call
	        
	        if (responseBody != null && !responseBody.isEmpty()) {
	        	responseObj = new JSONObject(responseBody);
	        }
		} catch (Exception e) {
			log.error(methodLogPrefix+ "Exception e: {}", e.toString());
		}
        
		return responseObj;
	}

	@Override
	public JSONObject getTestChangeBalanceSlot(String LOG_PREFIX, String memberId, String token, String authorization) {

		JSONObject responseObj = null;
		String methodLogPrefix = LOG_PREFIX; // LOG_PREFIX is not modified in this method, so it's effectively final.
		
		try {
			// 1. 요청 URL + 쿼리 파라미터
	        String url = "https://cb.nxapi.cc/api/t1/spdtest/testSlot";
	        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(url)
	                .queryParam("memberId", memberId);

	        // 2. 헤더 설정 (토큰 필요 시)
	        HttpHeaders headers = getHeader();
	        headers.set("Authorization", authorization);
	        headers.set("token", token);

	        // 3. GET 요청
	        String responseBody = this.webClient.get()
	                .uri(uriBuilder.toUriString())
	                .headers(h -> h.addAll(headers))
	                .retrieve()
	                .bodyToMono(String.class)
	                .onErrorResume(Exception.class, e -> {
						log.error(methodLogPrefix+ "Exception e: {}", e.toString());
						return reactor.core.publisher.Mono.empty(); // Return empty to simulate null body
					})
	                .block(); // Synchronous call
	        
	        if (responseBody != null && !responseBody.isEmpty()) {
	        	responseObj = new JSONObject(responseBody);
	        }
		} catch (Exception e) {
			log.error(methodLogPrefix+ "Exception e: {}", e.toString());
		}
        
		return responseObj;
	}
}
