package com.eactive.eai.common.session;

import java.util.List;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.StringUtils;
import org.java_websocket.WebSocket;
import org.json.simple.JSONObject;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import com.eactive.eai.adapter.websocket.WebSocketSessionManager;
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.sessioninfo.SessionHistoryDTO;
import com.eactive.eai.common.sessioninfo.SessionHistoryVO;
import com.eactive.eai.common.sessioninfo.SessionInfoVO;
import com.eactive.eai.common.sessioninfo.SessionInfoVO.Status;
import com.eactive.eai.common.util.DatetimeUtil;
import com.eactive.eai.common.util.Logger;

public class ChannelSessionFacade {
	
	public enum LogoutType {
		Logout, AutoLogout, ForceLogout, WebSocketTimeout, DateChange, ServerDown
	}

	private static ChannelSessionFacade instance;
	private Logger logger;

	private SessionManager sessionManager;
	private WebSocketSessionManager webSocketSessionManager;

	private SessionStatusManager sessionStatusManager;
	
	private ExecutorService logoutRunnerExecutor;
	
	private static final Integer DEFAULT_LOGOUT_MIN_THREAD_COUNT = 10;

	public static ChannelSessionFacade getInstance() {
		if (instance == null) {
			synchronized (ChannelSessionFacade.class) {
				if (instance == null) {
					instance = new ChannelSessionFacade();
					instance.logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
					instance.sessionManager = SessionManager.getInstance();
					instance.webSocketSessionManager = WebSocketSessionManager.getInstance();
					instance.sessionStatusManager = SessionStatusManager.getInstance();
					
					PropManager pManager  = PropManager.getInstance();
			        if (pManager.isContainProperties(PropertyLoginKeys.LOGIN)){
			        	
			        	Properties loginProp  = pManager.getProperties(PropertyLoginKeys.LOGIN);
			        	
			        	int logoutMaxThreadCount = Integer.parseInt(loginProp.getProperty(PropertyLoginKeys.LOGOUT_MAX_THREAD, "30"));
			        	int logoutMinThreadCount = logoutMaxThreadCount < DEFAULT_LOGOUT_MIN_THREAD_COUNT ? logoutMaxThreadCount : DEFAULT_LOGOUT_MIN_THREAD_COUNT;
			        	int logoutMaxQueueSize = Integer.parseInt(loginProp.getProperty(PropertyLoginKeys.LOGOUT_MAX_QUEUE, "500"));
			        	
			        	LinkedBlockingQueue<Runnable> runnerQueue = new LinkedBlockingQueue<Runnable>(logoutMaxQueueSize);
			        	instance.logoutRunnerExecutor = 
			        			new ThreadPoolExecutor(
			        					logoutMinThreadCount, logoutMaxThreadCount, 60000L, TimeUnit.MILLISECONDS, runnerQueue, new CustomizableThreadFactory("LogoutRunner-"));
			        }
					
				}
			}
		}
		return instance;
	}

