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

import com.eactive.eai.adapter.*;
import com.eactive.eai.adapter.http.dynamic.wrapper.KeepAliveFalseWrapper;
import com.eactive.eai.adapter.http.dynamic.wrapper.KeepAliveTrueWrapper;
import com.eactive.eai.adapter.http.util.HttpsGenerator;
import com.eactive.eai.agent.command.CommandException;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.util.Logger;
import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.UndertowOptions;
import io.undertow.server.handlers.*;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.servlet.api.ServletInfo;
import org.apache.commons.lang3.StringUtils;

import javax.net.ssl.SSLContext;
import javax.servlet.ServletException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

public class HttpDynamicPortInAdapterManager implements Lifecycle {
	public String HTTP_PARAM_TYPE = "HTTP_TYPE";
	public String HTTP_LISTEN_PORT = "LISTEN_PORT";
	public String API_PATH = "API_PATH";

	public String HTTPS_USE_YN = "HTTPS_USE_YN";
	public String HTTPS_KEY_STORE = "HTTPS_KEY_STORE";
	public String HTTPS_KEY_PASS = "HTTPS_KEY_PASS";

	public String MAX_CONNECTION = "MAX_CONNECTION";
	public String QUEUE_SIZE = "QUEUE_SIZE";
	public String ALLOW_IP = "ALLOW_IP";

	public final String IO_THREADS = "IO_THREADS";
	public final String WORKER_THREADS = "WORKER_THREADS";
	
	//keep alive 관련
	public final String KEEP_ALIVE_YN = "KEEP_ALIVE_YN";
	public final String KEEP_ALIVE_TIMEOUT = "KEEP_ALIVE_TIMEOUT";
	public final String MAX_REQUEST_COUNT = "MAX_REQUEST_COUNT";

	Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

	private static HttpDynamicPortInAdapterManager instance = new HttpDynamicPortInAdapterManager();

	// url - Adapter Map
	private ConcurrentHashMap<String, HttpDynamicInAdapterUri> adptUriMap = new ConcurrentHashMap<String, HttpDynamicInAdapterUri>();

	// adapter Name - Adapter Map
	private ConcurrentHashMap<String, HttpDynamicInAdapterUri> adptNameMap = new ConcurrentHashMap<String, HttpDynamicInAdapterUri>();

	// Listen Port - Adapter Map
	private ConcurrentHashMap<Integer, ConcurrentHashMap> adptPortMap = new ConcurrentHashMap<Integer, ConcurrentHashMap>();

	// Listen Port - Adapter Map
	private ConcurrentHashMap<Integer, Undertow> svrPortMap = new ConcurrentHashMap<Integer, Undertow>();

