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

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.lang.time.StopWatch;
import org.apache.commons.lang3.StringUtils;
import org.apache.mina.common.ByteBuffer;
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.HttpMethodType;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.common.dao.DAOException;
import com.eactive.eai.common.dao.DAOFactory;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.property.PropertyLoginKeys;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.session.ChannelSessionFacade;
import com.eactive.eai.common.session.ChannelSessionFacade.LogoutType;
import com.eactive.eai.common.session.SessionHistoryLogger;
import com.eactive.eai.common.session.SessionManager;
import com.eactive.eai.common.session.SessionVO;
import com.eactive.eai.common.sessioninfo.SessionHistoryDTO;
import com.eactive.eai.common.sessioninfo.SessionHistoryVO;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.CommonLib;
import com.eactive.eai.common.util.DatetimeUtil;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.UUIDGenerator;
import com.eactive.eai.env.ElinkConfig;
import com.eactive.eai.inbound.error.InboundErrorInfoDAO;
import com.eactive.eai.inbound.error.InboundErrorInfoVO;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.manager.StandardMessageManager;
import com.eactive.eai.message.parser.StandardReader;
import com.eactive.eai.message.service.InterfaceMapper;
import com.ext.eai.common.stdmessage.STDMessageErrorKeys;
import com.ext.eai.common.stdmessage.STDMessageKeys;

public class HttpAdapterServiceChannel extends HttpAdapterServiceSupport {

	private static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
	private static String instid = null;
	private static String serverName = null;
	static {
		EAIServerManager eaiServerManager = EAIServerManager.getInstance();
		instid = eaiServerManager.getInstId();
		serverName = eaiServerManager.getLocalServerName();
	}
	
	/**
	 * 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) {
		
		try {
			
			ChannelServiceDataBundle bundle = new ChannelServiceDataBundle(adptGrpName, adptName, request, response);
			
			try {
				
				/** SERVICE START **/
				StopWatch stopWatch = new StopWatch();
				stopWatch.start();

				logger.info("시작 >> encode = [" + bundle.getEncode() + "] Method [" + request.getMethod() + "]");
				
				response.addHeader("Access-Control-Allow-Origin", "*");
				response.addHeader("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");
				response.addHeader("Access-Control-Allow-Headers", "Origin,Accept,X-Requested-With,Access-Control-Request-Method,Access-Control-Request-Header,Authorization,Cookie,Content-Type,Content-Length");
				response.addHeader("Access-Control-Expose-Headers", "Set-Cookie,X-Powered-By");
				response.addHeader("Access-Control-Allow-Credentials", "true");
				response.addHeader("Access-Control-Max-Age", "3600");
				
				switch (HttpMethodType.getValue(request.getMethod())) {
				case GET:
				case DELETE:
				case POST:
				case PUT:
					break;
				default:
					if (logger.isInfo()) {
						logger.info("HttpAdapterServiceChannel] SET RESPONSE HEADER FOR ACAO Method[" + request.getMethod() + "] remoteAddress=" + request.getRemoteAddr());
					}
					return;
				}
				
				ChannelResponseDivider responseDivider = new ChannelResponseDivider(bundle);
				
				/** Phase 1 **/
				switch(responseDivider.dividePhase1()) {
				
				case HttpPoll:
					httpPollResponse(request, response, bundle);
					return; // * return service
					
				case DupLoginCheck:
					dupLoginCheckResponse(request, response, bundle);
					return; // * return service
					
				case LoginValidation:
					loginValidationResponse(request, response, bundle);
					return; // * return service
					
				case LoginUuidValidation:
					loginUuidValidationResponse(request, response, bundle);
					return; // * return service
					
				default:
					break; // go phase 2
				}
				
				stopWatch.stop();
				
		        if(logger.isDebug()){
		        	String[] msgArgs = { adptGrpName, new String(bundle.getRequestBytes(), bundle.getEncode()) };  
			        String resMsg = ExceptionUtil.make("RICEAIAHA005", msgArgs);
		            logger.debug("HttpAdapterServiceChannel] " + resMsg);
		        }
				
	            // 로컬 서비스 호출
	            logger.warn("HttpAdapterServiceChannel] REAL CALL HttpSession userId = " + bundle.getUserId() + ", message = " + new String(bundle.getRequestBytes(), bundle.getEncode()));
	            Object obj = service(adptGrpName, adptName, bundle.getRequestBytes(), new Properties(), request, response );
	            AdapterManager manager = AdapterManager.getInstance();
	            AdapterGroupVO group = manager.getAdapterGroupVO(adptGrpName);
	            
	            byte[] result = null;
	            
	            if (obj instanceof byte[]) {
	               result = (byte[])obj;
	            }else if (obj instanceof String){
	               result = ((String)obj).getBytes(StringUtils.defaultIfBlank(group.getMessageEncode(), Charset.defaultCharset().name()));
	            }
				
