package com.eactive.eai.adapter.service;

import java.io.IOException;
import java.io.StringWriter;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletInputStream;
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.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.json.simple.JSONValue;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;

import com.eactive.eai.adapter.AdapterGroupVO;
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.HttpMethodType;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.filter.ApiKeyExtractFilter;
import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter;
import com.eactive.eai.common.context.ElinkTransactionContext;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.util.CommonLib;
import com.eactive.eai.common.util.JacksonUtil;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.TxFileLogger;
import com.eactive.eai.common.util.XMLUtils;
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;
import com.eactive.eai.util.JsonPathUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;


@Service
public class DJBApiAdapterService extends HttpAdapterServiceSupport {
	private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
	
	static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
	static Logger siftLogger = Logger.getLogger(Logger.LOGGER_SIFT);

    public static final String HEADER_GROUP = "HEADER_GROUP";
	public static final String HTTP_STATUS = "HTTP_STATUS";   // jwhong
    // HEADER_GROUP JSON에 추가할 항목 정의, 없으면 전체 header 추가
    public static final String HEADER_KEYS = "HEADER_KEYS";
    public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod";

    public static final String PAYLOAD_PARAM_NAME_CLIENT_ID = "client_id";  // jwhong
	
    private ObjectMapper mapper = new ObjectMapper();
	
