package com.eactive.eai.inbound.processor; 


import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;

import com.eactive.eai.adapter.AdapterGroupVO;
import com.eactive.eai.adapter.AdapterManager;
import com.eactive.eai.adapter.http.HttpStatusException;
import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceKey;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.common.exception.ExceptionHandler;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.message.EAIMessageKeys;
import com.eactive.eai.common.message.MessageType;
import com.eactive.eai.common.util.DatetimeUtil;
import com.eactive.eai.common.util.InboundErrorLogger;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.MessageUtil;
import com.eactive.eai.inbound.error.InboundErrorInfoVO;
import com.eactive.eai.inbound.error.InboundErrorKeys;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.manager.StandardMessageManager;
import com.eactive.eai.message.mapper.MessageMapper;
import com.eactive.eai.transformer.util.IConv;

/** 
 * 1. 기능 : Inbound Adapter를 통해 전달 받은 메시지를 EAIMessage로 변환하고
 *           FlowControl로 전달하는 기능을 제공한다.  
 * 2. 처리 개요 : 
 *     - STDMessage 생성
 *     - EAIMessage 생성
 *     - 요청 거래로깅
 *     - FlowControl 호출
 *     - 응답 거래로깅
 * 3. 주의사항 
 *
 * @author  : 
 * @version : v 1.0.0
 * @see :
 * @since : 
 *  :
 */

public abstract class RequestProcessorSupport implements Processor
{ 
	static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
	protected static final String SERVICE_FLAG_MESSAGE = "SERVICE BLOCKED BY EAI SYSTEM";
	protected static final String STARTING_SERVICE_KEY = "ESBFW";
	
	// 장애전문송신용 EAI 수신 가상어댑터 명
	protected static final String FAIL_MSG_RECEIVER = "FAIL_MSG_RECEIVER";
	
	private static int rcvCount;
	private static int errCount;
	
	private static boolean EAI_SERVICE_FLAG = true;
	
	static Logger esbLogger = Logger.getLogger(Logger.LOGGER_ESBFW);  
	protected int svrLogLevel;                                        
	private static ThreadLocal local = new ThreadLocal();   //경과시간 동기화를 위한 ThreadLocal 
	
	public RequestProcessorSupport() {
	}
	
	public boolean isEAIServiceFlag() {
		return EAI_SERVICE_FLAG;    
	}
	
	public void setEAIServiceFlag(boolean serviceFlag) {
		EAI_SERVICE_FLAG = serviceFlag;
	}
	
	public static int rcvCount() {
		return rcvCount;    
	}
	
	public static int errCount() {
		return errCount;
	} 
	
	public synchronized static void resetCount() {
		rcvCount = 0;
		errCount = 0;
	}
	
	private synchronized void increaseRcvCount() {
		rcvCount++;   
	}
	
	public synchronized void increaseErrCount() {
		errCount++;   
	}
	
	/** 
	 * 1. 기능 : RequestDispatcher에서 호출하기 위한 메소드로 내부의 hookup을 호출한다
	 * 2. 처리 개요 : 
	 *     - 메시지수신시각설정
	 *     - hookup 호출
	 *     - 요청 거래로깅
	 * 3. 주의사항 
	 *
	 * @param message Object 타입의 요청메시지
	 * @param prop 호출시 전달할 Property 값(추후 확장을 위해)
	 * @return Object
	 * @throws JwtAuthException 
	 * @exception
	 **/
	public Object execute(Object message, Properties prop) throws HttpStatusException 
	{
		String receivedTimestamp = prop.getProperty(HttpAdapterServiceKey.INBOUND_REQUESTED_TIME);
		long msgRcvTm = 0L;
		if(receivedTimestamp == null)
			msgRcvTm = System.currentTimeMillis();
		else
			msgRcvTm = Long.parseLong(receivedTimestamp);
		
		increaseRcvCount();
		local.set(new Long(msgRcvTm));    
		
		if (logger.isDebug()) logger.debug("RequestProcessor] starting - "+prop);
		Object result = null;
		String encode = "euc-kr";
		String messageType = "";
		try {
			messageType = prop.getProperty(Processor.MESSAGE_TYPE);
			encode = prop.getProperty(com.eactive.eai.adapter.Keys.CHARSET_KEY_IN);
			result = hookup(message, prop, msgRcvTm);
			return result;            
			//return message;
		} catch(HttpStatusException e) {
			throw e;
		} catch(Exception e) {
			increaseErrCount();
			esbLogger.error("execute error", e);
			String msg = "";
			if (StringUtils.equalsAny(messageType, MessageType.JSON, MessageType.XML)) {
				throw new HttpStatusException(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value());
			} else {
				msg = ExceptionHandler.handle(e.getMessage());
			}
					
			if(message instanceof byte[]) {
				byte[] response = null;
//				if(MessageUtil.isUTF8(messageType)) {
//					encode = "utf-8"; 
//				}
				try {
					response = msg.getBytes(encode);
				}
				catch(Exception ex) {
					return msg.getBytes();
				}				
				return response;
			}
			else {
				return msg;
			}
		} finally {
			long msgSndTm = System.currentTimeMillis();
			if (logger.isDebug()) logger.debug("RequestProcessor] EAI 처리시각["+(msgSndTm-msgRcvTm)+" msec] - "+prop);
			if(svrLogLevel >= 1) {
				try{   
					Long threadData = (Long)local.get();
					long processTm =  msgSndTm - threadData.longValue();    
					if (esbLogger.isInfo()) esbLogger.info("RequestProcessor]  End... - 경과시간 : " + processTm + "(ms) - EAI Server Log Level is INFO");
				}catch (Exception e){
					logger.warn("execute", e);
				}
			}
		}
	}
	
