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

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletException;
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.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import com.eactive.eai.adapter.AdapterGroupVO;
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.client.HttpClientAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.UnknownMessageLogUtils;
import com.eactive.eai.common.TransactionContextKeys;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.util.CommonLib;
import com.eactive.eai.common.util.HttpAdapterExtraLogUtil;
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;

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;
    	
    	AdapterGroupVO adapterGroupVo = null;
    	Properties prop = null;
    	byte[] requestBytes = null;
    	String encode = "MS949";
        try {
            AdapterManager adapterManager = AdapterManager.getInstance();
            AdapterVO adptVO = adapterManager.getAdapterVO(adptGrpName,adptName);    
            adapterGroupVo = adptVO.getAdapterGroupVO();
            AdapterPropManager manager = AdapterPropManager.getInstance();
            
            if(adptVO == null) {
            	throw new ServletException("Adapter not Found adptName="+adptName);
            }
            
            Properties httpProp  = manager.getProperties(adptVO.getPropGroupName());
            encode = StringUtils.defaultIfBlank(adapterManager.getAdapterGroupVO(adptGrpName).getMessageEncode(), "MS949");
            String responseType  = httpProp.getProperty(RESPONSE_TYPE,"SYNC");
            String urlDecodeYn   = httpProp.getProperty(URL_DECODE_YN,"N");
            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 + "]");
    		
			
            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);
            }

            prop = new Properties();
            prop.put(INBOUND_METHOD, request.getMethod());
			prop.put(INBOUND_URI, request.getRequestURI());
			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(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, ""));
			
//			String body = new String ( requestBytes,encode);
//
//			try {
//				Object root = JSONValue.parse(body);
//				if( root instanceof JSONArray ) {
//					JSONObject wrappedObject = new JSONObject();
//					wrappedObject.put("DJB_ROOTLESS_ARRAY", root);
//					requestBytes = wrappedObject.toJSONString().getBytes(encode);
//				}
//				
//			}catch (Exception e) {
//				// ignore json이 아니기에 해줄것이 없음.
//			}
			
            // 로컬 서비스 호출
            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 apiInternalErrorCode = prop.getProperty("API_INTERNAL_ERROR_CODE", "");
            if(StringUtils.isNotEmpty(apiInternalErrorCode))
            	response.setStatus(500);
            
            byte[] responseBytes = null;
            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);
					responseBytes = responseData.getBytes();
				} else if (result instanceof byte[]) {
					responseData = new String((byte[]) result, encode);
					responseBytes = (byte[]) result;
				} else if (result instanceof String) {
					responseBytes = ((String)result).getBytes();
					if ( StringUtils.isBlank((String)result ) ) {
						responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
						responseBytes = responseData.getBytes();
					}
				}
				
                response.setCharacterEncoding(encode);