				/** Phase 2 **/
				switch(responseDivider.dividePhase2()) {
				
				case Async:
					asyncResponse(request, response, bundle, stopWatch, result);
					break;
					
				case LoginTransaction:
					result = loginResponse(request, response, bundle, result);
					break;
					
				case LogoutTransaction:
					result = logoutResponse(request, response, bundle, result);
					break;
					
				default:
					break;
				
				}
				
				String responseData = new String(result, bundle.getEncode()); // UI와 통신시(UTF-8) 변환오류로 ENCODE 제거
				logger.info("종료 >> encode = [" + bundle.getEncode() + "]");

				if (bundle.getAddData().length() > 0) {
					responseData = bundle.getAddData() + responseData;
				}

				response.setContentType("application/json"); // FIXME ContentType application/json으로 임시 셋팅 추후 어댑터 설정으로 변경 필요
				response.setCharacterEncoding(bundle.getEncode()); // UI와 통신시(UTF-8) 변환오류로 ENCODE 제거
				response.getWriter().print(responseData);
				
				if (logger.isDebug()) {
					logger.debug("HttpAdapterServiceChannel] SEND (" + adptGrpName + ") = [" + responseData + "]");
					logger.debug("HttpAdapterServiceChannel] SEND (" + adptGrpName + ") = " + ((responseData == null) ? "" : CommonLib.getDumpMessage(responseData.getBytes(bundle.getEncode()))));
				}
				
				if (bundle.getTraceLevel() >= 3) {
					HttpMemoryLogger.txlog(adptGrpName + adptName, "SEND " + "[" + responseData + "]" + ((responseData == null) ? "" : CommonLib.getDumpMessage(responseData.getBytes(bundle.getEncode()))));
				}
				
				
			} catch (Exception e) {
				if (bundle.getTraceLevel() >= 3) {
					HttpMemoryLogger.error(adptGrpName + adptName, e.toString(), e);
				}
				logger.error("HttpAdapterServiceChannel] " + adptGrpName + "-" + adptName + " guid=" + bundle.getGuid(), 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);
				}
			}
			
			
		} catch (ServletException | IOException e) {
			logger.error("HttpAdapterServiceChannel] " + 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);
			}
		} 
		
	}

	private void httpPollResponse(HttpServletRequest request, HttpServletResponse response, ChannelServiceDataBundle bundle) throws IOException {
		
		String message = new String(bundle.getRequestBytes(), bundle.getEncode());

		if (logger.isInfo()) {
			logger.info("HttpAdapterServiceChannel] HTTPPOLL remoteAddress=" + request.getRemoteAddr() + " response=" + message);
		}
		String userId = message.substring(9, 9 + 9);
		String uuid = message.substring(9 + 9, 58);
		
		String responseMessage;

		SessionVO sVo = SessionManager.getInstance().getHttpLogin(uuid);
		String trimingId = StringUtils.trim(userId);

		if (sVo == null) {
			//ERROR
			logger.warn("HttpAdapterServiceChannel] HTTPPOLL loginId=(" + trimingId + ") uuid[" + uuid + "] HTTPLOGIN not found");
			responseMessage = "POLLERR@@" + userId + uuid;
			
		} else {
			//RESPONSE
			PropManager pManager = PropManager.getInstance();
			String cacheType = pManager.getProperty(SessionManager.SESSION_MANAGER_GROUPNAME,
			SessionManager.SESSION_MANAGER_CACHETYPE, "EHCACHE");
			logger.info("HttpAdapterServiceChannel] HTTPPOLL loginId=(" + trimingId + ") uuid[" + uuid + "] refresh HTTPLOGIN");
			
			ChannelSessionFacade.getInstance().refreshHttpLoginCache(uuid, sVo);

			responseMessage = "POLLRES@@" + userId + uuid;
		}

		logger.info("HttpAdapterServiceChannel] HTTPPOLL loginId=(" + trimingId + ") uuid[" + uuid + "] RESPONSE[" + responseMessage + "]");
		
		response.setCharacterEncoding(bundle.getEncode());
		response.getWriter().print(responseMessage);
		
	}
	
	private void loginValidationResponse(HttpServletRequest request, HttpServletResponse response, ChannelServiceDataBundle bundle) throws Exception {
		
		Properties prop = PropManager.getInstance().getProperties(PropertyLoginKeys.LOGIN);
		
		String CODE = prop.getProperty(PropertyLoginKeys.VALIDATION_LOGIN_CODE, STDMessageErrorKeys.NOT_LOGIN_ERROR_CODE_VALUE);
		String MSG = prop.getProperty(PropertyLoginKeys.VALIDATION_LOGIN_MSG, STDMessageErrorKeys.NOT_LOGIN_ERROR_MSG_VALUE);
		
		logger.error("HttpAdapterServiceChannel] NOTLOGIN OR EXPIRED SESSION userId[" + bundle.getUserId() + "] - RETURN ERROR.");
		
		StandardMessage standardMessage = null;
		StandardMessageManager standardManager = StandardMessageManager.getInstance();
		StandardReader reader = null;
		InterfaceMapper mapper = standardManager.getMapper();
		AdapterGroupVO vo = AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName());
		String messageType = vo.getMessageType();
		String messageEncode = vo.getMessageEncode();
		standardMessage = standardManager.getStandardMessage();
		reader = standardManager.getReader(messageType);
		reader.parse(standardMessage, bundle.getRequestBytes());
		// 표준전문 파싱 후 어댑터의 charset을 설정한다.
		standardMessage.setBizDataCharset(messageEncode);
		standardManager.getMessageCoordinator().coordinateSetStandardMessageError(standardMessage, mapper, "RECEAIAHA020", null);
		String responseData = standardMessage.getDataString(messageType, messageEncode);
	      