	/** 
	 * 1. 기능 : 미확인 메시지에 대한 로그
	 * 2. 처리 개요 : 
	 *     - Inbound Adapter를 통해 수신된 메시지에 대한 Rule을 확인할 수 없는 경우 로그출력
	 * 3. 주의사항 
	 *
	 * @param uuid EAI서비스일련번호
	 * @param adapterGroupName Inbound Adapter 그룹명    
	 * @param adapterName Inbound Adapter 명
	 * @param errCode 에러코드
	 * @param errMsg 에러내용
	 * @param errTm 에러발생시각
	 * @param svcInstNm  EAI서버인스턴스명
	 * @param message Inbound Adapter가 수신받은 메시지 Object
	 * @exception
	 **/
	protected void logUnknownMessage(String uuid, String adapterGroupName, String 
			adapterName,  String bzwkSvcKeyName, String eaiSvcCd, boolean isTasStarted, 
			String errCode, String errMsg, long errTm, String svcInstNm,String errDstCd, Object message) {
		String msg = null;
		
		if(errDstCd == null || errDstCd.length() ==0) {
			errDstCd = "ND";
		}
		
		if(isTasStarted) {
			errDstCd = InboundErrorKeys.TAS_START;
		}
		
		InboundErrorInfoVO errorInfoVO = new InboundErrorInfoVO();
		errorInfoVO.setEaiSvcSno(uuid);                     // EAI서비스일련번호
		errorInfoVO.setAdptBwkGrpNm(adapterGroupName);      // 어댑터업무그룹명
		errorInfoVO.setEaiSvcCd(eaiSvcCd);
		errorInfoVO.setErrCd(errCode);                           // 에러코드
		errorInfoVO.setErrTxt(errMsg);                          // 에러내용
		errorInfoVO.setErrTm(DatetimeUtil.getCurrentTime(errTm));                           // 에러발생시각
		errorInfoVO.setErrDstcd(errDstCd);
		errorInfoVO.setBzwkSvcKeyName(bzwkSvcKeyName);
		
		if(message == null) {
			msg = "null";
		}
		else {
			if(message instanceof byte[]) {
				AdapterManager adapterManager = AdapterManager.getInstance();
		        AdapterGroupVO srcAdapterGrpVO = adapterManager.getAdapterGroupVO(adapterGroupName);
		        
		        // TAS 거래일 경우 NULL일 수 있음
		        if(srcAdapterGrpVO == null) {
		        	msg = new String((byte[])message);
		        }
		        else {
//			        String srcAdapterMsgType = srcAdapterGrpVO.getMessageType();
//			        if( MessageUtil.isUTF8(srcAdapterMsgType) ) {
//			        	try {
//							msg = new String((byte[])message, UJSONMessage.encode);
//						} catch (UnsupportedEncodingException e) {
//							logger.warn("msg encode error", e);
//							msg = new String((byte[])message);					}
//			        }
//			        else {
//			        	msg = new String((byte[])message);
//			        }
		        	
		        	String charset = StringUtils.defaultIfBlank(srcAdapterGrpVO.getMessageEncode(), Charset.defaultCharset().name());
		        	try {
						msg = new String((byte[])message, charset);
					} catch (UnsupportedEncodingException e) {
						logger.warn("msg encode error", e);
						msg = new String((byte[])message);
					}
		        }
		        
			}
			else if(message instanceof String) {
				msg = (String)message;
			}
			else {
				msg = "invaild message ["+message.getClass().getName()+"] "+message.toString();
			}
		}
		
		errorInfoVO.setBwkDataTxt(msg);                      // 업무데이터내용
		errorInfoVO.setEaiSvrInstNm(svcInstNm);                    // EAI서버인스턴스명
		
		InboundErrorLogger.error(errorInfoVO);
		
		if (logger.isError()) {
			logger.error("======================================================================" );
			logger.error(" RequestDispatcher] GET UNKNOWN MESSAGE");
			logger.error(" ADAPTER GROUP NAME [" + adapterGroupName + "]");
			logger.error(" EAISVCNAME [" + eaiSvcCd + "]");
			logger.error(" ADAPTER NAME [" + adapterName + "]");
			logger.error("======================================================================" );
//			logger.error("<< MESSAGE DUMP >>");
//			String[] dump = CommonLib.makeDumpFormat( msg.getBytes() );
//			for(int i=0; i< dump.length; i++) {
//			logger.error(dump[i]);
//			}
//			logger.error("======================================================================" );
		}    
		
	}
	protected void logUnknownMessage(ProcessVO vo, Object orgMessage) {
		if(MessageType.EBC.equals(vo.getAdptrMsgType())) {
			logUnknownMessage(vo.getUuid(), vo.getAdapterGroupName(), vo.getAdapterName(),  vo.getSelectedKey(), vo.getEaiSvcCd(), vo.isTasStarted(), 
					vo.getRspErrorCode(), vo.getRspErrorMsg(), vo.getErrTm(), vo.getServerName(),vo.getErrTypeCode(), IConv.ebcdicToASCII((byte[])orgMessage));
		}else{
			logUnknownMessage(vo.getUuid(), vo.getAdapterGroupName(), vo.getAdapterName(),  vo.getSelectedKey(), vo.getEaiSvcCd(), vo.isTasStarted(), 
					vo.getRspErrorCode(), vo.getRspErrorMsg(), vo.getErrTm(), vo.getServerName(),vo.getErrTypeCode(), orgMessage);
		}
	}
        