//            	response.getWriter().print(responseData);
            	response.getOutputStream().write(responseBytes);
            	
                if (traceLevel >= 3){
                	HttpMemoryLogger.txlog(adptGrpName+adptName, "SEND "+"["+responseData+"]"+
                			((responseData == null)?"":CommonLib.getDumpMessage(responseData))
                	);
                }              	
            } else {
                if(requestBytes != null) {
                	if ( result == null) {
    					responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName);
    					responseBytes = responseData.getBytes();
    				} else if (result instanceof byte[]) {
    					responseData = new String((byte[]) result, encode);
    					responseBytes = (byte[]) result;
    				} else if (result instanceof String) {
    					responseBytes = ((String)result).getBytes(encode);
    					if ( StringUtils.isBlank((String)result ) ) {
    						responseData = ElinkConfig.getAsyncDummyDataForAdapterGroup(adptGrpName, adptName);
    						responseBytes = responseData.getBytes(encode);
    					}
    				}

        			logger.info("종료 >> encode = [" + encode + "]");
                	
                	if (addData.length() > 0){
                		responseData = addData + responseData;
                		responseBytes = responseData.getBytes();
                	}
                	
//                	try {
//    					Object root = JSONValue.parse(responseData);
//    					if( root instanceof JSONObject ) {
//    						JSONObject wrappedObject = (JSONObject) root;
//    						responseData = wrappedObject.get("DJB_ROOTLESS_ARRAY").toString();
//    					}
//    					
//    				}catch (Exception e) {
//    					// ignore json이 아니기에 해줄것이 없음.
//    				}
   
                	// 300응답에서 받은 Header정보 사용하기 위해 읽어옴.
//                	Map<String, Object> responsePropertyMap = (Map<String, Object>) prop.get(OUTBOUND_PROPERTY_MAP);
//					if(responsePropertyMap != null){
//						Properties responseHeaders = (Properties)responsePropertyMap.get(OUTBOUND_RESPONSE_HEADERS);
//						Map<String, String> responseHeaderMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
//						// Properties 내용을 모두 복사
//						for (String name : responseHeaders.stringPropertyNames()) {
//							responseHeaderMap.put(name, responseHeaders.getProperty(name));
//						}
//						String contentTytpe = responseHeaderMap.get("Content-Type");
//						response.setContentType(contentTytpe); // 300응답에서 받은 Content-Type을 그대로 적용.
//					}

        			// UI와 통신시(UTF-8) 변환오류로 ENCODE 제거
                    response.setCharacterEncoding(encode);
//                	response.getWriter().print(responseData);
                    String resContentType = request.getContentType();
                    response.setContentType(resContentType);
                	response.getOutputStream().write(responseBytes);

                	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());
			
			if( "JSN".equals( adapterGroupVo.getMessageType() ) ) {
				try {
					String errorMsg = MessageUtil.makeJsonErrorMessage( MessageUtil.ERROR_CODE_AP_ERROR,   e.getMessage());
					response.getWriter().println(errorMsg);
				} catch (Exception ex) {
					// IGNORE
				}
			}else {
				try {
					String errorMsg = String.format("(%d) %s",	e.getStatus(), e.getMessage());
					response.getWriter().println(errorMsg);
				} catch (Exception ex) {
					// IGNORE
				}
			}
			
        } catch (Exception e) {
        	try {
				UnknownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, requestBytes != null ? new String(requestBytes, encode) : null, prop, request, response,
						"RECEAIIRP202", e);
			} catch (UnsupportedEncodingException e1) {
				UnknownMessageLogUtils.logUnkownMessage(adptGrpName, adptName, requestBytes != null ? new String(requestBytes) : null, prop, request, response,
						"RECEAIIRP202", e);
			}
        	if (traceLevel >= 3){
               	HttpMemoryLogger.error(adptGrpName+adptName, e.toString(),e);
    		}
            logger.error("HttpAdapter] "+adptGrpName+"-"+adptName, e);
            response.setStatus(500);
            
            if( "JSN".equals( adapterGroupVo.getMessageType() ) ) {
            	try {
            		String errorMsg = MessageUtil.makeJsonErrorMessage( MessageUtil.ERROR_CODE_AP_ERROR,   e.getMessage());
					response.getWriter().println(errorMsg);
                    throw new Exception(MessageUtil.ERROR_CODE_AP_ERROR);
                } catch (Exception ex) {
                	logger.warn("error", ex);
                }
            }else {
            	try {
                    response.getWriter().println(e.getMessage());
                    String errCode = ExceptionUtil.getErrorCode(e, "RECEAIAHA003");
                    throw new Exception(errCode);
                } catch (Exception ex) {
                	logger.warn("error", ex);
                }
            }
            
            
        }finally {
        	String uuid = prop.getProperty(TransactionContextKeys.TRANSACTION_UUID);
			String url = prop.getProperty(HttpClientAdapterServiceKey.INBOUND_EXTURI);
			String method = prop.getProperty(HttpClientAdapterServiceKey.INBOUND_METHOD);
			String adapterGroupName = prop.getProperty(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
			String adapterName = prop.getProperty(HttpClientAdapterServiceKey.ADAPTER_NAME);
			int httpStatusCode = response.getStatus();
			Map<Object, Object> headerMap = new HashMap<>();
			// 응답 헤더 로깅
			for (String headerName : response.getHeaderNames()) {
				String headerValue = response.getHeader(headerName);
				headerMap.put(headerName, headerValue);
			}
			HttpAdapterExtraLogUtil.insertHttpAdapterExtraLog(uuid, 400, adapterGroupName, adapterName, headerMap, url, method, httpStatusCode);
		}
    }

	/**
	 * 어댑터 명 이후의 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;
	}
} 