//		ExtMessage extMessage = ExtMessageConverter.getExtMessageForStandard(bundle.getRequestBytes(), AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName()).getMessageType());
//		byte[] result = ExtMessageConverter.getDuplicateLoginResponseForStandard(extMessage, "", MSG, CODE, MSG);
//		String responseData = new String(result, bundle.getEncode());
		
//		response.setCharacterEncoding(bundle.getEncode());
		response.setContentType("application/json");// FIXME ContentType application/json으로 임시 셋팅 추후 어댑터 설정으로 변경필요"
		response.setCharacterEncoding(messageEncode);
		response.getWriter().print(responseData);
		
		//정의되지 않는 메시지에 로그추가
		logUnknown(bundle,CODE,MSG); 
		

	}
	private void loginUuidValidationResponse(HttpServletRequest request, HttpServletResponse response, ChannelServiceDataBundle bundle) throws Exception {
		
		Properties prop = PropManager.getInstance().getProperties(PropertyLoginKeys.LOGIN);
		
		String CODE = prop.getProperty(PropertyLoginKeys.VALIDATION_LOGIN_CODE, STDMessageErrorKeys.UUID_MISMATCH_ERROR_CODE_VALUE);
		String MSG = prop.getProperty(PropertyLoginKeys.VALIDATION_LOGIN_MSG, STDMessageErrorKeys.UUID_MISMATCH_ERROR_MSG_VALUE);
		
		logger.error("HttpAdapterServiceChannel] UUID mismatch userId[" + bundle.getUserId() + "] - RETURN ERROR.");
		StandardMessage standardMessage = null;
		StandardMessageManager standardManager = StandardMessageManager.getInstance();
		StandardReader reader = null;
		InterfaceMapper mapper = standardManager.getMapper();
		AdapterGroupVO vo = AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName());
		String messageType = vo.getMessageType();
		String messageEncode = vo.getMessageEncode();
		standardMessage = standardManager.getStandardMessage();
		reader = standardManager.getReader(messageType);
		reader.parse(standardMessage, bundle.getRequestBytes());
		// 표준전문 파싱 후 어댑터의 charset을 설정한다.
		standardMessage.setBizDataCharset(messageEncode);
		standardManager.getMessageCoordinator().coordinateSetStandardMessageError(standardMessage, mapper, "RECEAIAHA022", null);
		String responseData = standardMessage.getDataString(messageType, messageEncode);