	public void login(String userId, String uuid, JSONObject resultJson) {
		
		logger.debug("login] userId : " + userId, " uuid : " + uuid);

		PropManager pManager = PropManager.getInstance();
		Properties loginProp = pManager.getProperties(PropertyLoginKeys.LOGIN);
		
		/** PostBank
		String KEY_USER_NM = loginProp.getProperty(PropertyLoginKeys.LOGIN_USER_NM);
		// 소속국
		String KEY_BRN_CD = loginProp.getProperty(PropertyLoginKeys.LOGIN_BRN_CD);
		String KEY_BRN_NM = loginProp.getProperty(PropertyLoginKeys.LOGIN_BRN_NM);

		// 소속청
		String KEY_ROFC_CD = loginProp.getProperty(PropertyLoginKeys.LOGIN_ROFC_CD);

		// terminalId
		String TERMINAL_ID = loginProp.getProperty(PropertyLoginKeys.LOGIN_TERMINAL_ID);
		**/
		
		EAIServerManager eaiServerManager = EAIServerManager.getInstance();
		String serverName = eaiServerManager.getLocalServerName();
//		String brncd = (String) resultJson.get(KEY_BRN_CD);

		SessionVO sessionVo = new SessionVO();
		sessionVo.setUuid(uuid);
		sessionVo.setLoginUserId(userId);
		sessionVo.setLoginInstId(serverName);
//		sessionVo.setBrncd(brncd);
//		sessionVo.setTerminalId((String) resultJson.get(TERMINAL_ID));

		sessionManager.putLogin(userId, sessionVo);
		sessionManager.putHttpLogin(uuid, sessionVo);
		
		sessionManager.putWebSocketTimeout(userId, sessionVo);

		SessionInfoVO sessionInfoVO = new SessionInfoVO(userId);
		sessionInfoVO.setSessionId(uuid);
		sessionInfoVO.setCreateTime(DatetimeUtil.getCurrentDateTime());
		sessionInfoVO.setSessionStatus(SessionInfoVO.Status.active);
//		sessionInfoVO.setUserName((String) resultJson.get(KEY_USER_NM));
//		sessionInfoVO.setRofcCode((String) resultJson.get(KEY_ROFC_CD));
//		sessionInfoVO.setBrnName((String) resultJson.get(KEY_BRN_NM));
//		sessionInfoVO.setBrnCode(brncd);
//		sessionInfoVO.setTerminalId((String) resultJson.get(TERMINAL_ID));

		sessionStatusManager.manageStatus(sessionInfoVO);
	}

	public void logout(String userId, String uuid, boolean logoutTransaction, LogoutType logoutType) {

		sessionManager.removeLogin(userId);
		sessionManager.removeHTTPLogin(uuid);
		
		sessionManager.removeWebSocketTimeout(userId); // logout시 timeout 해제
		
		logger.info(userId + " 사용자 연결 끊어짐으로 로그아웃 거래 수행.");
		
		if(logoutTransaction) {
			logoutRunnerExecutor.submit(() -> {
				try {
					sendLogoutTransaction(userId, uuid, logoutType);
				} catch (Exception e) {
					logger.error("sendLogoutTransaction Runner error. USERID{"+userId+"}", e);
				}
			});
		}

		sessionStatusManager.manageLoginStatus(userId);
	}
	
	public void logout(String userId, String uuid, boolean logoutTransaction, LogoutType logoutType, CountDownLatch latch) {

		sessionManager.removeLogin(userId);
		sessionManager.removeHTTPLogin(uuid);
		
		sessionManager.removeWebSocketTimeout(userId); // logout시 timeout 해제

		logger.info(userId + " 사용자 연결 끊어짐으로 로그아웃 거래 수행.");
		
		if(logoutTransaction) {
			logoutRunnerExecutor.submit(() -> {
				try {
					sendLogoutTransaction(userId, uuid, logoutType);
					latch.countDown();
					logger.info("Remove Cache And Logout. key= " + userId + "remain=" + latch.getCount());
				} catch (Exception e) {
					logger.error("sendLogoutTransaction Runner error. USERID{"+userId+"}", e);
				}
			});
		} else {
			latch.countDown();
			logger.info("cache has been removed but not a logoutTransaction. key= " + userId + "remain=" + latch.getCount());
	         SessionHistoryLogger.getInstance().log(
	                 new SessionHistoryDTO("LOGOUT( "+userId+" )NOT-TRANSACTION", SessionHistoryVO.convertLogoutType_EnumToString(logoutType), "N", uuid, null, false));
		}

		sessionStatusManager.manageLoginStatus(userId);
	}
	
	public void removeFromLoginCache(String userId) {
		logger.debug("removeFromLoginCache] userId : " + userId);
		sessionManager.removeLogin(userId);
	}

	public void removeFromHttpCache(String uuid) {
		logger.debug("removeFromHttpCache] uuid : " + uuid);
		sessionManager.removeHTTPLogin(uuid);
	}
	