	/** 
	 * 1. 기능 : ServiceFlag의 설정에 대한 경고메시지를 생성하여 리턴한다.
	 * 2. 처리 개요 : 
	 *     - 메시지의 유형에 따라 ServieFlag 메시지를 리턴한다.
	 * 3. 주의사항 
	 *
	 * @param inObj 요청메시지
	 * @return Object
	 **/
//	private Object makeServiceFlagMessage(Object inObj) {
//		Object retObject = null;
//		
//		if(inObj instanceof byte[]) {
//			retObject = SERVICE_FLAG_MESSAGE.getBytes();
//		}
//		else if(inObj instanceof byte[]) {
//			retObject = SERVICE_FLAG_MESSAGE;
//		}
//		else {
//			retObject = SERVICE_FLAG_MESSAGE;
//		}
//		return retObject;
//	}
	
	/** 
	 * 1. 기능 : 응답메시지 오브젝트에 따라 응답메시지를 생성하여 리턴 
	 * 2. 처리 개요 : 
	 *     - Adapter메시지패턴에 따라 응답메시지를 생성하여 리턴
	 * 3. 주의사항 
	 *
	 * @param retEaiMsg 응답EAI메시지
	 * @param message Object 타입의 요청메시지
	 * @param retMsgObject 응답메시지오브젝트
	 * @param adptrMsgPtrnCd Adapter메시지패턴 - [S, N, C] 값을 가짐 : Standard, Non-Standard, Custom
	 * @param adptrMsgType Adapter 메시지 타입 - XML, ASC, FML, EBC
	 * @param uuid 거래에 대한 Unique ID - 로깅을 위한 값
	 * @return Object
	 **/
	