	@SuppressWarnings("unchecked")
	public void addAdapter(AdapterVO adapter) throws Exception {
		String adapterFullName;

		HttpDynamicInAdapterUri oldAdapterUri, newAdapterUri;

		String oldUri, newUri;

		// Port별 Adapter 목록 매핑
		ConcurrentHashMap<String, HttpDynamicInAdapterUri> adptPortSubMap;

		// 기존 Port Listener
		Undertow oldListener;

		AdapterGroupVO gvo = adapter.getAdapterGroupVO();

		if (gvo != null & gvo.getType().equals(Keys.TYPE_HTTP_CUSTOM) && gvo.getGubun().equals("I")) {
			// Server Adapter인 경우 초기화
			adapterFullName = adapter.getAdapterGroupName() + "." + adapter.getName();
			oldAdapterUri = adptNameMap.get(adapterFullName);

			AdapterPropManager manager = AdapterPropManager.getInstance();
			Properties httpProp = manager.getProperties(adapter.getPropGroupName());
			String httpType = httpProp.getProperty(HTTP_PARAM_TYPE, "STANDARD");
			String portString = httpProp.getProperty(HTTP_LISTEN_PORT, "0");
			Integer listenPort = Integer.valueOf(portString);
			if (listenPort.intValue() == 0) {
				throw new CommandException("LISTEN_PORT is required.");
			}

			newUri = httpProp.getProperty(API_PATH);
			if (!StringUtils.equals(newUri, "/")) {
				newUri = StringUtils.removeEnd(newUri, "/");
			}
			if (StringUtils.isBlank(newUri)) {
				throw new CommandException("API_PATH is required.");
			}

			// /api/vi/public
			if (!StringUtils.startsWith(newUri, "/")) {
				newUri = "/" + newUri;
			}

			newAdapterUri = new HttpDynamicInAdapterUri(adapter.getAdapterGroupName(), adapter.getName(), listenPort,
					newUri, httpType);

			adptPortSubMap = null;
			oldListener = null;
		} else {
			// Client Adapter 인 경우 무시
			return;
		}

		// 1. Adapter 명으로 기 등록된 Adapter인지 확인
		// -- 이미 등록된 Adapter 이면
		if (oldAdapterUri != null) {

			if (oldAdapterUri.getListenPort() != 0) {
				oldUri = adapterFullName + "/" + oldAdapterUri.getUri();

				// 2. Port별 Adapter 정보 확인 및 제거
				Integer listenPort = oldAdapterUri.getListenPort();

				adptPortSubMap = adptPortMap.get(listenPort);
				if (adptPortSubMap != null) {
					adptPortSubMap.remove(adapterFullName);
				}

				// 2.1 해당 Port에 추가 Adapter가 없는 경우 해당 Listener 제거
				if (adptPortSubMap == null || adptPortSubMap.size() == 0) {

					// 2.1.1 해당 Port에 추가 Adapter가 없는 경우 Port 매핑 제거
					adptPortMap.remove(listenPort);
					// 2.1.2 해당 Port에 추가 Adapter가 없는 경우 해당 Listener 제거
					oldListener = svrPortMap.get(listenPort);
					svrPortMap.remove(listenPort);
					if (oldListener != null)
						oldListener.stop();
				}

				// 3. Adapter명 별 Adapter 정보 확인 및 제거
				adptNameMap.remove(adapterFullName);
			} else {
				oldUri = oldAdapterUri.getUri();
				// Default Adapter (WAS) 처리
				// adptUriMap만 처리
				adptUriMap.remove(oldUri);
			}

		}

		// 신규 Adapter 추가
		if (newAdapterUri.getListenPort() != 0) {
			// Port 별 Adapter 정보 등록
			adptPortSubMap = adptPortMap.get(newAdapterUri.getListenPort());
			if (adptPortSubMap == null) {
				adptPortSubMap = new ConcurrentHashMap<String, HttpDynamicInAdapterUri>();
				adptPortMap.put(newAdapterUri.getListenPort(), adptPortSubMap);
			}
			adptPortSubMap.put(newAdapterUri.getUri(), newAdapterUri);

			// Adapter명 별 Adapter 정보 등록
			adptNameMap.put(adapterFullName, newAdapterUri);
		} else {
			// Default Adapter (WAS) 처리
			// adptUriMap만 등록
			adptUriMap.put(newUri, newAdapterUri);
		}
	}