	public void removeFromHttpCacheByUserId(String userId) {
		logger.debug("removeFromHttpCacheByUserId] userId : " + userId);
		for(String uuid : sessionManager.getHttpLoginCache().getKeys()) {
			SessionVO httpLogin = sessionManager.getHttpLogin(uuid);
			if(StringUtils.equals(userId, httpLogin.getLoginUserId())) {
				sessionManager.removeHTTPLogin(uuid);
			}
		}
	}

	public void onAttachWebSocketSession(String adapterGroupName, WebSocket conn, String userId) {

		logger.debug("onAttachWebSocketSession] adapterGroupName : " + adapterGroupName + " userId : " + userId);
		SessionVO sessionVo = SessionManager.getInstance().getLogin(userId);

		webSocketSessionManager.sessionLogin(adapterGroupName, conn, userId);

		// WebSocket connection instance
		String serverName = EAIServerManager.getInstance().getLocalServerName();
		sessionVo.setWsInstId(serverName);
		sessionVo.setWsAdapterGroupName(adapterGroupName);
		
		sessionManager.putLogin(userId, sessionVo); // refresh
		sessionManager.putHttpLogin(sessionVo.getUuid(), sessionVo); // refresh
		
		sessionManager.removeWebSocketTimeout(userId); // webSocket 연결 시 timeout 해제
		
		SessionInfoVO sessionInfoVO = new SessionInfoVO(userId);
		sessionInfoVO.setWsocInstance(serverName);
		sessionInfoVO.setWsocRemoteAddr(conn.getRemoteSocketAddress().toString());
		sessionInfoVO.setWsocSessionStatus(Status.active);

		sessionStatusManager.manageStatus(sessionInfoVO);
	}
	
	public void onDetachWebSocketSession(String adapterGroupName, WebSocket conn) {
		
		logger.debug("onDetachWebSocketSession] adapterGroupName : " + adapterGroupName + " connection : " + conn.getRemoteSocketAddress());
		webSocketSessionManager.onCloseEvent(adapterGroupName, conn);
		
	}
	
	public void closeWebSocketByUserId(String userId, boolean logoutTransaction, LogoutType logoutType) {
		
		logger.debug("closeWebSocketByUserId] userId : " + userId + " logoutTransaction : " + logoutTransaction + " logoutType : " + logoutType.name());
		
		for (String adapterGroupName : webSocketSessionManager.getAdapterGroupNames()) {
			try {
				if (webSocketSessionManager.isContainKeys(adapterGroupName, userId)) {
					WebSocket conn = webSocketSessionManager.getSession(adapterGroupName, userId);
					if (conn != null) {
						if (logger.isDebug()) {
							logger.debug("Close WebSocket Connection : " + conn.getRemoteSocketAddress());
						}
						webSocketSessionManager.closeSession(adapterGroupName, userId);
					} else {
						logger.debug("closeWebSocketByUserId] userId ["+userId+"]connection is NULL. closeWebSocket skipped");
					}
				}
			} catch (Exception e) {
				// Logging And Skip
				logger.error("closeSession Error : " + e.getMessage(), e);
				continue;
			}
		}
		
		if(logoutTransaction) {
			logoutRunnerExecutor.submit(() -> {
				try {
					sendLogoutTransaction(userId, null, logoutType);
				} catch (Exception e) {
					logger.error("sendLogoutTransaction Runner error. USERID{"+userId+"}", e);
				}
			});
		}
		
		SessionStatusManager.getInstance().manageWebSocketStatus(userId, Status.inactive);
	}
	
