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

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.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.processor.Processor;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.mina.common.ByteBuffer;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

public class HttpAdapterServiceStandard extends HttpAdapterServiceSupport
{ 
	public static final String PROPERTIES_NAME_HTTP_REQUEST_METHOD = "httpRequestMethod";
    /*
     * Adapter Logger
     */
    static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
    
    /** 
    * 1. 기능 : Http Servlet service 구현 메소드
    * 2. 처리 개요 : 
    *     - 로컬 서비스 호출
    * 3. 주의사항 
    *
    * @param    request Http Servlet Request
    * @return   response    Http Servlet Response
    * @exception    Exception   PARAMETER_MESSAGE가 null 이거나 로컬 서비스 호출 중 오류가 발생할 경우
    **/ 
    public void service(String adptGrpName, String adptName, HttpServletRequest request, HttpServletResponse response) {
    	int traceLevel = 0;
        try {
            AdapterManager adapterManager = AdapterManager.getInstance();
            AdapterVO adptVO = adapterManager.getAdapterVO(adptGrpName,adptName);    

            AdapterPropManager manager = AdapterPropManager.getInstance();
            
            if(adptVO == null) {
            	throw new ServletException("Adapter not Found adptName="+adptName);
            }
            
            Properties httpProp  = manager.getProperties(adptVO.getPropGroupName());
            String responseType  = httpProp.getProperty(RESPONSE_TYPE,"SYNC");
            String urlDecodeYn   = httpProp.getProperty(URL_DECODE_YN,"N");
            String encode = StringUtils.defaultIfBlank(adapterManager.getAdapterGroupVO(adptGrpName).getMessageEncode(), "MS949");
            String messageType = adapterManager.getAdapterGroupVO(adptGrpName).getMessageType();
            String requestBodyYn = httpProp.getProperty(REQUEST_BODY_YN,"N");
            String parameterName = httpProp.getProperty(PARAMETER_NAME,PARAMETER_MESSAGE);
            String removeLength  = httpProp.getProperty(REMOVE_LENGTH,"0");
            String addData       = httpProp.getProperty(ADD_DATA,"");
            String traceLevelTemp   = httpProp.getProperty(TRACE_LEVEL,"0");
            try{
            	traceLevel = Integer.parseInt(traceLevelTemp);
            }catch(Exception e){
            	traceLevel = 0;
            }
            
            String paramValue    = null;
            
            int removeLen = 0;
            try{
            	removeLen = Integer.parseInt(removeLength);
            }catch(Exception e){
            	removeLen = 0;
            }
            
    		StopWatch stopWatch = new StopWatch();
    		stopWatch.start();
    		
			logger.info("시작 >> encode = [" + encode + "]");
    		
			byte[] requestBytes = null;
			
            if ("Y".equals(requestBodyYn)){
            	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);
        	    	}
        	    }
        	    requestBytes = new byte[bb.position()];
        	    bb.position(0);
        	    bb.get(requestBytes);
            	if (traceLevel >= 3){
            		HttpMemoryLogger.txlog(adptGrpName+adptName, "RECV "+"["+new String(requestBytes, encode)+"]"+ CommonLib.getDumpMessage(requestBytes));
            	}
            }else{
            	paramValue = request.getParameter(parameterName);
            	if (traceLevel >= 3){
            		HttpMemoryLogger.txlog(adptGrpName+adptName, "RECV "+"["+paramValue+"]"+ 
            				((paramValue == null)?"":CommonLib.getDumpMessage(paramValue))
            			);
            	}

            	if(paramValue==null) 
                	throw new ServletException("RECEAIAHA002");
            	
            	if ("Y".equals(urlDecodeYn)){
            		paramValue = URLDecoder.decode(paramValue);
                }
            	
                if (logger.isDebug()){
                	logger.debug("HttpAdapter] RECV ("+adptGrpName+") = [" + paramValue + "]");
                	logger.debug("HttpAdapter] RECV ("+adptGrpName+") = " +
                			((paramValue == null)?"":CommonLib.getDumpMessage(paramValue))
                			);
                }
                requestBytes = paramValue.getBytes(encode);
            }
            
            if (removeLen > 0 ) {
            	byte[] cuttedBytes = new byte[requestBytes.length-removeLen];
            	System.arraycopy(requestBytes, removeLen, cuttedBytes, 0, cuttedBytes.length);
            	requestBytes = cuttedBytes;
            	if (logger.isDebug()){
                	logger.debug("HttpAdapter] RECV after remove ("+adptGrpName+") = [" + new String(requestBytes, encode) + "]");
                	logger.debug("HttpAdapter] RECV after remove ("+adptGrpName+") = " + CommonLib.getDumpMessage(requestBytes));
                }            	
            }
            
            if(logger.isDebug()){
            	String[] msgArgs = new String[2];
                msgArgs[0] = adptGrpName;
                msgArgs[1] = new String(requestBytes, encode);
                String resMsg = ExceptionUtil.make("RICEAIAHA005", msgArgs);
                logger.debug("HttpAdapter] " + resMsg);
            }

            Properties prop = new Properties();
            prop.put(INBOUND_METHOD, request.getMethod());
			prop.put(INBOUND_URI, request.getRequestURI());
			prop.put(INBOUND_HEADER, getHeaders(request));
			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(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, ""));
			
            // 로컬 서비스 호출
            Object result = null;
            if (MessageUtil.isBytesMessage(messageType)) {
            	result = service(adptGrpName, adptName, requestBytes, prop, request, response);
            } else {
            	result = service(adptGrpName, adptName, new String(requestBytes, encode), prop, request, response);
            }
            
            stopWatch.stop();
            
            String responseData = "";
            if (RESPONSE_TYPE_ASYNC.equals(responseType)){
            	if (stopWatch.getTime() > slowTranTime){
            		if (logger.isWarn()){
            			logger.warn("HttpAdapter] lazy async response time = " + stopWatch.toString() + ", message = "+new String(requestBytes, encode));
            		}
            	}
            	
				if (result == null) {
					responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);
				} else if (result instanceof byte[]) {
					responseData = new String((byte[]) result, encode);
				} else if (result instanceof String) {
					responseData = (String) result;
				}
            	
                response.setCharacterEncoding(encode);
            	response.getWriter().print(responseData);
                if (traceLevel >= 3){
                	HttpMemoryLogger.txlog(adptGrpName+adptName, "SEND "+"["+responseData+"]"+
                			((responseData == null)?"":CommonLib.getDumpMessage(responseData))
                	);
                }              	
            } else {
                if(requestBytes != null) {
                	if (result instanceof byte[]) {
    					responseData = new String((byte[]) result, encode);
    				} else if (result instanceof String) {
    					responseData = (String) result;
    				}
                	
        			logger.info("종료 >> encode = [" + encode + "]");
                	
                	if (addData.length() > 0){
                		responseData = addData + responseData;
                	}
            	    // UI와 통신시(UTF-8) 변환오류로 ENCODE 제거
                	response.setContentType("text/plain");
                    response.setCharacterEncoding(encode);
                	response.getWriter().print(responseData);
                	if (logger.isDebug()){
                		logger.debug("HttpAdapter] SEND ("+adptGrpName+") = [" + responseData + "]");
                		logger.debug("HttpAdapter] SEND ("+adptGrpName+") = " + 
                				((responseData == null)?"":CommonLib.getDumpMessage(responseData))
                		);
                	}
                    if (traceLevel >= 3){
                    	HttpMemoryLogger.txlog(adptGrpName+adptName, "SEND "+"["+responseData+"]"+
                    			((responseData == null)?"":CommonLib.getDumpMessage(responseData))
                    	);
                    }                	
                }            	
            }
        } 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 = String.format("(%d) %s",	e.getStatus(), e.getMessage());
				response.getWriter().println(errorMsg);
			} catch (Exception ex) {
				// IGNORE
			}
        } catch (Exception e) {
    		if (traceLevel >= 3){
               	HttpMemoryLogger.error(adptGrpName+adptName, e.toString(),e);
    		}
            logger.error("HttpAdapter] "+adptGrpName+"-"+adptName, e);
            response.setStatus(500);
            try {
                response.getWriter().println(e.getMessage());
                String errCode = ExceptionUtil.getErrorCode(e, "RECEAIAHA003");
                throw new Exception(errCode);
            } catch (Exception ex) {
            	logger.warn("error", ex);
            }
        }
    }

	/**
	 * 어댑터 명 이후의 URI값을 가져온다.
	 * 
	 * @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(), "/");
	}
    /**
	 * request header 를 hashmap으로 조립
	 * 
	 * @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;
	}
} 