//		ExtMessage extMessage = ExtMessageConverter.getExtMessageForStandard(bundle.getRequestBytes(), AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName()).getMessageType());
//		byte[] result = ExtMessageConverter.getDuplicateLoginResponseForStandard(extMessage, "", MSG, CODE, MSG);
//		String responseData = new String(result, bundle.getEncode());
		
		response.setContentType("application/json");// FIXME ContentType application/json으로 임시 셋팅 추후 어댑터 설정으로 변경필요"
		response.setCharacterEncoding(messageEncode);
		response.getWriter().print(responseData);

		//정의되지 않는 메시지에 로그추가
		logUnknown(bundle,CODE,MSG); 
		
	}

	//login validation 오류 로그 추가 2022-11-09
	private void logUnknown(ChannelServiceDataBundle bundle,String errorCode,String errorMsg) throws Exception {
		String uuid = instid + UUIDGenerator.getUUID();
		
		InboundErrorInfoVO errorInfoVO = new InboundErrorInfoVO();
		errorInfoVO.setEaiSvcSno(uuid);                     // EAI서비스일련번호
		errorInfoVO.setAdptBwkGrpNm(bundle.getAdptGrpName());      // 어댑터업무그룹명
		errorInfoVO.setEaiSvcCd("");
		errorInfoVO.setErrCd(errorCode);                           // 에러코드
		errorInfoVO.setErrTxt(errorMsg);                          // 에러내용
		errorInfoVO.setErrTm(DatetimeUtil.getCurrentTimeMillis());                           // 에러발생시각
		errorInfoVO.setErrDstcd("IE");
		errorInfoVO.setBzwkSvcKeyName("");
		errorInfoVO.setBwkDataTxt(new String(bundle.getRequestBytes(), bundle.getEncode()));                      // 업무데이터내용
		errorInfoVO.setEaiSvrInstNm(serverName);                    // EAI서버인스턴스명
		try {
			InboundErrorInfoDAO dao = ApplicationContextProvider.getContext().getBean(InboundErrorInfoDAO.class);
			dao.addInboundErrorInfo(errorInfoVO);
		} catch(DAOException e) {
			if (logger.isError()) logger.error("RequestProcessor] Unknown Message Logging Error", e);            
		}
		
		if (logger.isError()) {
			logger.error("======================================================================" );
			logger.error(" HttpAdapterServiceChannel] GET LOGIN VALIDATION ERROR MESSAGE");
			logger.error(" ADAPTER GROUP NAME [" + bundle.getAdptGrpName() + "]");
			logger.error(" ADAPTER NAME [" + bundle.getAdptName() + "]");
			logger.error(" USER_ID [" + bundle.getUserId() + "]");
			logger.error("======================================================================" );
		} 
	}
	
	private void dupLoginCheckResponse(HttpServletRequest request, HttpServletResponse response, ChannelServiceDataBundle bundle) throws Exception {
		
		Properties prop = PropManager.getInstance().getProperties(PropertyLoginKeys.LOGIN);
		
		logger.error("HttpAdapterServiceChannel] LOGIN-DUPLICATED loginUserId= " + bundle.getUserId());
		
		String DUPLICATE_LOGIN_CODE = prop.getProperty(PropertyLoginKeys.DUPLICATE_LOGIN_CODE, STDMessageErrorKeys.DUP_LOGIN_ERROR_CODE_VALUE);
		String DUPLICATE_LOGIN_MSG = prop.getProperty(PropertyLoginKeys.DUPLICATE_LOGIN_MSG, STDMessageErrorKeys.DUP_LOGIN_ERROR_MSG_VALUE);
		StandardMessage standardMessage = null;
		StandardMessageManager standardManager = StandardMessageManager.getInstance();
		StandardReader reader = null;
		InterfaceMapper mapper = standardManager.getMapper();
		AdapterGroupVO vo = AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName());
		String messageType = vo.getMessageType();
		String messageEncode = vo.getMessageEncode();
		standardMessage = standardManager.getStandardMessage();
		reader = standardManager.getReader(messageType);
		reader.parse(standardMessage, bundle.getRequestBytes());
		// 표준전문 파싱 후 어댑터의 charset을 설정한다.
		standardMessage.setBizDataCharset(messageEncode);
		standardManager.getMessageCoordinator().coordinateSetStandardMessageError(standardMessage, mapper,"RECEAIAHA021", null);
		String responseData = standardMessage.getDataString(messageType, messageEncode);     
		