	public void closeWebSocketByAdapterGroupAndUserId(String adapterGroupName, String userId, boolean logoutTransaction, LogoutType logoutType) {
		
		if(webSocketSessionManager.getAdapterGroupNames().contains(adapterGroupName)) {
			try {
				WebSocket conn = webSocketSessionManager.getSession(adapterGroupName, userId);
				if (conn != null) {
					if (logger.isDebug()) {
						logger.debug("Close WebSocket Connection : " + conn.getRemoteSocketAddress());
					}
					webSocketSessionManager.closeSession(adapterGroupName, userId);
				} else {
					logger.debug("closeWebSocketByUserId] userId ["+userId+"]connection is NULL. closeWebSocket skipped");
				}
			} catch (Exception e) {
				// Logging And Skip
				logger.error("closeSession Error : " + e.getMessage(), e);
			}
		}
		
		if(logoutTransaction) {
			logoutRunnerExecutor.submit(() -> {
				try {
					sendLogoutTransaction(userId, null, logoutType);
				} catch (Exception e) {
					logger.error("sendLogoutTransaction Runner error. USERID{"+userId+"}", e);
				}
			});
		}
		
		SessionStatusManager.getInstance().manageWebSocketStatus(userId, Status.inactive);
	}
	
	public void refreshLoginCache(String userId, SessionVO sessionVO) {
		sessionManager.putLogin(userId, sessionVO);
	}
	
	public void refreshHttpLoginCache(String uuid, SessionVO sessionVO) {
		sessionManager.putHttpLogin(uuid, sessionVO);
	}
	
	public void onDateChange() {
		
		logger.debug("DateChange] Start");
		
		webSocketSessionManager.closeSessionAll();
		
		try {
			
			removeAllLocalCache(false, LogoutType.DateChange);
			
		} catch (Exception e) {
			logger.error("removeAllLocalCache error.", e);
		}
		
		logger.debug("DateChange] End");
	}
	
	public void onServerDown() {

		logger.debug("ServerDown] Start");

		webSocketSessionManager.closeSessionAll();

		try {
			removeAllLocalCache(true, LogoutType.ServerDown);
		} catch (Exception e) {
			logger.error("removeAllLocalCache error.", e);
		} finally { // onServerDown시 Executor와 sessionManager를 close한다.
			if (logoutRunnerExecutor != null)
				try {
					logoutRunnerExecutor.shutdown();
				} catch (Exception e) {
				}
			if (sessionManager != null)
				try {
					sessionManager.closeManager();
				} catch (Exception e) {
				}

		}
		if (logoutRunnerExecutor != null) {
			logoutRunnerExecutor.shutdown();
		}
		logger.debug("ServerDown] End");
	}
	
	private void removeAllLocalCache(boolean logoutTransaction, LogoutType logoutType) throws InterruptedException {
		
		List<String> loginCacheKeys = sessionManager.getLoginCache().getKeys();
		CountDownLatch latch = new CountDownLatch(loginCacheKeys.size());
		
		for(String loginKey : loginCacheKeys) {
			SessionVO login = sessionManager.getLogin(loginKey);
			if(login != null) {
				String localServerName = EAIServerManager.getInstance().getLocalServerName();
				String wsInstId = login.getWsInstId();
				String loginInstId = login.getLoginInstId();
				if((wsInstId == null && localServerName.equals(loginInstId))// http login 을 했는데 websocket 로그인이 안된경우
									 || localServerName.equals(wsInstId)) {	// stop 하는 인스턴스의 websocket을 구별해서
					logout(login.getLoginUserId(), login.getUuid(), logoutTransaction, logoutType, latch);
				} else {
					latch.countDown();
					logger.info("pass other instance. key= " + loginKey + "remain=" + latch.getCount());
				}
			} else {
				latch.countDown();
				logger.info("pass sessionVo is null. key= " + loginKey + "remain=" + latch.getCount());
			}
		}
	
		boolean await = latch.await(180, TimeUnit.SECONDS);
		
		if(await) {
			logger.debug("removeAllLocalCache success. remain latch count : " + latch.getCount());
		} else {
			logger.warn("latch timeout occurred. remain count : " + latch.getCount());
		}
		
	}

