package com.eactive.eai.common.session;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.ignite.events.CacheEvent;
import org.apache.ignite.events.EventType;
import org.apache.ignite.internal.binary.BinaryObjectImpl;
import org.apache.ignite.lang.IgnitePredicate;

import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.session.ChannelSessionFacade.LogoutType;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.worker.Future;
import com.eactive.eai.common.worker.ResponseMap;

public class IgniteEvent implements IgnitePredicate<CacheEvent> {
	private static final long serialVersionUID = 1L;
	static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

	private static final ExecutorService executor = new ThreadPoolExecutor(20, 20, 0, TimeUnit.SECONDS,
			new LinkedBlockingQueue<Runnable>());

	@Override
	public boolean apply(CacheEvent event) {
		IgniteEventDataBundle bundle = new IgniteEventDataBundle(event);

		if (logger.isDebug()) {
			logger.debug("localListener] local event<"+ event.name() +"> [name= " + bundle.getCacheName() + ", " + "key=" + bundle.getKey() + ", "
					+ "oldVal=" + bundle.getOldValue() + ", " + "newVal=" + bundle.getNewValue());
		}

//		if (SessionManager.TOPIC_CAHCE_NAME.equals(bundle.getCacheName()) && EventType.EVT_CACHE_OBJECT_PUT == bundle.getEventType()) {
//			removeTopic(bundle);
//		} 
		if (SessionManager.TOPIC_CAHCE_NAME.equals(bundle.getCacheName()) && EventType.EVT_CACHE_OBJECT_PUT == bundle.getEventType()) {
			Future future = ResponseMap.getInstance().get(event.key());
			if ( future == null) {
				if (logger.isDebug()) {
					logger.debug("localListener] event.key["+event.key() + "] skip to Future");
				}
			} else {
				if (logger.isDebug()) {
					logger.debug("localListener] event.key["+event.key() + "] put to Future");
				}
				EAIMessage data = (EAIMessage)event.newValue();
				future.getSlot().put(data);
			}
		} else 
		if (SessionManager.LOGIN_CAHCE_NAME.equals(bundle.getCacheName())
				&& EventType.EVT_CACHE_OBJECT_REMOVED == bundle.getEventType()) {
//			if (logger.isWarn()) {
//				logger.warn("localListener] loginSession removed event[" + bundle.getKey() + "] start");
//			}
//			if (isWebSocketContainer(bundle)) {
//				closeWebSocket(bundle);
//			}
		} else if (SessionManager.LOGIN_CAHCE_NAME.equals(bundle.getCacheName())
				&& EventType.EVT_CACHE_OBJECT_EXPIRED == bundle.getEventType()) {
//			if (logger.isWarn()) {
//				logger.warn("localListener] loginSession expired event[" + bundle.getKey() + "] start");
//			}
//			if (isWebSocketContainer_or_httpLoginContainer(bundle)) {
//				SessionStatusManager.getInstance().manageLoginStatus(bundle.getKey());
//				closeWebSocketWithLogoutTransaction(bundle);
//			}
		} else if (SessionManager.HTTP_CAHCE_NAME.equals(bundle.getCacheName())
				&& EventType.EVT_CACHE_OBJECT_EXPIRED == bundle.getEventType()) {
			if (logger.isWarn()) {
				logger.warn("localListener] httpLogin expired event[" + bundle.getKey() + "] start");
			}
//			if (isWebSocketContainer_or_httpLoginContainer(bundle)) {
//				removeLoginCacheWithLogoutTransaction(bundle, LogoutType.AutoLogout);
//			}
			manageLoginStatusWithUUID(bundle);
		} else if (SessionManager.WEBSOCKER_CAHCE_NAME.equals(bundle.getCacheName())
				&& EventType.EVT_CACHE_OBJECT_EXPIRED == bundle.getEventType()) {
//			if (logger.isWarn()) {
//				logger.warn("localListener] webSocketTimeout expired event[" + bundle.getKey() + "] start");
//			}
//			if (isHTTPLoginContainer(bundle)) {
//				removeLoginCacheWithLogoutTransaction(bundle, LogoutType.WebSocketTimeout);
//			}
		}
		return true; // Continue listening.
	}