//		response.setCharacterEncoding(bundle.getEncode());
		response.getWriter().print(responseData);
		
		String remoteIp = request.getHeader("X-Forwarded-For") != null ? request.getHeader("X-Forwarded-For") : request.getRemoteAddr();
		
		SessionHistoryLogger.getInstance().log(
				new SessionHistoryDTO(new String(bundle.getRequestBytes(), bundle.getEncode()), SessionHistoryVO.HISTORYDSTCD_DUPLOGIN, "E", null, remoteIp, true));
		
	}
	
	private void asyncResponse(HttpServletRequest request, HttpServletResponse response, ChannelServiceDataBundle bundle, StopWatch stopWatch, byte[] serviced) throws IOException {
		
		if (stopWatch.getTime() > slowTranTime) {
			if (logger.isWarn()) {
				logger.warn("HttpAdapterServiceChannel] lazy async response time = " + stopWatch.toString() + ", message = " + new String(bundle.getRequestBytes(), bundle.getEncode()));
			}
		}
		
		String responseData = serviced == null ? ElinkConfig.getAsyncDummyData("K") : new String(serviced, bundle.getEncode());
		
		response.setCharacterEncoding(bundle.getEncode());
		response.getWriter().print(responseData);
		
		if (bundle.getTraceLevel() >= 3) {
			HttpMemoryLogger.txlog(bundle.getAdptGrpName() + bundle.getAdptName(), "SEND " + "[" + responseData + "]" + ((responseData == null) ? "" : CommonLib.getDumpMessage(responseData.getBytes())));
		}
		
	}
	
	private byte[] loginResponse(HttpServletRequest request, HttpServletResponse response, ChannelServiceDataBundle bundle, byte[] serviced) throws Exception {
		
		String uuid = "UUID" + UUIDGenerator.getUUID();
		// set uuid to STDMessage Header MCI_INTF_SESN_ID
		/*
		 * 응답전문 변경 및 로그인 거래 일 경우에만 수행하므로 개발 용이성을 위해
		 * ExtMessageConverter.getExtMessageForStandard 코드를 유지
		 */
//		ExtMessage resExtMessage = ExtMessageConverter.getExtMessageForStandard(serviced, AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName()).getMessageType());

		// 정상응답인지 확인 : RSPNS_RSLT_DVSN_CD = N
//		String respRsltDvsnCode = resExtMessage.getResponseType();
		StandardMessage standardMessage = null;
		StandardMessageManager standardManager = StandardMessageManager.getInstance();
		StandardReader reader = null;
		InterfaceMapper mapper = standardManager.getMapper();
		AdapterGroupVO vo = AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName());
		String messageType = vo.getMessageType();
		String messageEncode = vo.getMessageEncode();
		standardMessage = standardManager.getStandardMessage();
		reader = standardManager.getReader(messageType);
		reader.parse(standardMessage, serviced);
		// 표준전문 파싱 후 어댑터의 charset을 설정한다.
		standardMessage.setBizDataCharset(messageEncode);
		String respRsltDvsnCode = mapper.getResponseType(standardMessage);
		if (STDMessageKeys.RESPONSE_TYPE_CODE_N.equals(respRsltDvsnCode)) { // FIXME

			// 2023. 12. 12. KAKAOBANK-CARD userId From Response Standard Header
			bundle.setUserId(mapper.getUserId(standardMessage));

			mapper.setSessionId(standardMessage, uuid);
			mapper.setInstCode(standardMessage, EAIServerManager.getInstance().getLocalServerName());
//			resExtMessage.setSessionId(uuid);
//        	STDMessage stdMsg = (STDMessage)resExtMessage.getStdMessage();
//			byte[] bizData = (byte[]) resExtMessage.getBizData()[0];
//			JSONObject resultJson = (JSONObject) JSONValue.parse(new String((byte[]) bizData, MessageType.UJSON_ENCODE));
			
//			serviced = ExtMessageConverter.toJsonBytes(resExtMessage, bundle.getEncode());
			JSONObject resultJson = (JSONObject) JSONValue.parse(standardMessage.getBizData());
			serviced = standardMessage.getDataBytes(messageType, messageEncode);
			ChannelSessionFacade.getInstance().login(bundle.getUserId(), uuid, resultJson);
			
			if (logger.isDebug()) {
				logger.debug("HttpAdapterServiceChannel] loginId=(" + bundle.getUserId() + ") uuid[" + uuid + "]");
			}
			
		} else {
			logger.warn("HttpAdapterServiceChannel] LOGIN Failed RSPNS_RSLT_DVSN_CD =" + respRsltDvsnCode);
		}
		
		String remoteIp = request.getHeader("X-Forwarded-For") != null ? request.getHeader("X-Forwarded-For") : request.getRemoteAddr(); 
		
		SessionHistoryLogger.getInstance().log(
				new SessionHistoryDTO(new String(bundle.getRequestBytes(), bundle.getEncode()), SessionHistoryVO.HISTORYDSTCD_LOGIN, respRsltDvsnCode, uuid, remoteIp, true));
		
		return serviced;
	}
	
	private byte[] logoutResponse(HttpServletRequest request, HttpServletResponse response, ChannelServiceDataBundle bundle, byte[] serviced) throws Exception {
		
//		ExtMessage resExtMessage = ExtMessageConverter.getExtMessageForStandard(serviced, AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName()).getMessageType());
		StandardMessage standardMessage = null;
		StandardMessageManager standardManager = StandardMessageManager.getInstance();
		StandardReader reader = null;
		InterfaceMapper mapper = standardManager.getMapper();
		AdapterGroupVO vo = AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName());
		String messageType = vo.getMessageType();
		String messageEncode = vo.getMessageEncode();
		standardMessage = standardManager.getStandardMessage();
		reader = standardManager.getReader(messageType);
		reader.parse(standardMessage, serviced);
		// 표준전문 파싱 후 어댑터의 charset을 설정한다.
		standardMessage.setBizDataCharset(messageEncode);
		String respRsltDvsnCode = mapper.getResponseType(standardMessage);
	      
		// 정상응답인지 확인 : RSPNS_RSLT_DVSN_CD = N
//		String respRsltDvsnCode = resExtMessage.getResponseType();
		String uuid = "";
		if (STDMessageKeys.RESPONSE_TYPE_CODE_N.contentEquals(respRsltDvsnCode)) {
			SessionVO sessionVo = SessionManager.getInstance().getLogin(bundle.getUserId());
			if (sessionVo == null) {
				logger.warn("HttpAdapterServiceChannel] LOGOUT Login Session Not found userId =" + bundle.getUserId());
			} else {
				uuid = sessionVo.getUuid();
				ChannelSessionFacade.getInstance().logout(sessionVo.getLoginUserId(), sessionVo.getUuid(), false, LogoutType.Logout);
			}
		} else {
			logger.warn("HttpAdapterServiceChannel] LOGOUT Failed RSPNS_RSLT_DVSN_CD =" + respRsltDvsnCode);
		}

		SessionHistoryLogger.getInstance().log(
				new SessionHistoryDTO(new String(bundle.getRequestBytes(), bundle.getEncode()), SessionHistoryVO.HISTORYDSTCD_LOGOUT, respRsltDvsnCode, uuid, null, true));
		
		return serviced;
	}
	
}

