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

import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.mina.common.ByteBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.AntPathMatcher;

import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.AdapterPropManager;
import com.eactive.eai.adapter.AdapterVO;
import com.eactive.eai.adapter.Keys;
import com.eactive.eai.adapter.http.HttpMemoryLogger;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.util.CommonLib;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.env.ElinkConfig;
import com.eactive.eai.inbound.action.ActionFactory;
import com.eactive.eai.inbound.action.RequestAction;
import com.eactive.eai.inbound.processor.Processor;
import com.eactive.eai.message.StandardMessageUtil;

public class HttpAdapterServiceBypass extends HttpAdapterServiceSupport {
	static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);

	public static final String HTTP_STATUS = "HTTP_STATUS";
	public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod";
	public static final String[] HOP_BY_HOP_HEADERS = new String[] { "Connection", "Keep-Alive", "Proxy-Authenticate",
			"Proxy-Authorization", "TE", "Trailers", "Trailer", "Transfer-Encoding", "Upgrade" };

	protected boolean doPreserveCookies = false;
	protected boolean doPreserveCookiePath = false;

	/**
	 * 1. 기능 : Header 값과 Body 값을 Bypass 한다.
	 *
	 * @param request Http Servlet Request
	 * @return response Http Servlet Response
	 * @exception Exception
	 **/
	public void service(String adptGrpName, String adptName, HttpServletRequest request, HttpServletResponse response) {
		int traceLevel = 0;

		AdapterVO adptVO = null;
		AdapterPropManager manager = null;

		Properties httpProp = null;
		String responseType = null;
		String encode = null;

		String traceLevelTemp = null;

		StopWatch stopWatch = null;
		Properties prop = null;
		String errorResponseFormat = null;

		try {
			AdapterManager adapterManager = AdapterManager.getInstance();
			adptVO = adapterManager.getAdapterVO(adptGrpName, adptName);
			if (adptVO == null) {
				throw new Exception("Adapter not found error");
			}

			manager = AdapterPropManager.getInstance();

			httpProp = manager.getProperties(adptVO.getPropGroupName());
			responseType = httpProp.getProperty(RESPONSE_TYPE, "SYNC");
			encode = StringUtils.defaultIfBlank(adptVO.getAdapterGroupVO().getMessageEncode(), "UTF-8");
			traceLevelTemp = httpProp.getProperty(TRACE_LEVEL, "0");
			errorResponseFormat = httpProp.getProperty(ERROR_RESPONSE_FORMAT);

			try {
				traceLevel = Integer.parseInt(traceLevelTemp);
			} catch (Exception e) {
				logger.warn("traceLevel parsing error - " + traceLevelTemp);
			}

			stopWatch = new StopWatch();
			stopWatch.start();

			logger.debug("시작 >> encode = [" + encode + "]");

			String requestData = "";
			if (request.getHeader(HttpHeaders.CONTENT_LENGTH) != null
					|| request.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {
				ServletInputStream sis = request.getInputStream();
				ByteBuffer bb = ByteBuffer.allocate(1024).setAutoExpand(true);
				int i = 0;
				byte[] cbuf = new byte[1024];
				while ((i = sis.read(cbuf, 0, 1024)) != -1) {
					if (i == 1024) {
						bb.put(cbuf);
					} else {
						byte[] tail = new byte[i];
						System.arraycopy(cbuf, 0, tail, 0, i);
						bb.put(tail);
					}
				}
				byte[] data = new byte[bb.position()];
				bb.position(0);
				bb.get(data);
				requestData = new String(data, encode);

				if (traceLevel >= 3) {
					HttpMemoryLogger.txlog(adptGrpName + adptName,
							"RECV " + "[Bypass Request..]" + CommonLib.getDumpMessage(requestData));
				}
			}

			prop = new Properties();
			prop.put(INBOUND_METHOD, request.getMethod());
			prop.put(INBOUND_URI, request.getRequestURI());
			prop.put(INBOUND_QUERY_STRING, StringUtils.defaultString(request.getQueryString()));
			prop.put(INBOUND_HEADER, getHeaders(request));
			prop.put(INBOUND_EXTPARAMS, StringUtils.defaultString(request.getQueryString()));
			if (StringUtils.equals(adptVO.getAdapterGroupVO().getType(), Keys.TYPE_REST)
					|| StringUtils.equals(adptVO.getAdapterGroupVO().getType(), Keys.TYPE_HTTP_CUSTOM)) {
				// /api/v1/public/getUserInfo.svc
				String extUrl = StringUtils.removeStart(request.getRequestURI(), request.getContextPath());
				prop.put(INBOUND_EXTURI, extUrl);
			} else {
				prop.put(INBOUND_EXTURI, getExtUri(request));
			}
			prop.put(Processor.REQUEST_ACTION, adptVO.getAdapterGroupVO().getRefClass());
			prop.put(API_PATH, httpProp.getProperty(API_PATH, ""));
			prop.put(INBOUND_REWRITE_PATH,
					getRewritePath(prop.getProperty(INBOUND_EXTURI), prop.getProperty(API_PATH)));
			prop.put(INBOUND_REMOTE_ADDR, request.getRemoteAddr());
			prop.put(INBOUND_SCHEME, request.getScheme());
			prop.put(PRE_FILTERS, httpProp.getProperty(PRE_FILTERS, ""));
			prop.put(POST_FILTERS, httpProp.getProperty(POST_FILTERS, ""));
			prop.put(PROPERTIES_NAME_HTTP_REQUEST_METHOD, request.getMethod());
			prop.put(ALLOW_IP, httpProp.getProperty(ALLOW_IP, ""));
			
			Map<String, String> pathVariables = assignPathVariables(request, adptGrpName, adptName, null, prop);
			if (pathVariables != null) {
				prop.put(INBOUND_PATH_VARIABLES, pathVariables);
			}

			if (logger.isDebug()) {
				logger.debug("HttpAdapterServiceBypass] INBOUND_METHOD= " + prop.getProperty(INBOUND_METHOD));
				logger.debug("HttpAdapterServiceBypass] INBOUND_URI= " + prop.getProperty(INBOUND_URI));
				logger.debug("HttpAdapterServiceBypass] INBOUND_EXTURI= " + prop.getProperty(INBOUND_EXTURI));
				logger.debug(
						"HttpAdapterServiceBypass] INBOUND_QUERY_STRING= " + prop.getProperty(INBOUND_QUERY_STRING));
			}

//			// check(현재 어댑터 = 서비스정보의 기동어댑터)
//			if (StringUtils.equals(adptVO.getAdapterGroupVO().getType(), Keys.TYPE_REST)) {
//				validateServiceAndAdapter(adptGrpName, adptName, message.getBytes(encode), prop);
//			}

			// 로컬 서비스 호출
			String result = (String) service(adptGrpName, adptName, requestData, prop, request, response);

			stopWatch.stop();
			logger.info("종료 >> encode = [" + encode + "]");

			if (RESPONSE_TYPE_ASYNC.equals(responseType)) {
				String responseData = "";
				if (stopWatch.getTime() > slowTranTime) {
					if (logger.isWarn()) {
						logger.warn("HttpAdapterServiceBypass] lazy async response time = " + stopWatch.toString()
								+ ", message = " + requestData);
					}
				}

				if (result == null) {
					responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);;
				} else {
					responseData = result;
				}

				long contentLength = result.getBytes(encode).length;
				response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
				response.setCharacterEncoding(encode);
				response.getWriter().print(responseData);
				if (traceLevel >= 3) {
					HttpMemoryLogger.txlog(adptGrpName + adptName, "SEND " + "[" + responseData + "]"
							+ ((responseData == null) ? "" : CommonLib.getDumpMessage(responseData)));
				}
			} else {
				assignResponseHeaders(request, response, prop);

				if (result != null) {

					long contentLength = result.getBytes(encode).length;
					response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
					if (logger.isDebug()) {
						logger.debug("Response Content Lenght(Reset)=" + contentLength);
					}

					response.setCharacterEncoding(encode);
					response.getWriter().print(result);
				}

				if (traceLevel >= 3) {
					HttpMemoryLogger.txlog(adptGrpName + adptName,
							"SEND " + "[Bypass Response..]" + CommonLib.getDumpMessage(result));
				}
			}
		} catch (HttpStatusException e) {
			if (traceLevel >= 3) {
				HttpMemoryLogger.error(adptGrpName + adptName, e.toString(), e);
			}
			logger.warn("HttpAdapter] " + adptGrpName + "-" + adptName + ">>" + e.getMessage());
			response.setStatus(e.getStatus());
			try {
				String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptVO.getAdapterGroupVO().getMessageType(),
						encode, e.getCode(), e.getMessage(), errorResponseFormat);
				response.getWriter().println(errorMsg);
			} catch (Exception ex) {
				// IGNORE
			}
		} catch (JwtAuthException e) {
			if (traceLevel >= 3) {
				HttpMemoryLogger.error(adptGrpName + adptName, e.toString(), e);
			}
			logger.error("HttpAdapterServiceRest] " + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
			response.setStatus(HttpStatus.UNAUTHORIZED.value());
			try {
				String errorMsg = MessageUtil.makeErrorMessageByMessageType(adptVO.getAdapterGroupVO().getMessageType(),
						encode, e.getCode(), e.getMessage(), errorResponseFormat);
				response.getWriter().println(errorMsg);
			} catch (Exception ex) {
				// IGNORE
			}
		} catch (Exception e) {
			if (traceLevel >= 3) {
				HttpMemoryLogger.error(adptGrpName + adptName, e.toString(), e);
			}
			logger.error("HttpAdapterServiceBypass] " + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
			response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
			try {
				response.getWriter().println(e.getMessage());
				String errCode = ExceptionUtil.getErrorCode(e, "RECEAIAHA003");
				throw new Exception(errCode);
			} catch (Exception ex) {
				// IGNORE
				logger.warn("HttpAdapterServiceBypass] " + adptGrpName + "-" + adptName + ">>" + e.getMessage(), e);
			}
		}
	}

//	private void validateServiceAndAdapter(String adptGrpName, String adptName, byte[] requestBytes, Properties prop)
//			throws JwtAuthException {
//		try {
//			String actionName = prop.getProperty(Processor.REQUEST_ACTION);
//			RequestAction action = ActionFactory.createAction(actionName);
//			action.setAdapterInfo(adptGrpName, adptName, prop);
//			String[] keys = action.perform(requestBytes);
//			String apiId = keys[0];
//			
//			// PathVariable 지원 추가
//			apiId = StandardMessageUtil.getMatchedKey(apiId, actionName);
//			if (apiId == null) {
//				throw new Exception("apiId");
//			}
//
////			STDMessageManager stdheaderManager = STDMessageManager.getInstance();
////			STDMessage stdmessage = stdheaderManager.getSTDMessage(apiId);
////			if (stdmessage == null) {
////				throw new Exception("stdMsg");
////			}
//
//			long msgRcvTm = System.currentTimeMillis();
//			AdapterManager adapterManager = AdapterManager.getInstance();
//			AdapterGroupVO adptGrpVO = adapterManager.getAdapterGroupVO(adptGrpName);
//			ProcessVO processVO = setProcessVO(apiId, adptGrpVO.getOsidInstiNo(), msgRcvTm, adptGrpName, adptName);
//			ExtMessage extMessage = ExtMessageConverter.getExtMessageForNonStandard(requestBytes, requestBytes, processVO);
//
//			EAIMessageManager eaiMsgManager = EAIMessageManager.getInstance();
////			String eaiSvcCd = stdmessage.getEAITranName() + stdmessage.getSTDHeader().getMesgDvcd();
//			String eaiSvcCd = extMessage.getEAIServiceCode();
//			if (eaiSvcCd == null) {
//				eaiSvcCd = extMessage.getInterfaceId() + extMessage.getSendRecv() + extMessage.getDirection();
//			}
//			EAIMessage eaiMsg = eaiMsgManager.getEAIMessage(eaiSvcCd);
//			logger.warn("adptGrpName=[" + adptGrpName + "],eaiSvcCd=[" + eaiSvcCd + "],eaiMsg=[" + eaiMsg
//					+ "],getSngSysItfTp=[" + "]");
//			if (eaiMsg == null) {
//				throw new Exception("eaiMsg");
//			}
//
//			if (!StringUtils.equals(eaiMsg.getSngSysItfTp(), adptGrpName)) {
//				throw new Exception("adapter");
//			}
//		} catch (Exception e) {
//			String msg = String.format("The resource does not exist or does not have permissions(%s): %s",
//					e.getMessage(), prop.get(INBOUND_EXTURI));
//			throw new JwtAuthException(JwtAuthFilter.ERROR_AUTHORIZATION_FAIL, msg);
//		}
//	}

//	private ProcessVO setProcessVO(String apiId, String osidInstiNo, long msgRcvTm, String adptGrpName,
//			String adptName) {
//		ProcessVO vo = new ProcessVO();
//		vo.setSelectedKey(apiId);
//		vo.setAdapterGroupName(adptGrpName);
//		vo.setAdapterName(adptName);
//		vo.setInAdapterOsidInstiNo(osidInstiNo);
//		vo.setErrTm(msgRcvTm);
//		return vo;
//	}

	private String getRewritePath(String extUri, String basePath) {
		return StringUtils.removeStart(extUri, StringUtils.removeEnd(basePath, "/"));
	}
	
	private Map<String, String> assignPathVariables(HttpServletRequest request, String adptGrpName, String adptName,
			Object requestBytes, Properties prop) {
		Map<String, String> variablesMap = null;
		// QueryString 있을때는 체크(X)
		if (StringUtils.isBlank(request.getQueryString())) {
			try {
				String actionName = prop.getProperty(Processor.REQUEST_ACTION);
				RequestAction action = ActionFactory.createAction(actionName);
				action.setAdapterInfo(adptGrpName, adptName, prop);
				String[] keys = action.perform(requestBytes);
				String requestPath = keys[0];

				// PathVariable 지원 추가
				String ruledPath = StandardMessageUtil.getMatchedKey(requestPath, actionName);
				if (!StringUtils.equals(requestPath, ruledPath) && StringUtils.contains(ruledPath, "{")) {
					variablesMap = new AntPathMatcher().extractUriTemplateVariables(ruledPath, requestPath);
				}
			} catch (Exception e) {
				logger.error(e.getMessage());
			}
		}

		return variablesMap;
	}

	/**
	 * request header 를 Properties로 조립
	 * 
	 * @param request
	 * @return
	 */
	private Properties getHeaders(HttpServletRequest request) {
		Properties prop = new Properties();

		Enumeration<String> headerNames = request.getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String key = headerNames.nextElement();
			String value = request.getHeader(key);
			prop.setProperty(key, value);
		}

		return prop;
	}

	@SuppressWarnings("unchecked")
	private void assignResponseHeaders(HttpServletRequest request, HttpServletResponse response, Properties prop) {
		Map<String, Object> outboundPropertyMap = (Map<String, Object>) prop.get(OUTBOUND_PROPERTY_MAP);
		if (outboundPropertyMap == null) {
			return;
		}

		Properties headerProp = (Properties) outboundPropertyMap.get(OUTBOUND_RESPONSE_HEADERS);
		if (headerProp == null) {
			return;
		}

		String httpStatus = "200";
		for (Entry<Object, Object> e : headerProp.entrySet()) {
			String key = (String) e.getKey();
			String value = (String) e.getValue();

			if (StringUtils.equalsAnyIgnoreCase(key, HOP_BY_HOP_HEADERS)) {
				continue;
			}

			if (StringUtils.equalsIgnoreCase(key, HttpHeaders.CONTENT_LENGTH)) {
				if (logger.isDebug()) {
					logger.debug("Response Content Lenght(Header)=" + value);
				}
				continue;
			}

			if (StringUtils.equalsAnyIgnoreCase(key, HttpHeaders.SET_COOKIE, HttpHeaders.SET_COOKIE2)) {
				for (HttpCookie cookie : HttpCookie.parse(value)) {
					Cookie servletCookie = createProxyCookie(request, cookie);
					response.addCookie(servletCookie);
				}
				continue;
			}

			if (StringUtils.equalsIgnoreCase(key, HTTP_STATUS)) {
				if (StringUtils.isNotBlank(value)) {
					httpStatus = value;
					continue;
				}
			}

			if (StringUtils.equalsIgnoreCase(key, HttpHeaders.LOCATION)) {
				if (!StringUtils.startsWith(httpStatus, "3")) {
					httpStatus = "302";
				}
			}

			response.addHeader(key, value);
		}
		response.setStatus(Integer.parseInt(httpStatus));
	}

	/**
	 * Creates a proxy cookie from the original cookie.
	 *
	 * @param servletRequest original request
	 * @param cookie         original cookie
	 * @return proxy cookie
	 */
	protected Cookie createProxyCookie(HttpServletRequest servletRequest, HttpCookie cookie) {
		String proxyCookieName = getProxyCookieName(cookie);
		Cookie servletCookie = new Cookie(proxyCookieName, cookie.getValue());
		servletCookie.setPath(this.doPreserveCookiePath ? cookie.getPath() : // preserve original cookie path
				buildProxyCookiePath(servletRequest) // set to the path of the proxy servlet
		);
		servletCookie.setComment(cookie.getComment());
		servletCookie.setMaxAge((int) cookie.getMaxAge());
		// don't set cookie domain
		servletCookie.setSecure(servletRequest.isSecure() && cookie.getSecure());
		servletCookie.setVersion(cookie.getVersion());
		servletCookie.setHttpOnly(cookie.isHttpOnly());
		return servletCookie;
	}

	/**
	 * Set cookie name prefixed with a proxy value so it won't collide with other
	 * cookies.
	 *
	 * @param cookie cookie to get proxy cookie name for
	 * @return non-conflicting proxy cookie name
	 */
	protected String getProxyCookieName(HttpCookie cookie) {
		return doPreserveCookies ? cookie.getName() : getCookieNamePrefix(cookie.getName()) + cookie.getName();
	}

	/**
	 * Create path for proxy cookie.
	 *
	 * @param servletRequest original request
	 * @return proxy cookie path
	 */
	protected String buildProxyCookiePath(HttpServletRequest servletRequest) {
		String path = servletRequest.getContextPath(); // path starts with / or is empty string
		path += servletRequest.getServletPath(); // servlet path starts with / or is empty string
		if (path.isEmpty()) {
			path = "/";
		}
		return path;
	}

	/** The string prefixing rewritten cookies. */
	protected String getCookieNamePrefix(String name) {
		return "!Proxy!eLink";
	}

	/**
	 * 어댑터 명 이후를 가져온다.
	 * 
	 * @param request
	 * @return
	 */
	private String getExtUri(HttpServletRequest request) {
		String orgUri = request.getRequestURI().replaceAll(request.getContextPath(), "");
		String uri = getExtUri(orgUri, 3);
		if (uri != null && uri.trim().length() > 0) {
			return "/" + uri;
		} else {
			return "";
		}
	}

	private static String getExtUri(String url, int length) {
		String[] urls = url.split("/");
		List<String> newUrls = new ArrayList<>();
		Collections.addAll(newUrls, urls);
		return StringUtils.join(newUrls.subList(length, urls.length).toArray(), "/");
	}

	public static void main(String[] args) throws Exception {
		String orgUri = "/HTT/CbsInNetSys/abcd/123456";
		String result = "";
		result = getExtUri(orgUri, 3);
		// System.out.println(result);
	}
}
