package com.eactive.eai.adapter.http.client.impl;

import com.eactive.eai.adapter.http.HttpMemoryLogger;
import com.eactive.eai.adapter.http.client.HttpClient5AdapterServiceSupport;
import com.eactive.eai.adapter.http.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.client.HttpClientAdapterVO;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.util.CommonLib;
import com.eactive.eai.common.util.TxFileLogger;

import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.Properties;

public class HttpClient5AdapterServiceBody extends HttpClient5AdapterServiceSupport {
	public Object execute(Properties prop, Object data, Properties tempProp) throws Exception {
		HttpClientAdapterVO vo = super.setting(prop, tempProp);

		boolean isAsync = false;
		if (RESPONSE_TYPE_ASYNC.equals(vo.getResponseType())) {
			isAsync = true;
		}

		RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();

		boolean useForwardProxy = StringUtils.equalsIgnoreCase(prop.getProperty("FORWARD_PROXY_USE_YN", "N"), "Y");
		String forwardProxyUrl = prop.getProperty("FORWARD_PROXY_URL");

		if(useForwardProxy && !isTas(tempProp)) {
			java.net.URL url = new java.net.URL(forwardProxyUrl);

			// 프로토콜, 호스트, 포트 추출
			HttpHost proxy = new HttpHost(url.getProtocol(), url.getHost(), url.getPort());
			requestConfigBuilder.setProxy(proxy);
		}

		String sendData = "";
		if (data instanceof String) {
			sendData = (String) data;
		} else if (data instanceof byte[]) {
			sendData = new String((byte[]) data, vo.getEncode());
		}

		if(MessageType.JSON.equals(prop.getProperty("messageType"))) {
			ObjectMapper mapper = new ObjectMapper();
			ObjectNode jsonNode = (ObjectNode) mapper.readTree(sendData);
			ObjectNode headerPart = (ObjectNode) jsonNode.get("header_part");

			if( headerPart.get("mciIntfId") != null && headerPart.get("mciIntfId").asText().trim().length() > 0 ) {
				headerPart.put("eaiIntfId", "");
				sendData = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode);
			}
		}

		if ("Y".equals(vo.getUrlEncodeYn())) {
			sendData = java.net.URLEncoder.encode(sendData);
		}

		ContentType contentTypeObj ;
		if (StringUtils.isNotEmpty(vo.getContentType())) {
			//주석이유 : ContentType.create에서 "application/json;charset=UTF-8" 이런 형태면 오류발생
//			String contentTypeValue ;
//			if (StringUtils.isNotEmpty(vo.getEncode())) {
//				contentTypeValue = vo.getContentType() + "; charset=" + vo.getEncode();
//			} else {
//				contentTypeValue = vo.getContentType();
//			}
			contentTypeObj = ContentType.create(vo.getContentType(), vo.getEncode());
		}else{
			contentTypeObj = ContentType.APPLICATION_JSON;
		}

		HttpPost method = new HttpPost(vo.getUrl());
		// POST 요청 본문 설정
		if (sendData != null) {
			method.setEntity(new StringEntity(sendData, contentTypeObj));
		}

		if (vo.getAdapterHeaders() != null) {
			for (String key : vo.getAdapterHeaders().keySet()) {
				method.setHeader(key, vo.getAdapterHeaders().get(key));
			}
		}
		if (logger.isInfoEnabled()) {
			logger.info("" + CONTENT_TYPE_KEY + "(" + contentTypeObj + ")");
		}

		configureHttpClient(requestConfigBuilder, vo, method);

		int status = -1;

		try {
			if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
				logger.debug(
						"SEND (" + vo.getAdapterGroupName() + ") = [" + sendData + "]");
				logger.debug("SEND (" + vo.getAdapterGroupName() + ") = "
						+ CommonLib.getDumpMessage(sendData));
			}
			StopWatch stopWatch = new StopWatch();
			stopWatch.start();