	public static Object getReturnObject(EAIMessage retEaiMsg, Object message, Object retMsgObject,
			String adptrMsgPtrnCd, String adptrMsgType, String uuid, String charset) {

		if(com.eactive.eai.adapter.Keys.IF_STANDARD.equals(adptrMsgPtrnCd)) {
	        StandardMessage stdMessage = retEaiMsg.getStandardMessage();
	        try {
				stdMessage.setBizData(retMsgObject, charset);
			} catch (Exception e) {
				logger.error("setBizData failed.", e);
			}
	        StandardMessageManager standardManager = StandardMessageManager.getInstance();
	        standardManager.getMessageCoordinator().coordinateInReturnObject(stdMessage);
/*	        
			
//			if(MessageType.JSON.equals(adptrMsgType) 
//	    			|| MessageType.UJSON.equals(adptrMsgType)) {
			if(MessageType.JSON.equals(adptrMsgType)) {
				return stdMessage.toJson();
			}
//	    	else if(MessageType.XML.equals(adptrMsgType)
//	    			|| MessageType.UXML.equals(adptrMsgType)) {
	    	else if(MessageType.XML.equals(adptrMsgType)) {
				return stdMessage.toXML();
			}
			else if(MessageType.ASC.equals(adptrMsgType)) {
				return stdMessage.toFixedString(charset);
			}
			else if(MessageType.EBC.equals(adptrMsgType)) {
				logger.error("Unsupported message type - " + MessageType.EBC);
			}		
//			else if(MessageUtil.isUTF8(adptrMsgType)) {
//				return stdMessage.toJson();
//			}
			else {
				return retMsgObject;
			}
			return retMsgObject;
*/
	        String obj = null;
	        try {
	        	obj = stdMessage.getDataString(adptrMsgType, charset);
	        }catch(Exception e) {
	        	
	        }
	        if(obj == null) return retMsgObject;
	        return obj;
		}
		else if(com.eactive.eai.adapter.Keys.IF_SUBSTANDARD.equals(adptrMsgPtrnCd)) {
	        StandardMessage stdMessage = retEaiMsg.getStandardMessage();
	        StandardMessage subMessage = retEaiMsg.getSubMessage();
	        try {
	        	subMessage.setBizData(retMsgObject, charset);
			} catch (Exception e) {
				logger.error("setBizData failed.", e);
			}
	        StandardMessageManager standardManager = StandardMessageManager.getInstance();
	        standardManager.getMessageCoordinator().coordinateInReturnObject(stdMessage);
	        
			if(MessageType.JSON.equals(adptrMsgType)) {
				return subMessage.toJson();
			}
	    	else if(MessageType.XML.equals(adptrMsgType)) {
				return subMessage.toXML();
			}
			else if(MessageType.ASC.equals(adptrMsgType)) {
				return subMessage.toFixedString(charset);
			}
			else if(MessageType.EBC.equals(adptrMsgType)) {
				logger.error("Unsupported message type - " + MessageType.EBC);
			}		
			else {
				return retMsgObject;
			}
			return retMsgObject;
		}
		else {
			// 비표준 XML일 경우 XML Header Tag의 존재여부를 확인하여 없는 경우 추가해야 한다.
//			if(MessageType.XML.equals(adptrMsgType) || MessageType.UXML.equals(adptrMsgType)) {
			if(MessageType.XML.equals(adptrMsgType)) {
//				String encode = MessageType.XML_ENCODE;
//				if(MessageType.UXML.equals(adptrMsgType)) {
//					encode = MessageType.UXML_ENCODE;
//				}
				if(retMsgObject == null) {
					return "";
				}
				else if(message instanceof String) {
					String xmlString = (String)retMsgObject;
					if(xmlString.indexOf("<?") == -1 && xmlString.indexOf("?>") == -1) {
						xmlString = "<?xml version=\"1.0\" encoding=\""+ charset +"\"?>"
							+ xmlString;
					}
					return xmlString;
				}
				else if(message instanceof byte[]) {
					String xmlString = null;
					try {
						xmlString = new String( (byte[])retMsgObject, charset );
					} catch (UnsupportedEncodingException e) {
						xmlString = new String( (byte[])retMsgObject);
					}
					if(xmlString.indexOf("<?") == -1 && xmlString.indexOf("?>") == -1) {
						xmlString = "<?xml version=\"1.0\" encoding=\""+ charset +"\"?>"
							+ xmlString;
					}
					byte[] responseBytes = null;
					try {
						responseBytes = xmlString.getBytes(charset);
					} catch (UnsupportedEncodingException e) {
						responseBytes = xmlString.getBytes();
					}
					return responseBytes;
				} else {
					return retMsgObject;
				}
			}else if(MessageType.JSON.equals(adptrMsgType)) {
				if(retMsgObject == null) {
					return "{}";
				}else if(retMsgObject instanceof byte[]) {
					byte[] bytesObject = (byte[])retMsgObject;
					if(bytesObject.length == 0) {
						return "{}";
					}
				}else if(retMsgObject instanceof String) {
					String stringObject = (String)retMsgObject;
					if(StringUtils.isBlank(stringObject)) {
						return "{}";
					}
				}
				
				return retMsgObject;
			}else {
				return retMsgObject;
			}
		}
	}
	