class ChannelServiceDataBundle {
	
	private Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
	
	private String adptGrpName;
	private String adptName;
	
	private int traceLevel = 0;
	
	private String responseType; 
	private String urlDecodeYn;
	private String encode;
	private String requestBodyYn;
	private String parameterName;
	private String removeLength;
	private String addData;
	
	private byte[] requestBytes;
	private int removeLen;
	
	/** lazy setting **/
	private String guid;
	private String userId;
	
	private String apiPath;
	private String extUri;
	private String standardApiPath;
	
	public ChannelServiceDataBundle(String adptGrpName, String adptName, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		this.adptGrpName = adptGrpName;
		this.adptName = adptName;
		
		AdapterVO adptVO = AdapterManager.getInstance().getAdapterVO(adptGrpName, adptName);
		if (adptVO == null) {
			throw new ServletException("Adapter not Found adptName=" + adptName);
		}

		Properties httpProp = AdapterPropManager.getInstance().getProperties(adptVO.getPropGroupName());
		
		this.responseType = httpProp.getProperty(HttpAdapterServiceKey.RESPONSE_TYPE, "SYNC");
		this.urlDecodeYn = httpProp.getProperty(HttpAdapterServiceKey.URL_DECODE_YN, "N");
		this.encode = httpProp.getProperty(HttpAdapterServiceKey.ENCODE, "MS949");
//		this.requestBodyYn = httpProp.getProperty(HttpAdapterServiceKey.REQUEST_BODY_YN, "N");
		this.requestBodyYn ="Y";
		this.parameterName = httpProp.getProperty(HttpAdapterServiceKey.PARAMETER_NAME, HttpAdapterServiceKey.PARAMETER_MESSAGE);
		this.removeLength = httpProp.getProperty(HttpAdapterServiceKey.REMOVE_LENGTH, "0");
		this.addData = httpProp.getProperty(HttpAdapterServiceKey.ADD_DATA, "");
		String traceLevelTemp = httpProp.getProperty(HttpAdapterServiceKey.TRACE_LEVEL, "0");
		this.apiPath = httpProp.getProperty(HttpAdapterServiceKey.API_PATH, "");

		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());
			this.extUri = extUrl;
		} else {
			this.extUri = getExtUri(request);
		}

		standardApiPath = extUri.replaceAll(apiPath, "");
		if (!standardApiPath.startsWith("/")) {
			standardApiPath = standardApiPath + "/";
		}
		
		try {
			traceLevel = Integer.parseInt(traceLevelTemp);
		} catch (Exception e) {
			traceLevel = 0;
		}
		this.removeLen = 0;
		try {
			removeLen = Integer.parseInt(removeLength);
		} catch (Exception e) {
			removeLen = 0;
		}
		
		if ("Y".equals(requestBodyYn)) {
			this.requestBytes = requestBytesSetting(request);
			if (traceLevel >= 3) {
				HttpMemoryLogger.txlog(adptGrpName + adptName, "RECV " + "[" + new String(requestBytes, encode) + "]" + CommonLib.getDumpMessage(requestBytes));
			}
		} else {
			String paramValue = request.getParameter(parameterName);
			if (traceLevel >= 3) {
				HttpMemoryLogger.txlog(adptGrpName + adptName, "RECV " + "[" + paramValue + "]" + ((paramValue == null) ? "" : CommonLib.getDumpMessage(paramValue.getBytes())));
			}
			this.requestBytes = requestBytesFromParameterValue(paramValue, urlDecodeYn, encode);
			if (logger.isDebug()) {
				logger.debug("HttpAdapterServiceChannel] RECV (" + adptGrpName + ") = [" + paramValue + "]");
				logger.debug("HttpAdapterServiceChannel] RECV (" + adptGrpName + ") = " + ((paramValue == null) ? "" : CommonLib.getDumpMessage(paramValue.getBytes())));
			}
		}

		if (removeLen > 0) {
			byte[] cuttedBytes = new byte[requestBytes.length - removeLen];
			System.arraycopy(requestBytes, removeLen, cuttedBytes, 0, cuttedBytes.length);
			this.requestBytes = cuttedBytes;
			if (logger.isDebug()) {
				logger.debug("HttpAdapterServiceChannel] RECV after remove (" + adptGrpName + ") = [" + new String(requestBytes, encode) + "]");
				logger.debug("HttpAdapterServiceChannel] RECV after remove (" + adptGrpName + ") = " + CommonLib.getDumpMessage(requestBytes));
			}
		}
		
	}
	
	private byte[] requestBytesSetting(HttpServletRequest request) throws IOException {
		
		byte[] result = 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);
			}
		}
		result = new byte[bb.position()];
		bb.position(0);
		bb.get(result);
		
		return result;
	}

	private byte[] requestBytesFromParameterValue(String paramValue, String urlDecodeYn, String encode) throws ServletException, UnsupportedEncodingException {
		
		if (paramValue == null)
			throw new ServletException("RECEAIAHA002");

		if ("Y".equals(urlDecodeYn)) {
			paramValue = URLDecoder.decode(paramValue, encode);
		}

		return paramValue.getBytes(encode);
	}

	public String getAdptGrpName() {
		return adptGrpName;
	}

	public String getAdptName() {
		return adptName;
	}

	public int getTraceLevel() {
		return traceLevel;
	}

	public String getResponseType() {
		return responseType;
	}

	public String getUrlDecodeYn() {
		return urlDecodeYn;
	}

	public String getEncode() {
		return encode;
	}

	public String getRequestBodyYn() {
		return requestBodyYn;
	}

	public String getParameterName() {
		return parameterName;
	}

	public String getRemoveLength() {
		return removeLength;
	}

	public String getAddData() {
		return addData;
	}

	public byte[] getRequestBytes() {
		return requestBytes;
	}

	public int getRemoveLen() {
		return removeLen;
	}
	
	public String getGuid() {
		return guid;
	}
	
	public String getUserId() {
		return userId;
	}
	
	public void setGuid(String guid) {
		this.guid = guid;
	}
	
	public void setUserId(String userId) {
		this.userId = userId;
	}

	public String getApiPath() {
		return apiPath;
	}

	public void setApiPath(String apiPath) {
		this.apiPath = apiPath;
	}

	public String getExtUri() {
		return extUri;
	}

	public void setExtUri(String extUri) {
		this.extUri = extUri;
	}

	public String getStandardApiPath() {
		return standardApiPath;
	}

	public void setStandardApiPath(String standardApiPath) {
		this.standardApiPath = standardApiPath;
	}
	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(), "/");
	}
	
}