			if (vo.getTraceLevel() >= 3) {
				HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
						"SEND [" + sendData + "]" + CommonLib.getDumpMessage(sendData));
			}
			
			TxFileLogger.logTxFile(tempProp, sendData, "[OUT_SEND]");
			
			String uuid = tempProp.getProperty(TransactionContextKeys.TRANSACTION_UUID);
			HttpContext context = new BasicHttpContext();
			context.setAttribute(TransactionContextKeys.TRANSACTION_UUID, uuid);
			context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME, vo.getAdapterGroupName());
			context.setAttribute(HttpClientAdapterServiceKey.ADAPTER_NAME, vo.getAdapterName());
			Integer logProcessNo = (Integer) tempProp.get(HttpClientAdapterServiceKey.LOG_PROCESS_NO);
			if (logProcessNo != null && logProcessNo > 0) {
				context.setAttribute(HttpClientAdapterServiceKey.LOG_PROCESS_NO, logProcessNo);
			}

			byte[] responseMessage = null;
			String responseString = "";
			
			DeadlineAwareRetryExecutor executor =
				    new DeadlineAwareRetryExecutor((CloseableHttpClient)this.client,
				                                   vo.getConnectionTimeout(),
				                                   vo.getTimeout());

			// Properties에서 RetryPolicy 로드
			RetryPolicy policy = RetryPolicy.from(prop); // 설정 로드 방식에 맞게
			try (CloseableHttpResponse response = (CloseableHttpResponse) executor.execute(method, context, policy)) {
				status = response.getCode();
				responseMessage = EntityUtils.toByteArray(response.getEntity());
				responseString = new String(responseMessage, vo.getEncode());
				if (logger.isDebug()) {
					logger.debug("[received status]" + status);
					logger.debug("HttpClientAdapterServiceBody] RECV (" + vo.getAdapterGroupName() + ") = [" + responseString + "]");
				}
			} catch (ConnectException e) {
				if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
					logger.debug("HttpClientAdapterServiceBody] responseType =" + vo.getResponseType());
				}
				throw new Exception("excuteMethod java.net.ConnectException = " + e.getMessage());
			}

			stopWatch.stop();
			TxFileLogger.logTxFile(tempProp, responseString, "[OUT_RECV]");
			

			if (status >= 400 && status < 500) {
				String errMsg = String.format(
						"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s  response Data=%s",
						status, vo.getAdapterGroupName(), vo.getAdapterName(), vo.getUrl(), "post", responseString);
				throw new Exception(errMsg);
			} else if(status != HttpStatus.SC_OK) {
				String errMsg = String.format(
						"http receive status fail value=%d adapterName=%s.%s uri=%s method=%s  response Data=%s",
						status, vo.getAdapterGroupName(), vo.getAdapterName(), vo.getUrl(), "post", responseString);
				throw new Exception(errMsg);
			}

			if (isAsync && stopWatch.getTime() > vo.getSlowTranTime() && (logger.isWarn())) {
				logger.warn("URL[" + vo.getUrl() + "] lazy async response time = "
						+ stopWatch.toString() + " responseMessage=" + responseString);
			}

			if (vo.getTraceLevel() >= 3) {
				HttpMemoryLogger.txlog(vo.getAdapterGroupName() + vo.getAdapterName(),
						"RECV [" + responseString + "]"
								+ CommonLib.getDumpMessage(responseMessage));
			}

//			return responseMessage;
			return responseString;

		} catch (SocketTimeoutException ste) { // Read Timeout :: HttpClient 3.1일 경우
			if (vo.getTraceLevel() >= 3) {
				HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
						"SocketTimeoutException : " + ste.toString(), ste);
			}
			if (logger.isError()) {
				logger.error("SocketTimeoutException : " + ste.toString(), ste);
			}
			throw ste;
		} catch (ConnectException ce) {
			if (vo.getTraceLevel() >= 3) {
				HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
						"Connection Exception : " + ce.toString(), ce);
			}
			if (logger.isError()) {
				logger.error("Connection Exception : " + ce.toString(), ce);
			}
			throw ce;
		} catch (Exception e) {
			if (vo.getTraceLevel() >= 3) {
				HttpMemoryLogger.error(vo.getAdapterGroupName() + vo.getAdapterName(),
						"Exception : " + e.toString(), e);
			}
			if (logger.isError()) {
				logger.error("Exception : " + e.toString(), e);
			}
			throw e;
		}
	}
}