    public String callApi(HttpServletRequest request, HttpServletResponse response, Properties httpProp,
			AdapterGroupVO adapterGroupVO, AdapterVO adapterVO, Properties transactionProp) throws Exception {
        int traceLevel = 0;

        String adptGrpName = adapterGroupVO.getName();
        String adptName = adapterVO.getName();

        String urlDecodeYn = httpProp.getProperty(URL_DECODE_YN, "N");
        String encode = StringUtils.defaultIfBlank(adapterGroupVO.getMessageEncode(), "UTF-8");

        String traceLevelTemp = httpProp.getProperty(TRACE_LEVEL, "0");
        String relayRequestHeaderKeys = httpProp.getProperty(HEADER_KEYS);
        String headerGroupName = httpProp.getProperty(HEADER_GROUP);

        boolean isParameterType = false;
        String message = null;

        String paramValue = null;
        String adptMsgType = null;
        
        Properties inboundHeaderProp = getHeaders(request);
        transactionProp.put(INBOUND_METHOD, request.getMethod());
        transactionProp.put(INBOUND_URI, request.getRequestURI());
        transactionProp.put(INBOUND_QUERY_STRING, StringUtils.defaultString(request.getQueryString()));
        transactionProp.put(INBOUND_HEADER, inboundHeaderProp);
		transactionProp.put(INBOUND_EXTPARAMS, StringUtils.defaultString(request.getQueryString()));
        if (StringUtils.equals(adapterVO.getAdapterGroupVO().getType(), Keys.TYPE_REST)
                || StringUtils.equals(adapterVO.getAdapterGroupVO().getType(), Keys.TYPE_HTTP_CUSTOM)) {
            // /api/v1/public/getUserInfo.svc
            String extUrl = StringUtils.removeStart(request.getRequestURI(), request.getContextPath());
            transactionProp.put(INBOUND_EXTURI, extUrl);
        } else {
            transactionProp.put(INBOUND_EXTURI, getExtUri(request)); 
        }
        transactionProp.put(INBOUND_CLIENT_IP, getClientIp(request));  // Client IP 추가
        transactionProp.put(Processor.REQUEST_ACTION, adapterVO.getAdapterGroupVO().getRefClass());
        transactionProp.put(API_PATH, httpProp.getProperty(API_PATH, ""));
        
        //djerp bypass
        transactionProp.put(INBOUND_REWRITE_PATH,
				getRewritePath(transactionProp.getProperty(INBOUND_EXTURI), transactionProp.getProperty(API_PATH)));
        transactionProp.put(INBOUND_REMOTE_ADDR, request.getRemoteAddr());
        transactionProp.put(INBOUND_SCHEME, request.getScheme());
		
		transactionProp.put(PRE_FILTERS, httpProp.getProperty(PRE_FILTERS, ""));
        transactionProp.put(POST_FILTERS, httpProp.getProperty(POST_FILTERS, ""));
        transactionProp.put(PROPERTIES_NAME_HTTP_REQUEST_METHOD, request.getMethod());
        transactionProp.put(ALLOW_IP, httpProp.getProperty(ALLOW_IP, ""));
        transactionProp.put("BLOCK_IP", httpProp.getProperty("BLOCK_IP", ""));
        
        //djerp bypass
        Map<String, String> pathVariables = assignPathVariables(request, adptGrpName, adptName, null, transactionProp);
		if (pathVariables != null) {
			transactionProp.put(INBOUND_PATH_VARIABLES, pathVariables);
		}
		
        transactionProp.put(ENC_PATHS, httpProp.getProperty(ENC_PATHS, ""));
        
        transactionProp.put(ENCRYPT_ALGORITHM, httpProp.getProperty(ENCRYPT_ALGORITHM, ""));  //jwhong
        if (inboundHeaderProp.get(HEADER_NAME_CLIENT_ID) != null) {    // jwhong
        	transactionProp.put(HEADER_NAME_CLIENT_ID, inboundHeaderProp.get(HEADER_NAME_CLIENT_ID));  
        }
        transactionProp.put(ENCRYPT_BASE_TYPE, httpProp.getProperty(ENCRYPT_BASE_TYPE, ""));  //jwhong
//        //transactionProp.put(INBOUND_TOKEN, getHeaders(request).get(INBOUND_TOKEN));  // jwhong
//        transactionProp.put(INBOUND_TOKEN, getHeaders(request).get(INBOUND_TOKEN) != null ? getHeaders(request).get(INBOUND_TOKEN) : "");  //jwhong , null 이면 default로 "" put
        String inboundToken = inboundHeaderProp.getOrDefault(INBOUND_TOKEN, "").toString();
        if (StringUtils.isNotBlank(inboundToken) && StringUtils.startsWith(inboundToken, "Bearer ")) {
        	inboundToken = inboundToken.substring(7);
        }
        transactionProp.put(INBOUND_TOKEN, inboundToken);
        transactionProp.put(ENCRYPT_AES256_IV, httpProp.getProperty(ENCRYPT_AES256_IV, ""));  //jwhong
        transactionProp.put(ENCRYPT_AES256_KEY, httpProp.getProperty(ENCRYPT_AES256_KEY, ""));  //jwhong
        
        transactionProp.put(AbstractCryptoFilter.PROP_MODULE_NAME, httpProp.getProperty(AbstractCryptoFilter.PROP_MODULE_NAME, ""));
        
        transactionProp.put(HEADER_GROUP, headerGroupName);
        transactionProp.put(HEADER_KEYS, relayRequestHeaderKeys);
        
        transactionProp.put(ADAPTER_TOKEN_HEADER_NAME, httpProp.getProperty(ADAPTER_TOKEN_HEADER_NAME, ""));  // OAuth 인증 토큰 헤더 이름
        transactionProp.put(ADAPTER_APIKEY_HEADER_NAME, httpProp.getProperty(ADAPTER_APIKEY_HEADER_NAME, ""));  // API-KEY 인증 헤더 이름
        // SEED 컬럼암호하 시  Key로 사용함
        String seedkey = inboundHeaderProp.getOrDefault("x-obp-partnercode", "").toString();
        ElinkTransactionContext.setSeedKey(seedkey);

        try {
            traceLevel = Integer.parseInt(traceLevelTemp);
        } catch (Exception e) {
            traceLevel = 0;
        }

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

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

        switch (HttpMethodType.getValue(request.getMethod())) {
            case GET:
            case DELETE:
                isParameterType = true;
                break;
            case POST:
            case PUT:
                if (StringUtils.contains(request.getContentType(), "application/x-www-form-urlencoded")) {
                    isParameterType = true;
                } else if ( StringUtils.isNoneBlank(request.getQueryString()) )  { // jwhong
                	isParameterType = true;
                } else {
                    isParameterType = false;
                }
                break;
            default:
                break;
        }

        if (isParameterType) {
            paramValue = request.getQueryString();
            transactionProp.put(INBOUND_QUERY_STRING, StringUtils.defaultString(paramValue));  // Filter에서 QueryString 검증을 위해 저장 
            if (paramValue == null)
                paramValue = "";
            if (traceLevel >= 3) {
                HttpMemoryLogger.txlog(adptGrpName + adptName,
                        "RECV " + "[" + paramValue + "]" + CommonLib.getDumpMessage(paramValue));
            }

            // json으로 변환
            StringBuilder sb = new StringBuilder();
            sb.append("{");
            Map<String, String[]> paramMap = assignParameterMap(request, adptGrpName, adptName, null, transactionProp);
            int i = 0;
            for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
                if (i > 0) {
                    sb.append(",");
                }
                sb.append("\"").append(entry.getKey()).append("\":");
                String[] values = entry.getValue();
                if (values.length > 1) {
                    // ["111", "222"]
                    sb.append("[");
                    for (int j = 0; j < values.length; j++) {
                        if (j > 0) {
                            sb.append(",");
                        }
                        sb.append("\"").append(JSONValue.escape(values[j])).append("\"");
                    }
                    sb.append("]");
                } else {
                    sb.append("\"").append(JSONValue.escape(values[0])).append("\"");
                }

                i++;
            }

            sb.append("}");

            paramValue = sb.toString();
            
            if (logger.isDebug()) {
                logger.debug("HttpAdapterServiceRest] RECV (" + adptGrpName + ") = [" + paramValue + "]\n"
                        + CommonLib.getDumpMessage(paramValue.getBytes(encode)));
            }
		} else {
		    if (request.getContentLength() > 0) {
			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);
			String bodyEncode = encode;
			String contentTypeHeader = request.getContentType();
			if (StringUtils.isNotBlank(contentTypeHeader)) {
			    try {
				MediaType mediaType = MediaType.parseMediaType(contentTypeHeader);
				if (mediaType.getCharset() != null) {
				    bodyEncode = mediaType.getCharset().name();
				}
			    } catch (Exception ignored) {}
			}
			paramValue = new String(data, bodyEncode);
	
			if (traceLevel >= 3) {
			    HttpMemoryLogger.txlog(adptGrpName + adptName,
				    "RECV " + "[" + paramValue + "]" + CommonLib.getDumpMessage(data));
			}
	
			if (logger.isDebug()) {
			    logger.debug("HttpAdapterServiceRest] RECV (" + adptGrpName + ") = [" + paramValue + "]\n"
				    + CommonLib.getDumpMessage(data));
			}
		    }
		}

        if (paramValue == null) { // parameter가 없는 경우때문에 처리
            paramValue = "";
        }
        // 순수한 Body값을 저장을 위해 위치 변경.
        transactionProp.put(INBOUND_REQUEST_MESSAGE, paramValue);

        // paramValue가 null이 아닌 빈문자열(""," ")인 경우에 대비하여 조건 수정.