	private void removeTopic(IgniteEventDataBundle bundle) {
		EAIMessage data = (EAIMessage) bundle.getNewValue();
		if (logger.isDebug()) {
			logger.debug("localListener] setEvent[" + bundle.getKey() + "]");
		}
		Future future = ResponseMap.getInstance().get(bundle.getKey());
		if (future != null) {
			if (logger.isDebug()) {
				logger.debug("localListener] setEvent[" + bundle.getKey() + "] put to Future");
			}
			future.getSlot().put(data);
			SessionManager.getInstance().removeTopic(bundle.getKey());
		}
	}

	private boolean isWebSocketContainer(IgniteEventDataBundle bundle) {
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession setExpireEvent[" + bundle.getKey() + "] start");
		}

		SessionVO sessionVo = bundle.getSessionVO();

		if (sessionVo == null) {
			if (logger.isWarn()) {
				logger.warn("localListener] loginSession setExpireEvent SessionVO not found! - " + bundle.getKey());
			}
			return false;
		}

		EAIServerManager eaiServerManager = EAIServerManager.getInstance();
		String serverName = eaiServerManager.getLocalServerName();
		String webSocketInstanceName = sessionVo.getWsInstId();

		if (webSocketInstanceName == null) {
			return false;
		} else if (serverName.equals(webSocketInstanceName)) {
			return true;
		}
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession setExpireEvent[" + bundle.getKey() + "] skip.. ");
		}
		return false;
	}

	private boolean isWebSocketContainer_or_httpLoginContainer(IgniteEventDataBundle bundle) {
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession event[" + bundle.getKey() + "] start");
		}

		SessionVO sessionVo = bundle.getSessionVO();

		if (sessionVo == null) {
			if (logger.isWarn()) {
				logger.warn("localListener] loginSession event SessionVO not found! - " + bundle.getKey());
			}
			return false;
		}

		EAIServerManager eaiServerManager = EAIServerManager.getInstance();
		String serverName = eaiServerManager.getLocalServerName();
		String webSocketInstanceName = sessionVo.getWsInstId();

		if (webSocketInstanceName == null) {
			if (serverName.equals(sessionVo.getLoginInstId())) {
				return true;
			}
		} else if (serverName.equals(webSocketInstanceName)) {
			return true;
		}

		if (logger.isWarn()) {
			logger.warn("localListener] loginSession event[" + bundle.getKey() + "] skip.. ");
		}
		return false;
	}

	private boolean isHTTPLoginContainer(IgniteEventDataBundle bundle) {
		SessionVO sessionVo = bundle.getSessionVO();
		if (sessionVo == null) {
			if (logger.isWarn()) {
				logger.warn("localListener] loginSession setExpireEvent SessionVO not found! - " + bundle.getKey());
			}
			return false;
		}

		String localServerName = EAIServerManager.getInstance().getLocalServerName();
		String httpLoginInstanceName = sessionVo.getLoginInstId();

		if (localServerName.equals(httpLoginInstanceName)) {
			return true;
		} else {
			if (logger.isWarn()) {
				logger.warn("localListener] loginSession setExpireEvent[" + bundle.getKey() + "] skip.. ");
			}
			return false;
		}
	}

	private void closeWebSocket(IgniteEventDataBundle bundle) {
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession closeWebSocket[" + bundle.getKey() + "] start");
		}

		SessionVO sessionVo = bundle.getSessionVO();

		String adapterGroupName = sessionVo.getWsAdapterGroupName();

		try {
			executor.execute(() -> {
				ChannelSessionFacade.getInstance().closeWebSocketByAdapterGroupAndUserId(adapterGroupName,
						bundle.getKey(), false, LogoutType.AutoLogout);
			});
			if (logger.isDebug()) {
				logger.debug("localListener] loginSession ChannelSessionFacade.closeWebSocketByAdapterGroupAndUserId ["
						+ bundle.getKey() + "] -" + adapterGroupName);
			}
		} catch (Exception ex) {
			logger.error(
					"localListener] loginSession login removed ChannelSessionFacade.closeWebSocketByAdapterGroupAndUserId failed, adapterGroupName ["
							+ adapterGroupName + "] loginUserId [" + bundle.getKey() + "]",
					ex);
		}
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession login removed[" + bundle.getKey() + "] end");
		}

	}

	private void closeWebSocketWithLogoutTransaction(IgniteEventDataBundle bundle) {
		if (logger.isWarn()) {
			logger.warn(
					"localListener] loginSession closeWebSocketWithLogoutTransaction[" + bundle.getKey() + "] start");
		}
		SessionVO sessionVo = bundle.getSessionVO();

		String adapterGroupName = sessionVo.getWsAdapterGroupName();

		try {
			executor.execute(() -> {
				ChannelSessionFacade.getInstance().closeWebSocketByAdapterGroupAndUserId(adapterGroupName,
						bundle.getKey(), true, LogoutType.AutoLogout);
				ChannelSessionFacade.getInstance().removeFromHttpCacheByUserId(bundle.getKey());
			});
			if (logger.isDebug()) {
				logger.debug("localListener] loginSession ChannelSessionFacade.closeWebSocketByAdapterGroupAndUserId ["
						+ bundle.getKey() + "] - adapterGroupName : " + adapterGroupName);
			}
		} catch (Exception ex) {
			logger.error(
					"localListener] loginSession login expired ChannelSessionFacade.closeWebSocketByAdapterGroupAndUserId failed, adapterGroupName ["
							+ adapterGroupName + "] loginUserId [" + bundle.getKey() + "]",
					ex);
		}
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession login expired[" + bundle.getKey() + "] end. ");
		}
	}

	private void removeLoginCacheWithLogoutTransaction(IgniteEventDataBundle bundle, LogoutType logoutType) {
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession removeLoginCacheWithLogoutTransaction [" + bundle.getKey()
					+ "] start");
		}

		SessionVO sessionVo = bundle.getSessionVO();
		try {
			executor.execute(() -> {
				ChannelSessionFacade.getInstance().logout(sessionVo.getLoginUserId(), sessionVo.getUuid(), true,
						logoutType);
			});
			if (logger.isDebug()) {
				logger.debug("localListener] loginSession ChannelSessionFacade.logout [" + bundle.getKey() + "]");
			}
		} catch (Exception e) {
			logger.error("localListener] loginSession HTTP expired ChannelSessionFacade.logout failed", e);
		}
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession HTTP expired [" + bundle.getKey() + "] end. ");
		}
	}

	private void manageLoginStatusWithUUID(IgniteEventDataBundle bundle) {
		if (logger.isWarn()) {
			logger.warn("localListener] loginSession manageLoginStatusWithUUID [" + bundle.getKey() + "] start");
		}

		SessionVO sessionVo = bundle.getSessionVO();

		SessionStatusManager.getInstance().manageLoginStatusWithUUID(sessionVo);
	}
	public void shutdown() {
		if(executor != null) {
			executor.shutdown();
		}
	}
}

class IgniteEventDataBundle {
	private String cacheName;
	private int eventType;
	private String key;
	private Object oldValue;
	private Object newValue;
	private SessionVO sessionVO;

	public IgniteEventDataBundle(CacheEvent event) {
		cacheName = event.cacheName();
		eventType = event.type();
		key = event.key();
		oldValue = event.oldValue();
		newValue = event.newValue();
		if (oldValue instanceof SessionVO) {
			sessionVO = (SessionVO) oldValue;
		}
		else if (oldValue instanceof BinaryObjectImpl) {
			BinaryObjectImpl b = (BinaryObjectImpl) oldValue;
			sessionVO = (SessionVO) b.deserialize();
		}
	}

	public String getCacheName() {
		return cacheName;
	}

	public int getEventType() {
		return eventType;
	}

	public String getKey() {
		return key;
	}

	public Object getOldValue() {
		return oldValue;
	}

	public Object getNewValue() {
		return newValue;
	}

	public SessionVO getSessionVO() {
		return sessionVO;
	}
}