	@SuppressWarnings("unchecked")
	public void removeAdapter(AdapterVO adapter) throws Exception {

		String adapterFullName;
		HttpDynamicInAdapterUri oldAdapterUri;

		// Port별 Adapter 목록 매핑
		ConcurrentHashMap<String, HttpDynamicInAdapterUri> adptPortSubMap;

		// 기존 Port Listener
		Undertow oldListener;

		AdapterGroupVO gvo = adapter.getAdapterGroupVO();

		if (gvo != null && gvo.getType().equals(Keys.TYPE_HTTP_CUSTOM) && gvo.getGubun().equals("I")) {
			// Server Adapter 인 경우 초기화
			adapterFullName = adapter.getAdapterGroupName() + "." + adapter.getName();
			oldAdapterUri = adptNameMap.get(adapterFullName);
			adptPortSubMap = null;
			oldListener = null;
		} else {
			// Client Adapter 인 경우 무시
			return;
		}

		if (oldAdapterUri != null) {
			if (oldAdapterUri.getListenPort() != 0) {

				// 2. Port 별 Adapter 정보 확인 및 제거
				Integer listenPort = oldAdapterUri.getListenPort();

				adptPortSubMap = adptPortMap.get(listenPort);
				HttpDynamicInAdapterUri httpDynamicInAdapterUri = adptPortSubMap.get(oldAdapterUri.getUri());

				// 20211201 어댑터 그룹체크, 동일하면 삭제
				if (httpDynamicInAdapterUri.getAdptGrpName().equals(oldAdapterUri.getAdptGrpName())) {
					adptPortSubMap.remove(oldAdapterUri.getUri());
				}

				// 2.1 해당 Port에 추가 Adapter 없는 경우 해당 Listener 제거
				if (adptPortSubMap.size() == 0) {
					// 2.1.1 해당 Port에 추가 Adapter 없는 경우 Port 매핑 제거
					adptPortMap.remove(listenPort);
					// 2.1.2 해당 Port에 추가 Adapter 없는 경우 해당 Listener 제거
					oldListener = svrPortMap.get(listenPort);
					try {
						if (oldListener != null) {
							oldListener.stop();
						}
					} catch (Throwable t) {
						// 중지 실패시 원복
						adptPortSubMap.put(oldAdapterUri.getUri(), oldAdapterUri);
						adptPortMap.put(listenPort, adptPortSubMap);
						throw t;
					}

					// stop 성공후 삭제
					svrPortMap.remove(listenPort);
				}

				// 3. Adapter명 별 Adapter 정보 확인 및 제거
				adptNameMap.remove(adapterFullName);
			} else {
				// Default Adapter(WAS) 처리
				// adptUriMap만 처리
				adptUriMap.remove(oldAdapterUri.getUri());
			}
		}
	}

	public static HttpDynamicPortInAdapterManager getInstance() {
		return instance;
	}

	public HttpDynamicInAdapterUri getAdptUri(String uri) {
		return adptUriMap.get(uri);
	}

	@SuppressWarnings("unchecked")
	public HttpDynamicInAdapterUri getAdptUri(Integer port, String uri) {
		ConcurrentHashMap<String, HttpDynamicInAdapterUri> tmpAdptUriMap = adptPortMap.get(port);
		if (tmpAdptUriMap != null)
			return tmpAdptUriMap.get(uri);
		return null;
	}

	@Override
	public void addLifecycleListener(LifecycleListener listener) {
		
	}

	@Override
	public LifecycleListener[] findLifecycleListeners() {
		
		return null;
	}

	@Override
	public void removeLifecycleListener(LifecycleListener listener) {
		
	}