class ChannelResponseDivider {
	
	private Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
	
	private ChannelServiceDataBundle bundle;
	
	private boolean loginTransaction = false;
	private boolean logoutTransaction = false;
	private boolean skipTransaction = false;
	
	public ChannelResponseDivider(ChannelServiceDataBundle bundle) {
		this.bundle = bundle;
	}

	public enum ResponseType_Phase1 {
		HttpPoll, DupLoginCheck, LoginValidation, LoginUuidValidation, Skip
	}
	
	public enum ResponseType_Phase2 {
		Async, LoginTransaction, LogoutTransaction, Skip
	}
	
	public ResponseType_Phase1 dividePhase1() throws Exception {
		
		if(bundle.getRequestBytes().length == 58) {
			String message = new String(bundle.getRequestBytes(), bundle.getEncode());
			if (message.startsWith("POLLREQ@@")) {
				return ResponseType_Phase1.HttpPoll;
			}
		}
		
		AdapterGroupVO agvo = AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName());
		String LOGINSERVICEID = "";
		String LOGOUTSERVICEID = "";
		String SKIP_SERVICEID = "";
		
		String userId = "";
		String uuid   = "";
		
		if (MessageType.JSON.equals(agvo.getMessageType())) {
			
			PropManager pManager = PropManager.getInstance();
			Properties prop = pManager.getProperties(PropertyLoginKeys.LOGIN);
			LOGINSERVICEID = prop.getProperty(PropertyLoginKeys.LOGIN_SERVICE_ID);
			LOGOUTSERVICEID = prop.getProperty(PropertyLoginKeys.LOGOUT_SERVICE_ID);
			SKIP_SERVICEID = prop.getProperty(PropertyLoginKeys.SKIP_SERVICE_ID, "");// 비밀번호 변경 등등

			String requestStr = new String(bundle.getRequestBytes(), bundle.getEncode());
			logger.debug("HttpAdapterServiceChannel] LOGIN_SERVICE_ID = " + LOGINSERVICEID);
			logger.debug("HttpAdapterServiceChannel] LOGOUT_SERVICE_ID = " + LOGOUTSERVICEID);
			logger.debug("HttpAdapterServiceChannel] SKIP_SERVICE_ID = " + SKIP_SERVICEID);
			logger.debug("HttpAdapterServiceChannel] Request-JSON = " + requestStr);

			/*
			 * 모든 거래가 서비스ID, 사용자ID를 가져오기 위해 표준전문 파싱을 하는것은 낭비라고 판단하여 JSON에서 직접 가져오도록 변경
			 */
			// 표준전문 파싱으로 변경.
			StandardMessage standardMessage = null;
			StandardMessageManager standardManager = StandardMessageManager.getInstance();
			StandardReader reader = null;
			InterfaceMapper mapper = standardManager.getMapper();
			AdapterGroupVO vo = AdapterManager.getInstance().getAdapterGroupVO(bundle.getAdptGrpName());
			String messageType = vo.getMessageType();
			String messageEncode = vo.getMessageEncode();
			standardMessage = standardManager.getStandardMessage();
			reader = standardManager.getReader(messageType);
			reader.parse(standardMessage, bundle.getRequestBytes());
			// 표준전문 파싱 후 어댑터의 charset을 설정한다.
			standardMessage.setBizDataCharset(messageEncode);

			String standardApiPath = bundle.getStandardApiPath();

			String serviceId = "";

			if (standardApiPath.length() > 1) {
				serviceId = standardApiPath;
			} else {
				serviceId = mapper.getServiceId(standardMessage);
			}

			uuid = mapper.getSessionId(standardMessage);
			userId = mapper.getUserId(standardMessage);
			bundle.setGuid(mapper.getGuid(standardMessage));
			bundle.setUserId(userId);
			
			if (LOGINSERVICEID.equals(serviceId)) {
				
				loginTransaction = true;
//				2023.12.11 KAKAOBANK-CARD 중복 로그인 체크 안함				
//				SessionVO sessionVo = SessionManager.getInstance().getLogin(userId);
//				if (sessionVo != null) {
//					String LOGIN_DUP_CHECK_YN = prop.getProperty(PropertyLoginKeys.LOGIN_DUP_CHECK_YN, "Y");
//					if ("Y".equals(LOGIN_DUP_CHECK_YN)) {
//						return ResponseType_Phase1.DupLoginCheck;
//					} else {
//						logger.error("HttpAdapterServiceChannel] LOGIN-DUPLICATED loginUserId= " + userId + " skip");
//					}
//				}
				
			}

			if (LOGOUTSERVICEID.equals(serviceId)) {
				logoutTransaction = true;
			}

			//통합로그 비로그인 거래 처리(2022-08-12) AA요구사항
			//대내 MCI 및 전사공통 선처리에서 사용자 ID가 가상 사용자( 'VRxxxxxxx') 인 경우 로그인 여부등 유효성 검증 SKIP(대내MCI/전사공통)
			if (SKIP_SERVICEID.indexOf(serviceId) >= 0 /* || userId.startsWith("VR")*/) {
				skipTransaction = true;
			}
			logger.debug("HttpAdapterServiceChannel] loginTransaction = " + loginTransaction);
			logger.debug("HttpAdapterServiceChannel] logoutTransaction = " + logoutTransaction);
			logger.debug("HttpAdapterServiceChannel] skipTransaction = " + skipTransaction);

		}
		