//        if (StringUtils.isNotBlank(paramValue)) {      // jwhong decrypt
//        	paramValue = doPreDecryption(paramValue, transactionProp, request);
//        }

        if ("Y".equals(urlDecodeYn) && isParameterType) {
            message = URLDecoder.decode(paramValue);
        } else {
            message = paramValue;
        }
        
        TxFileLogger.logTxFile(transactionProp, message, "[IN_RECV]");

        if (logger.isDebug()) {
            String[] msgArgs = new String[2];
            msgArgs[0] = adptGrpName;
            msgArgs[1] = message;
            String resMsg = ExceptionUtil.make("RICEAIAHA005", msgArgs);
            logger.debug(resMsg);
        }


        adptMsgType = adapterVO.getAdapterGroupVO().getMessageType();
        
        Object reqObject = assignHeaderGroupRequestHeaders(adptGrpName, adptName, message,
        		transactionProp, request, response, adptMsgType);

        // 위치변경 : 가공되지 않은 Body값을 저장하기 위하여 위쪽으로 이동.
//        transactionProp.put(INBOUND_REQUEST_MESSAGE, message);
        
        ApiKeyExtractFilter apiKeyExtractor = new ApiKeyExtractFilter();
        apiKeyExtractor.doPreFilter(adptGrpName, adptName, transactionProp.get(INBOUND_REQUEST_MESSAGE), transactionProp, request, response);
        
        Object result = service(adptGrpName, adptName, reqObject, transactionProp, request, response);
        
		// bypass만 필요
        applyOutboundResponseHeaders(transactionProp, response);
        
        result = applyHeaderGroupResponseHeaders(adptGrpName, adptName, result, transactionProp, request, response, adptMsgType);
		
		if (logger.isDebug()) {
            logger.debug("HttpAdapterServiceRest] result " + encode + " (" + adptGrpName + ") = [" + result + "]");
        }

        stopWatch.stop();

        String syncAsyncType = transactionProp.getProperty(INBOUND_SYNC_ASYNC_TYPE);
        
        if (!"ASYN".equals(syncAsyncType) && MessageType.JSON.equals(adptMsgType) && StringUtils.isNotBlank(headerGroupName)) {
        	ObjectNode rootNode = (ObjectNode) JacksonUtil.readTree(result, mapper);
        	JsonNode headerGroup = rootNode.get(headerGroupName);
        	if(headerGroup != null) {
        		for(Iterator<String> it = headerGroup.fieldNames(); it.hasNext();) {
    				String name = it.next();
    				String value = headerGroup.get(name).asText();
    				response.addHeader(name, value);
        		}
        		rootNode.remove(headerGroupName);
        		result = rootNode;
        	}
        }
        
        String strResult = null;
        if(result instanceof JsonNode)
        	strResult = OBJECT_MAPPER.writeValueAsString(result);
        else
        	strResult = (String) result;
        
        return strResult;
    }


	private Object assignHeaderGroupRequestHeaders(String adptGrpName, String adptName, Object message, Properties prop,
			HttpServletRequest request, HttpServletResponse response, String adptMsgType) throws DocumentException, IOException, Exception {
		// XML 지원 개발
		String headerGroupName = prop.getProperty(HEADER_GROUP);
		String relayRequestHeaderKeys = prop.getProperty(HEADER_KEYS);
        if(StringUtils.isNotBlank(headerGroupName) && StringUtils.isNotBlank(relayRequestHeaderKeys)) {
			if (MessageType.JSON.equals(adptMsgType)) {
				if(message instanceof String && StringUtils.isEmpty((String)message))
					message = "{}";
				
				JsonNode jsonMessageNode = JacksonUtil.readTree(message, mapper);
				if (jsonMessageNode == null || !jsonMessageNode.isObject()) {
				    jsonMessageNode = OBJECT_MAPPER.createObjectNode();
				}
				ObjectNode jsonNode = (ObjectNode) jsonMessageNode;

				JsonNode childNode = jsonNode.get(headerGroupName);
				ObjectNode headerJson = (childNode != null && childNode.isObject())
				        ? (ObjectNode) childNode
				        : OBJECT_MAPPER.createObjectNode();

				if (StringUtils.equalsIgnoreCase(relayRequestHeaderKeys, "ALL")) {
				    for (Enumeration<String> e = request.getHeaderNames(); e.hasMoreElements(); ) {
				        String key = e.nextElement();
				        headerJson.put(StringUtils.lowerCase(key), request.getHeader(key));
				    }
				} else {
				    String[] relayKeyArr = org.springframework.util.StringUtils
				            .tokenizeToStringArray(relayRequestHeaderKeys, ",");

				    for (String key : relayKeyArr) {
				        String headerValue = request.getHeader(key);
				        if (StringUtils.isNotBlank(headerValue)) {
				            headerJson.put(StringUtils.lowerCase(key), headerValue);
				        }
				    }
				}

				if (!headerJson.isEmpty()) {
				    jsonNode.set(headerGroupName, headerJson);
				    message = jsonNode;
				}
			} else if(MessageType.XML.equals(adptMsgType)) {
            	Document document = XMLUtils.convertXmlDocument(message);
            	if(document != null) {
            		Element rootElement = document.getRootElement();
            		Element headerElement = rootElement.element(headerGroupName);
            		if(headerElement == null)
            			headerElement = DocumentHelper.createElement(headerGroupName);
            		
            		boolean hasHeaderValues = false;
            		 if (StringUtils.equalsIgnoreCase(relayRequestHeaderKeys, "ALL")) {
                         for (Enumeration<String> e = request.getHeaderNames(); e.hasMoreElements(); ) {
                             String key = e.nextElement();
                             String value = request.getHeader(key);
                             /*
                              *  IBK 표준안에 따라 <{key}>{value}</{key}> 형태로 저장, KEY 는 소문자로 변경한다
                              *  대상 사이트의 표준안에 따라 작성 할 것
                              *  
                              *  HTTP 헤더 변환 예시
                              *  
                              *  일반적인 HTTP 헤더 변환 예시:
                              *  Content-Type: application/json
                              *  X-Forwarded-For: 10.0.0.1
                              *  
                              *  <root>
                              *  	<data> original content </data>
                              *  	<httpHeaderGroup>
                              *  		<content-type>application/json</content-type>
                              *  		<x-forwarded-for>10.0.0.1</x-forwarded-for>
                              *  	</httpHeaderGroup>
                              *  </root>
                              */
                             if(StringUtils.isNotBlank(value)) {
                            	 Element headerItem = headerElement.addElement(key);
                            	 headerItem.setText(value);
                            	 hasHeaderValues = true;
                             }
                         }
                     } else {
                         String[] relayKeyArr = org.springframework.util.StringUtils
                                 .tokenizeToStringArray(relayRequestHeaderKeys, ",");

                         for (String key : relayKeyArr) {
                             String headerValue = request.getHeader(key);
                             if (StringUtils.isNotBlank(headerValue)) {
                                 String value = request.getHeader(key);
                                 String keyName = StringUtils.lowerCase(key);
                            	 Element headerItem = headerElement.addElement(keyName);
                            	 headerItem.setText(value);
                            	 hasHeaderValues = true;
                             }
                         }
                     }
            		 
                     if (hasHeaderValues) {
                    	 rootElement.add(headerElement);
                    	 OutputFormat format = OutputFormat.createPrettyPrint();
                    	 StringWriter writer = new StringWriter();
                    	 XMLWriter xmlWriter = new XMLWriter(writer, format);
                    	 xmlWriter.write(document);
                         message = writer.toString();
                     }
            	}
            }
		}
        if (message == null) {
            message = "";
        } // XML 지원 개발
		return message;
	}
    
    
    /**
     * HttpClientAdapterServiceBypass.assignRelayDataToInbound() 에서
     * transactionProp에 저장한 OUTBOUND_RESPONSE_HEADERS를 response에 적용한다.
     */
    @SuppressWarnings("unchecked")
    private void applyOutboundResponseHeaders(Properties transactionProp, HttpServletResponse response) {
        Map<String, Object> outboundPropertyMap = (Map<String, Object>) transactionProp.get(OUTBOUND_PROPERTY_MAP);
        if (outboundPropertyMap == null) {
            return;
        }
        Properties headerProp = (Properties) outboundPropertyMap.get(OUTBOUND_RESPONSE_HEADERS);
        if (headerProp == null) {
            return;
        }

        String[] hopByHopHeaders = { "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization",
                "TE", "Trailers", "Transfer-Encoding", "Upgrade" };

        for (Map.Entry<Object, Object> entry : headerProp.entrySet()) {
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();

            if (StringUtils.equalsAnyIgnoreCase(key, hopByHopHeaders)) {
                continue;
            }
            if (StringUtils.equalsIgnoreCase(key, HttpHeaders.CONTENT_LENGTH)) {
                continue;
            }
            if (StringUtils.equalsIgnoreCase(key, HTTP_STATUS)) {
                if (StringUtils.isNotBlank(value)) {
                    try {
                        response.setStatus(Integer.parseInt(value));
                    } catch (NumberFormatException ex) {
                        // ignore
                    }
                }
                continue;
            }
            response.addHeader(key, value);
        }
    }

    private Object applyHeaderGroupResponseHeaders(String adptGrpName, String adptName, Object resultMessage, Properties prop,
			HttpServletRequest request, HttpServletResponse response, String adptMsgType) throws Exception {
		// body headergroup -> http header 세팅
		String headerGroupName = prop.getProperty(HEADER_GROUP);
        
        if (StringUtils.isNotBlank(headerGroupName)) {
			if (resultMessage instanceof Node) {
				adptMsgType = MessageType.XML;
			} else if (resultMessage instanceof String) {
				String strMessage = (String) resultMessage;
				if (StringUtils.startsWith(strMessage, "<")) {
					adptMsgType = MessageType.XML;
				}
			} else if (resultMessage instanceof byte[]) {
				String strMessage = new String((byte[]) resultMessage);
				if (StringUtils.startsWith(strMessage, "<")) {
					adptMsgType = MessageType.XML;
				}
			}

			if (MessageType.JSON.equals(adptMsgType)) {
				JsonNode jsonNode = JacksonUtil.readTree(resultMessage, OBJECT_MAPPER); // 또는
																						// objectMapper.readTree(resultMessage)
				JsonNode headerGroup = jsonNode.get(headerGroupName);

				if (headerGroup != null && !headerGroup.isNull()) {
					Iterator<Map.Entry<String, JsonNode>> fieldIterator = headerGroup.fields();

					while (fieldIterator.hasNext()) {
						Map.Entry<String, JsonNode> fieldEntry = fieldIterator.next();
						JsonNode valueNode = fieldEntry.getValue();

						logger.info("header group entry : {} , {}", fieldEntry.getKey(), valueNode);

						if (valueNode.isTextual()) {
							String value = valueNode.asText();

							if (StringUtils.equalsIgnoreCase(fieldEntry.getKey(), "content-type")) {
								logger.debug("set content-type {}: {}", fieldEntry.getKey(), value);
								response.setContentType(value);
							} else if (StringUtils.equalsIgnoreCase(fieldEntry.getKey(), HttpHeaders.CONTENT_LENGTH)) {
								logger.debug("skip content-length {}: {}", fieldEntry.getKey(), value);
								continue;
				            } else {
								logger.debug("add header {}: {}", fieldEntry.getKey(), value);
								response.addHeader(fieldEntry.getKey(), value);
							}
						} else {
							logger.info("fieldEntry value is not String. {}", valueNode);
						}
					}
				} else {
					logger.info("headerGroup is null");
				}

				((ObjectNode) jsonNode).remove(headerGroupName);
				return jsonNode;
        	} else if(MessageType.XML.equals(adptMsgType)) {
        		Document document = XMLUtils.convertXmlDocument(resultMessage);
            	if(document != null) {
            		Element rootElement = document.getRootElement();            		
            		Element headerGroupElement = rootElement.element(headerGroupName);
            		
            		if(headerGroupElement != null) {
            			//헤더그룹의 모든 하위 엘리먼트를 HTTP 응답 헤더로 설정
            			for(Iterator<Element> it = headerGroupElement.elementIterator(); it.hasNext();) {
            				Element headerElement = it.next();
            				String name = headerElement.getName();
            				String value = headerElement.getTextTrim();

            				if(StringUtils.isNoneBlank(value)) {
            					if(StringUtils.equalsIgnoreCase(name, "content-type")) {
            						logger.debug("set content-type {}: {}", name, value);
    								response.setContentType(value);
            					} else if(StringUtils.equalsIgnoreCase(name, "content-length")) {
            						logger.debug("skip content-length {}: {}", name, value);
    								continue;
            					} else {
            						logger.debug("add header {}: {}", name, value);
            						response.addHeader(name, value);
            					}
            				} else {
    							logger.info("value is not String. {}", value);
    						}
            			}
            		} else {
            			logger.info("headerGroup is null");
            		}
            		
            		// 헤더그룹 삭제
            		rootElement.remove(headerGroupElement);
            		
            		return document.asXML();
            	}
        	}
		} else {
			logger.info("headerGroupName is blank");
		}
			
		return (String)resultMessage;
	}

    private Map<String, String[]> assignParameterMap(HttpServletRequest request, String adptGrpName, String adptName,
                                                     Object requestBytes, Properties prop) {
        // PathVariable 체크
        if (StringUtils.equalsAnyIgnoreCase(request.getMethod(), HttpMethod.GET.name(), HttpMethod.DELETE.name())
                && 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, "{")) {
                    Map<String, String> paramMap = new AntPathMatcher().extractUriTemplateVariables(ruledPath,
                            requestPath);
                    if (paramMap != null && paramMap.size() > 0) {
                        Map<String, String[]> returnMap = new HashMap<>();
                        for (String key : paramMap.keySet()) {
                            if (StringUtils.equalsIgnoreCase(key, "method")) {
                                continue;
                            }
                            returnMap.put(key, new String[] { paramMap.get(key) });
                        }

                        return returnMap;
                    }
                }
            } catch (Exception e) {
                logger.error(e.getMessage());
            }
        }

        return request.getParameterMap();
    }
    
    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;
	}
	
    private Properties getHeaders(HttpServletRequest request) {
        Properties prop = new Properties();

        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            // 동일 이름의 헤더가 여러 개인 경우 콤마로 합침 (RFC 7230)
            Enumeration<String> values = request.getHeaders(key);
            StringBuilder sb = new StringBuilder();
            while (values.hasMoreElements()) {
                if (sb.length() > 0) sb.append(", ");
                sb.append(values.nextElement());
            }
            prop.setProperty(key.toLowerCase(), sb.toString());
        }

        return prop;
    }

    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(), "/");
    }

    @Override
    @Deprecated
    public void service(String adptGrpName, String adptName, HttpServletRequest request, HttpServletResponse response) {

    }

    /**
     * HTTP 요청에서 클라이언트 IP를 추출합니다.
     * X-Forwarded-For 헤더가 있는 경우 이를 우선적으로 사용하고,
     * 없는 경우 remoteAddr을 사용합니다.
     */
    private String getClientIp(HttpServletRequest request) {
        String ipAddress = request.getHeader("X-Forwarded-For");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
        }
        return ipAddress;
    }
    
	private String assignApiId(String adptGrpName, String adptName, Object message, Properties prop,
			HttpServletRequest request) {
		String apiId = null;
		try {
			String actionName = prop.getProperty(Processor.REQUEST_ACTION);
			RequestAction action = ActionFactory.createAction(actionName);
			action.setAdapterInfo(adptGrpName, adptName, prop);
			String[] keys = action.perform(message);
			apiId = keys[0];

			// PathVariable 지원 추가
			apiId = StandardMessageUtil.getMatchedKey(apiId, actionName);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}

//		// header 에서 확보
//		String apiId = request.getHeader(HEADER_NAME_API_CODE);
//		if (StringUtils.isBlank(apiId)) {
//			// url에서 확보
//			// /ONLWeb/api/v1/public/getUserInfo.svc/
//			apiId = request.getRequestURI();
//			// /ONLWeb/api/v1/public/getUserInfo.svc
//			apiId = StringUtils.removeEnd(apiId, "/");
//			// getUserInfo.svc
//			apiId = StringUtils.substringAfterLast(apiId, "/");
//		}

		return apiId;
	}
}