	public void start(AdapterVO adapter) throws Exception {
		AdapterGroupVO gvo = adapter.getAdapterGroupVO();

		if (gvo != null && gvo.getType().equals(Keys.TYPE_HTTP_CUSTOM) && gvo.getGubun().equals("I")) {
			// Server Adapter 인 경우 정보 확인
			AdapterPropManager adapterManager = AdapterPropManager.getInstance();
			Properties httpProp = adapterManager.getProperties(adapter.getPropGroupName());

			String httpsUseYn = httpProp.getProperty(HTTPS_USE_YN, "N");
			String httpsKeyStore = httpProp.getProperty(HTTPS_KEY_STORE, null);
			String httpsKeyPass = httpProp.getProperty(HTTPS_KEY_PASS, null);

			Integer listenPort = Integer.valueOf(httpProp.getProperty(HTTP_LISTEN_PORT, "0"));
			Integer max_connection = Integer.valueOf(httpProp.getProperty(MAX_CONNECTION, "100"));
			Integer max_queue = Integer.valueOf(httpProp.getProperty(QUEUE_SIZE, "3000"));
			String allowIp = httpProp.getProperty(ALLOW_IP, null);

			Integer ioThreads = Integer.valueOf(httpProp.getProperty(IO_THREADS, "2"));
			Integer workerThreads = Integer.valueOf(httpProp.getProperty(WORKER_THREADS, "16"));
			
			String keepAliveYn = httpProp.getProperty(KEEP_ALIVE_YN, "Y");
			Integer keepAliveTimeout = Integer.valueOf(httpProp.getProperty(KEEP_ALIVE_TIMEOUT, "15"));
			Integer maxRequestCount = Integer.valueOf(httpProp.getProperty(MAX_REQUEST_COUNT, "-1"));

			boolean isKeepAlive = false;
			if ("Y".equals(keepAliveYn)) {
				isKeepAlive = true;
			}			

			// Custom Adapter 인 경우 재시작
			if (listenPort != 0) {
				Undertow server = svrPortMap.get(listenPort);
				if (server != null) {
					// server.stop();
					// svrPortMap.remove(listenPort);

					return;
				}

				ServletInfo servletInfo = Servlets.servlet(HttpAdapterCustom.class);
				servletInfo.addMapping("/*");
				ServletInfo healthCheckServletInfo = Servlets.servlet(UndertowHealthCheckServlet.class);
				healthCheckServletInfo.addMapping("healthcheck.html");

				DeploymentInfo servletBuilder = Servlets.deployment()
						.setClassLoader(HttpDynamicPortInAdapterManager.class.getClassLoader())
						.setDeploymentName("HttpDynamicCustomAdapter").setContextPath("/")
						.addServlet(healthCheckServletInfo).addServlet(servletInfo);
				if (isKeepAlive) {
					KeepAliveTrueWrapper wrapper = new KeepAliveTrueWrapper();
					wrapper.setKeepAliveTimeout(keepAliveTimeout);
					wrapper.setMaxRequestCount(maxRequestCount);
					servletBuilder.addInitialHandlerChainWrapper(wrapper);
				} else {
					servletBuilder.addInitialHandlerChainWrapper(new KeepAliveFalseWrapper());
				}				
				DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
				manager.deploy();

				PathHandler path = null;
				IPAddressAccessControlHandler ipAddressAccessControlHandler = null;
				RequestLimitingHandler requestLimitHandler = null;

				try {
					path = Handlers.path(Handlers.redirect("/")).addPrefixPath("/", manager.start());
					if (allowIp != null && allowIp.length() > 1) {
						String[] allowIps = allowIp.split(",");
						ipAddressAccessControlHandler = Handlers.ipAccessControl(path, false);
						for (int i = 0; i < allowIps.length; i++) {
							ipAddressAccessControlHandler.addAllow(allowIps[i]);
						}
					}

					RequestLimit requestLimit = new RequestLimit(max_connection, max_queue);
					requestLimit.setFailureHandler(ResponseCodeHandler.HANDLE_500);

					if (ipAddressAccessControlHandler != null) {
						requestLimitHandler = Handlers.requestLimitingHandler(requestLimit,
								ipAddressAccessControlHandler);
					} else {
						requestLimitHandler = Handlers.requestLimitingHandler(requestLimit, path);
					}

				} catch (ServletException e) {
					logger.error(e.getMessage(), e);
				}

				SSLContext sslContext = null;
				if (StringUtils.equalsIgnoreCase(httpsUseYn, "Y")) {
//					if ((httpsKeyStore != null && httpsKeyStore.length() > 1)
//							&& (httpsKeyPass != null && httpsKeyPass.length() > 1)) {
						sslContext = HttpsGenerator.createSSLContext(httpsKeyStore, httpsKeyPass);
//					}
				}

				logger.debug("####[" + adapter.getName() + "] ioThreads-->" + ioThreads);
				logger.debug("####[" + adapter.getName() + "] workerThreads-->" + workerThreads);
	            Undertow.Builder builder = Undertow.builder().setIoThreads(ioThreads).setWorkerThreads(workerThreads);
	            if (sslContext != null) {
	               builder.addHttpsListener(listenPort, "0.0.0.0", sslContext);
//	               server = Undertow.builder().setIoThreads(ioThreads).setWorkerThreads(workerThreads)
//	                     .addHttpsListener(listenPort, "0.0.0.0", sslContext).setHandler(requestLimitHandler)
//	                     .build();
	            } else {
	               builder.addHttpListener(listenPort, "0.0.0.0");
//	               server = Undertow.builder().setIoThreads(ioThreads).setWorkerThreads(workerThreads)
//	                     .addHttpListener(listenPort, "0.0.0.0").setHandler(requestLimitHandler).build();
	            }
	            if (isKeepAlive){
	               builder.setServerOption(UndertowOptions.NO_REQUEST_TIMEOUT, keepAliveTimeout * 1000);
	            }
	            builder.setHandler(requestLimitHandler);
	            server = builder.build();

				try {
					server.start();
				} catch (Throwable t) {
					logger.error(t.getMessage());
					String adapterFullName = adapter.getAdapterGroupName() + "." + adapter.getName();
					throw new Exception("failed start: " + adapterFullName);
				}

				svrPortMap.put(listenPort, server);
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void stop(AdapterVO adapter) throws Exception {
		AdapterGroupVO gvo = adapter.getAdapterGroupVO();

		if (gvo != null && gvo.getType().equals(Keys.TYPE_HTTP_CUSTOM) && gvo.getGubun().equals("I")) {
			// Server Adapter 인 경우 정보 확인
			AdapterPropManager adapterManager = AdapterPropManager.getInstance();

			Properties httpProp = adapterManager.getProperties(adapter.getPropGroupName());
			String portString = httpProp.getProperty(HTTP_LISTEN_PORT, "0");
			Integer listenPort = Integer.valueOf(portString);

			// Custom Adapter 인 경우 재시작
			if (listenPort != 0) {
				ConcurrentHashMap<String, HttpDynamicInAdapterUri> uriMap = adptPortMap.get(listenPort);
				// 해당 Port로 수신하는 Adapter가 1개 이상 존재하는 경우 재시작 하지 않음 - 성능/안전성 이슈
				if (uriMap != null && uriMap.size() > 0)
					return;

				Undertow server = svrPortMap.get(listenPort);
				if (server != null) {
					server.stop();
					svrPortMap.remove(listenPort);
				}
			}
		}
	}

	@Override
	@SuppressWarnings({ "rawtypes" })
	public void start() throws LifecycleException {
		try {
			AdapterManager adptManager = AdapterManager.getInstance();
			HashMap adptGrps = adptManager.getAllAdapterGroupVO();

			for (Object obj : adptGrps.values()) {
				AdapterGroupVO gvo = (AdapterGroupVO) obj;
				if (gvo.getType().equals(Keys.TYPE_HTTP_CUSTOM) && gvo.getGubun().equals("I")) {
					Iterator i = gvo.getAdapters();
					while (i.hasNext()) {
						AdapterVO avo = (AdapterVO) i.next();
						addAdapter(avo);
					}
				}
			}

//	        adptPortMap.forEach((port,uriList) -> {
//	        	Undertow server = svrPortMap.get(port);
//	        	
//	        	if (server == null) {
//	        		ConcurrentHashMap<String, HttpDynamicInAdapterUri> uriMap = uriList;
//	        		ServletInfo servletInfo = Servlets.servlet(com.eactive.eai.adapter.http.dynamic.HttpAdapterCustom.class);
//	        		uriMap.forEach((url,adapterUri) -> {
//	        			//servletInfo.addMapping(adapterUri.getUri());
//	        			servletInfo.addMapping(adapterUri.getUri() + "/*");
//	        		});
//	        		
//	        		DeploymentInfo servletBuilder = Servlets
//							.deployment()
//							.setClassLoader(HttpDynamicPortInAdapterManager.class.getClassLoader())
//							.setDeploymentName("HttpDynamicCustomAdapter")
//							.setContextPath("/")
//							.addServlet(servletInfo);
//					DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
//					manager.deploy();
//					
//					PathHandler path = null;
//					
//					try {
//						path = Handlers.path(Handlers.redirect("/")).addPrefixPath("/", manager.start());
//					} catch (ServletException e) {
//						logger.error(e.getMessage(), e);
//					}
//					
//					server = Undertow.builder().addHttpListener(port, "0.0.0.0").setHandler(path).build();
//					
//					svrPortMap.put(port, server);
//					server.start();
//	        		
//	        	}
//	        });
		} catch (Exception e) {
			logger.error("HttpDynamicPortInAdapterManager] start error", e);
		}
	}

	@Override
	public void stop() throws LifecycleException {

		try {
			adptPortMap.forEach((port, UriList) -> {
				Undertow server = svrPortMap.get(port);

				if (server != null) {
					server.stop();
				}
			});
		} catch (Exception e) {
			logger.error("HttpDynamicPortInAdapterManager] stop error", e);
		}
	}

	@Override
	public boolean isStarted() {
		return false;
	}
}