	private void sendLogoutTransaction(String userId, String uuid, LogoutType logoutType) throws Exception {
//		
//		String historyDstcd = SessionHistoryVO.convertLogoutType_EnumToString(logoutType);
//		
//      	PropManager pManager = PropManager.getInstance();
//        Properties loginProp  = pManager.getProperties("LOGIN");
//        Properties stdMessageProp  = pManager.getProperties("STDMESSAGE");
//        EAIServerManager serverManager = EAIServerManager.getInstance();
////    	String envMode = System.getProperty(Keys.EAI_SYSTEMMODE);
//        String envMode = serverManager.getSystemEnvTypeConvert();
//        
//        String logoutServiceId = loginProp.getProperty("LOGOUT_SERVICE_ID");
//        String logoutInterfaceId = loginProp.getProperty("LOGOUT_INTERFACE_ID");
//        String messageVersion = stdMessageProp.getProperty("VERSION");
//        
//        String currentTimeMills = DatetimeUtil.getCurrentTimeMillis();
//        
//        String systemCode = "MCC";
//        String guid = UUIDGenerator.getGUID(systemCode);
//        STDMessage stdmsg = new STDMessage();
//        stdmsg.setData("systemHeader.VRSN", messageVersion);  //버전
//        stdmsg.setData("systemHeader.IPOP_DVSN_CD", "I");  //입력출력구분코드
//        stdmsg.setData("systemHeader.FRST_GUID", guid);  //최초GUID
//        stdmsg.setData("systemHeader.GUID", guid);  //GUID
//        //stdmsg.setData("systemHeader.PRGR_SN", "1");  //진행일련번호
//        //stdmsg.setData("systemHeader.TLGR_ENCRT_YN", "N");  //전문암호화여부
//        stdmsg.setData("systemHeader.CHNL_DVSN_CD", "00");  //채널구분
//        stdmsg.setData("systemHeader.IP_VRSN_DVSN_CD", "V4");  //IP버전구분코드
//        stdmsg.setData("systemHeader.ENVR_INFO_DVSN_CD", envMode);  //환경정보구분코드
//        stdmsg.setData("systemHeader.FRST_DMND_APLC_SYS_CD", systemCode);  //최초요청어플리케이션시스템코드
//        stdmsg.setData("systemHeader.FRST_DMND_INTF_SYS_CD", systemCode);  //최초요청인터페이스시스템코드
//        stdmsg.setData("systemHeader.TRSM_SYS_CD", systemCode);  //전송시스템코드
//        stdmsg.setData("systemHeader.MCI_INTF_ID", logoutInterfaceId);  //MCI_인터페이스ID
//        stdmsg.setData("systemHeader.FRST_TLGR_DMND_DT", currentTimeMills);  //최초전문요청일시
//        stdmsg.setData("systemHeader.DMND_SYS_CD", systemCode);  //요청시스템코드
//        stdmsg.setData("systemHeader.TLGR_DMND_DT", currentTimeMills);  //전문요청일시
//        stdmsg.setData("systemHeader.DLNG_PRCS_DVSN_CD", "S");  //거래처리구분코드
//        
//        stdmsg.setData("systemHeader.RSPNS_RSLT_DVSN_CD", "N");  //응답결과구분코드
//        
//        //stdmsg.setData("systemHeader.MULTILNG_DVSN_CD", "ko");  //다국어구분코드
//        stdmsg.setData("systemHeader.DLNG_ID", logoutServiceId);  //거래ID
//        //stdmsg.setData("systemHeader.RTRCN_DLNG_YN", "N");  //취소거래여부
//        //stdmsg.setData("systemHeader.TLGR_TYP_DVSN_CD", "G");  //전문 유형코드
//        //stdmsg.setData("systemHeader.LVOL_DAT_PRCS_DVSN_CD", "N");  //대량데이터처리구분코드
//        //stdmsg.setData("systemHeader.LVOL_DAT_PRCS_SN", "0");  //대량데이터처리일련번호
//        //stdmsg.setData("systemHeader.SMLT_DLNG_DVSN_CD", "G");  //시뮬레이션 거래 구분코드
//        //stdmsg.setData("systemHeader.ACNT_MODE_DVSN_CD", "0");  //계정모드 구분코드
//        
//        //기존 대비 validation 추가 항목 - start - 20220310
//        stdmsg.setData("systemHeader.FRST_DMND_SYS_IP_VAL", serverManager.getServerIp());  //최초요청시스템IP값
//        stdmsg.setData("systemHeader.FRST_DMND_SYS_MAC_VAL", serverManager.getServerMac());  //최초요청시스템MAC값
//        stdmsg.setData("systemHeader.TMOT_CD", "S");  //타임아웃코드
//        //기존 대비 validation 추가 항목 - end  - 20220310
//        
//        stdmsg.setData("commonHeader.USERID", userId);  //사용자 ID
//        
//        //공통 헤더 셋팅 못함 
//        //청코드
//        //국코드
//        //취급청코드
//        //취급국코드
//        
//        String body = "{\"USERID\":\""+userId+"\"}";
//        String sendData = stdmsg.toJSONString(body);
//        
//        try {
//        	String eaiSvcCd = logoutInterfaceId + STDMessage.SEND_RECV_CD_SEND + STDMessage.DIRECTION_IN;
//            EAIMessage eaiMsg = EAIMessageManager.getInstance().getEAIMessage(eaiSvcCd);
//            if(eaiMsg == null){
//        		logger.error(eaiSvcCd+" 서비스가 존재하지 않음");
//        		return;
//            }
//            String adapterGroupName = eaiMsg.getCurrentSvcMsg().getPsvSysItfTp();
//            
//            AdapterGroupVO gvo = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
//            
//        	// 어댑터가 사용중이 아니면 Skip한다.
//        	if (gvo == null || gvo.isUsedFlag() == false) {
//        		logger.error(adapterGroupName+" 어댑터가 등록되어있지 않거나 미사용 상태라 로그아웃 미수행");
//        		return;
//        	}
//        	
//            AdapterVO avo = gvo.nextAdapterVO();
//            if (avo == null){
//        		logger.error(adapterGroupName+"에 가용한 어댑터가 없어 로그아웃 미수행");
//        		return;
//            }
//            
//            AdapterPropManager manager = AdapterPropManager.getInstance();
//            Properties outboundProp = (Properties)manager.getProperties(avo.getPropGroupName()).clone();	  
//            String charset = outboundProp.getProperty(HttpClientAdapterServiceKey.ENCODE, "utf-8");
//            
//            if(logger.isDebug())
//            	logger.debug("logout send >> "+sendData);
//            
//            byte[] reqBytes = sendData.getBytes(charset);
//            Properties tempProp = new Properties();
//            tempProp.put(HttpClientAdapterServiceKey.TRAN_CODE, logoutServiceId);
//            tempProp.setProperty(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME, adapterGroupName);
//            tempProp.setProperty(HttpClientAdapterServiceKey.ADAPTER_NAME, avo.getName());
//            
//            byte[] resBytes = null;
//            if ("SOCKETOUT".equals(eaiMsg.getCurrentSvcMsg().getOutbRtnNm()) ){
//            	resBytes = SocketSender.callService(adapterGroupName, outboundProp, reqBytes, tempProp);
//            }else{
//            	resBytes = HttpSender.callService(adapterGroupName, outboundProp, reqBytes, tempProp);
//            }
//            
//            if(logger.isDebug()){
//            	String recvData = new String(resBytes, charset);
//            	logger.debug("logout recv << "+recvData);
//            }
//            
//            ExtMessage resExtMessage = ExtMessageConverter.getExtMessageForStandard(resBytes, gvo.getMessageType());
//            
//            SessionHistoryLogger.getInstance().log(
//            		new SessionHistoryDTO(sendData,	historyDstcd, resExtMessage.getResponseType(), uuid, null, true));
//            
//        } catch (Exception e) {
//        	
//        	SessionHistoryLogger.getInstance().log(
//            		new SessionHistoryDTO(sendData,	historyDstcd, "C", uuid, null, true));
//        	
//        	throw e;
//        }
        
	}
	
}