		if (!(loginTransaction || skipTransaction)) {
			
			// FIXME : 로그인 여부를 체크할 것인가 ?
			PropManager pManager = PropManager.getInstance();
			Properties prop = pManager.getProperties(PropertyLoginKeys.LOGIN);
			String LOGIN_VALIDATION_YN = prop.getProperty(PropertyLoginKeys.LOGIN_VALIDATION_YN, "N");// 로그인 validation
			String UUID_VALIDATION_YN = prop.getProperty(PropertyLoginKeys.UUID_VALIDATION_YN, "N");// uuid validation
			
			if ("Y".equals(LOGIN_VALIDATION_YN)) {
				SessionVO sessionVo = SessionManager.getInstance().getLogin(userId);
				if(sessionVo == null) {
					return ResponseType_Phase1.LoginValidation;
				}
			}else if ("Y".equals(UUID_VALIDATION_YN)) {
				//UUID validation 추가
				SessionVO sessionVo = SessionManager.getInstance().getHttpLogin(uuid);
				if (sessionVo == null || !userId.equals(sessionVo.getLoginUserId())) {
					return ResponseType_Phase1.LoginUuidValidation;
				}
			} else {
				logger.error("HttpAdapterServiceChannel] NOTLOGIN OR EXPIRED SESSION userId[" + userId + "] - continue.");
			}
			
		}

		return ResponseType_Phase1.Skip;
	}
	
	public ResponseType_Phase2 dividePhase2() {
		
		if (HttpAdapterServiceKey.RESPONSE_TYPE_ASYNC.equals(bundle.getResponseType())) {
			
			return ResponseType_Phase2.Async;
			
		} else {
			
			if (bundle.getRequestBytes() != null) {
				if (loginTransaction) {
					
					return ResponseType_Phase2.LoginTransaction;
					
				} else if (logoutTransaction) {
					
					return ResponseType_Phase2.LogoutTransaction;
				}
			}
			
		}
		
		return ResponseType_Phase2.Skip;
	}
	
}