	protected Object getRestrictedResponse(EAIMessage retEaiMsg, Object message,
			String adptrMsgPtrnCd, String adptrMsgType, String uuid, String charset) {
		if(com.eactive.eai.adapter.Keys.IF_STANDARD.equals(adptrMsgPtrnCd)) {
			return getRestrictedResponse(retEaiMsg, message, adptrMsgType, uuid, charset);
		} 
		else {
			String eaiRestrictedRespose = "";
			if (StringUtils.equals(retEaiMsg.getRspErrCd(), EAIMessageKeys.EAI_INFLOW_BLOCKED_CODE)) {
				eaiRestrictedRespose = "It was not processed by flow control.[" + retEaiMsg.getEAISvcCd() + "]";
			} else {
				eaiRestrictedRespose = "It was not processed by transaction control.[" + retEaiMsg.getEAISvcCd() + "]";
			}
			if(MessageType.XML.equals(adptrMsgType)) {
				return MessageUtil.makeXmlErrorMessage(retEaiMsg.getRspErrCd(), retEaiMsg.getRspErrMsg(), charset);
			} else if (MessageType.JSON.equals(adptrMsgType)) {
				return MessageUtil.makeJsonErrorMessage(retEaiMsg.getRspErrCd(), retEaiMsg.getRspErrMsg());
			} else {
				try {
					return eaiRestrictedRespose.getBytes(charset);
				} catch (UnsupportedEncodingException e) {
					return eaiRestrictedRespose.getBytes();
				}
			} 
		}
	}
	
	private static Object getRestrictedResponse(EAIMessage retEaiMsg, Object message,
			String adptrMsgType, String uuid, String charset) {
    	StandardMessage stdMessage = retEaiMsg.getStandardMessage();
    	//InterfaceMapper mapper = retEaiMsg.getMapper();
//    	String errorMessage = ExceptionUtil.make(retEaiMsg.getRspErrCd(), new String[]{retEaiMsg.getEAISvcCd()});
//    	mapper.setErrorCode(stdMessage, STDMessageKeys.ERROR_CODE_VALUE);
//    	mapper.setErrorMsg(stdMessage, errorMessage);
    	//mapper.setErrorCode(stdMessage, retEaiMsg.getRspErrCd());
    	//mapper.setErrorMsg(stdMessage, retEaiMsg.getRspErrMsg());
    	
    	// 표준메시지에 BizData 셋팅할 필요 없을듯(요청 BizData 리턴)
//    	try {
//			stdMessage.setBizData(errorMessage, charset);
//		} catch (Exception e) {
//			logger.error("setBizData failed.", e);
//		}
/*    	
//    	if(MessageType.JSON.equals(adptrMsgType) 
//    			|| MessageType.UJSON.equals(adptrMsgType)) {
    	if(MessageType.JSON.equals(adptrMsgType)) {
			return stdMessage.toJson();
		}
//    	else if(MessageType.XML.equals(adptrMsgType)
//    			|| MessageType.UXML.equals(adptrMsgType)) {
    	else if(MessageType.XML.equals(adptrMsgType)) {
			return stdMessage.toXML();
		}
		else if(MessageType.ASC.equals(adptrMsgType)) {
			return stdMessage.toFixedString(charset);
		}
		else if(MessageType.EBC.equals(adptrMsgType)) {
			logger.error("Unsupported message type - " + MessageType.EBC);
		}		
//		else if(MessageUtil.isUTF8(adptrMsgType)) {
//			return stdMessage.toJson();
//		}
		else {
			return message;
		}
    	return message;
*/
    	String obj = null;
    	try {
    		obj = stdMessage.getDataString(adptrMsgType, charset);
    	}catch(Exception e) {
    		
    	}
    	if(obj==null) return message;
    	return obj;
    }
	
	/** 
	 * 1. 기능 : Inbound Adapter를 통해 전달 받은 메시지를 EAIMessage로 변환하고
	 *           FlowControl로 전달하는 기능을 제공한다.  
	 * 2. 처리 개요 : 
	 *     - KBMessage 생성
	 *     - EAIMessage 생성
	 *     - 요청 거래로깅
	 *     - FlowControl 호출
	 *     - 응답 거래로깅
	 * 3. 주의사항 
	 *
	 * @param message Object 타입의 요청메시지
	 * @param prop 호출시 전달할 Property 값(추후 확장을 위해)
	 * @param msgRcvTm 메시지 수신시각
	 * @return Object
	 * @exception RequestProcessorException
	 **/
	abstract protected Object hookup(Object message, Properties prop, long msgRcvTm) throws RequestProcessorException, Exception ; 
}