package com.eactive.eai.rms.onl.common.service;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.eactive.eai.agent.command.Command;
import com.eactive.eai.data.entity.onl.server.EAIServer;
import com.eactive.eai.rms.data.entity.onl.property.PropertyService;
import com.eactive.eai.rms.data.entity.onl.server.EAIServerService;

/** 
* 1. 기능 : 등록된 EAI Server 에 Command를 broadcast
* 2. 처리 개요 : 등록된 EAI Server 에 Command를 broadcast
* 3. 주의사항 
*
* @see  관련 기능을 참조
* @since  
*  
*/

@Service("onlAgentUtilService")
public class OnlAgentUtilServiceImpl implements AgentUtilService {
    private static final Logger logger = Logger.getLogger(OnlAgentUtilServiceImpl.class);
    final static String SERVLET_URL = "/WebAgent";
    
    public static final String FIELD_URL = "URL";
    public static final String FIELD_INST_NAME = "INST_NAME";
    public static final String FIELD_INTERNAL_URL = "INTERNAL_URL";
    public static final String FIELD_IS_CLOUD_PROXY = "IS_CLOUD_PROXY";
    
    private static int DEFAULT_CONNECT_TIME_OUT = 3000; // 0 값을 입력하면 디폴트 값이 쓰이는 듯.
    
	private EAIServerService eaiServerService;

	@Autowired
	public OnlAgentUtilServiceImpl(EAIServerService eaiServerService) {
		this.eaiServerService = eaiServerService;
	}

    @Autowired 
    @Qualifier("propertyService")
    private PropertyService propertyService;
    
    /** 
    * 1. 기능 :  등록된 EAI Server 의 URL을 생성하여 반환
    * 2. 처리 개요 : EAIServerDAO를 통해 데이터베이스에 등록된 EAI Server의 URL을 생성하여 반환
    * 3. 주의사항 
    *
    * @param 
    * @return   HashMap 등록된 EAI Server 의 URL.
    * @exception    Exception
    **/
    private List<Map<String, String>> getAllSvrUrl() throws Exception {
        
        // K8S 로직 비활성화 - propertyService.getPrpty2valById 호출로 인한 오류 방지
		List<EAIServer> list = eaiServerService.selectEaiServerIpList();
		List<Map<String, String>> urlLists = new ArrayList<>();

		String svrName = "";
		String svrIp = "";
		String svrPort = "";
		String cloudrouterurl = "";
		for (int i = 0; i < list.size(); i++) {
			Map<String,String> infoMap = new HashMap<>();
			EAIServer map = list.get(i);
			svrName = map.getEaisevrinstncname();
			svrIp = map.getEaisevrip();
			svrPort = map.getSevrlsnportname();
			cloudrouterurl = map.getCloudrouterurl();
			if (!("ALL".equalsIgnoreCase(svrName))) {
				StringBuffer urlBuf = new StringBuffer();
				urlBuf.append("http://");
				if(StringUtils.isNotBlank(cloudrouterurl)) {
				   urlBuf.append(cloudrouterurl);
					infoMap.put(FIELD_IS_CLOUD_PROXY, "true");
					infoMap.put(FIELD_INTERNAL_URL, "http://" + svrIp + ":" + svrPort + SERVLET_URL);
				}else {
				   urlBuf.append(svrIp + ":" + svrPort);
				}
				urlBuf.append(SERVLET_URL);

				infoMap.put(FIELD_URL, urlBuf.toString());
				infoMap.put(FIELD_INST_NAME, svrName);
				urlLists.add(infoMap);
			}
		}
		return urlLists;
     }
    
    /** 
    * 1. 기능 :  등록된 EAI Server에 Command를 broadcast
    * 2. 처리 개요 : 등록된 EAI Server의 URLConnection을 생성하여 Command broadcast하고
    *               전송된 결과값의 HashMap을 생성하여 반환 
    * 3. 주의사항 
    *
    * @param    svrName     broadcast할 EAI Server명
    * @param    url         broadcast할 EAI Server의 url
    * @param    command     broadcast할 Command
    * @return   HashMap     broadcast후 전송된 결과값. 
    *                       전송된 데이터가 sucess가 아니면 Exception이 전송된 것임.
    * @exception    Exception
    **/
    public HashMap broadcast(String svrName, String url, Command command)
            throws Exception {
        URLConnection urlCon;
        String response = "";

        HashMap returnValue = new HashMap();

        if (url != null) {
            urlCon = getURLConnection(url);
            try {
            	if(urlCon != null){  //취약점소스 수정 2020.08.25
            		response = sendCommand(urlCon, command);
            	}
                returnValue.put(svrName, response);
            } catch (Exception e) {
                logger.error("AgentUtil] broadcast command : "+command.getName() + " broadcast Excpetion - " + urlCon.getURL() + e.getMessage());
                returnValue.put(svrName, e.getMessage());
            }

        } else {
            logger.error("AgentUtil] broadcast Error. - url is null");
        }
        return returnValue;
    }

    /** 
    * 1. 기능 :  등록된 EAI Server에 Command를 broadcast
    * 2. 처리 개요 : 등록된 EAI Server의 URLConnection을 생성하여 Command broadcast하고
    *               전송된 결과값의 HashMap을 생성하여 반환 
    * 3. 주의사항 
    *
    * @param    command     broadcast할 Command
    * @return   HashMap     broadcast후 전송된 결과값. 
    *                       전송된 데이터가 sucess가 아니면 Exception이 전송된 것임.
    * @exception    Exception
    **/
    public HashMap broadcast(Command command) throws Exception {
    	List<Map<String, String>> servers = getAllSvrUrl();
    	return broadcastMultiCall(command, servers);
    }
    
    private HashMap broadcastMultiCall(Command command, List<Map<String, String>> servers) throws Exception{
    	Map<String,String> conMap = new ConcurrentHashMap<String,String>();
    	HashMap<String,String> returnValue = new HashMap<String,String>();
    	if (servers != null) {
            try {
    			ExecutorService executor = new ThreadPoolExecutor(32, 32, 0, TimeUnit.SECONDS, new LinkedBlockingQueue());
    	        final CountDownLatch latch  = new CountDownLatch(servers.size());
    	
    	        for (final Map<String, String> serverInfo : servers) {
    	        	String svrName = serverInfo.get(FIELD_INST_NAME);
    	        	final String url = serverInfo.get(FIELD_URL);
    	            
    	            executor.execute(new Runnable() {
    					public void run() {
    						String response = "";    	            
    		    			if( logger.isDebugEnabled() ) {
    		    				logger.debug("메모리동기화:cmd=" + command.getName() + " ,url=" + url);
    		    			}
    		    			try {                    
    		    				URLConnection urlCon = getURLConnection(url);
    		    				String isCloud = serverInfo.get(FIELD_IS_CLOUD_PROXY);
    		    				if("true".equals(isCloud)) {
    		    					urlCon.setRequestProperty(FIELD_INST_NAME, svrName);
    		    					urlCon.setRequestProperty(FIELD_IS_CLOUD_PROXY, isCloud);
    		    					urlCon.setRequestProperty(FIELD_INTERNAL_URL, serverInfo.get(FIELD_INTERNAL_URL));
    		    				}
    		    				
    		    				response = sendCommand(urlCon, command);
    		    				conMap.put(svrName, response);
    		    			} catch (Exception e) {
    		    				logger.error("AgentUtil] broadcast command : " + command.getName() + " Excpetion - " + url + e.getMessage());
    		    				conMap.put(svrName, e.getMessage());
    			        	} finally {
    			        		latch.countDown();
    			        	}
    					}
    				});
    	        }
	        	latch.await( 180L ,TimeUnit.SECONDS);
    			executor.shutdown();
    		} catch (InterruptedException e) {
    			logger.error("AgentUtil] broadcast Error. - latch InterruptedException",e);
    		}
    	} else {
    		logger.error("AgentUtil] broadcast Error. - url is null");
    		
    	}
    	for(String key : conMap.keySet() ){
    		returnValue.put(key, conMap.get(key));
    	}
    	return returnValue;
    }
    /** 
    * 1. 기능 :  해당 URLConnection으로 command 전송 처리 결과를 반환
    * 2. 처리 개요 : 해당 URLConnection으로 command 전송하고 처리 결과를 반환
    * 3. 주의사항 
    *
    * @param    urlCon  연결할 URLConnection
    * @param    command 전송할 Command
    * @return   String 처리된 결과 
    * @exception    Exception
    **/
    private String sendCommand(URLConnection urlCon, Command command)
            throws Exception {
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        String response = "";

        // 2009.05.22 서버가 다운되어 있는 경우 응답시간이 너무 길어서 timeout 값을 지정함.
        // 2009.07.09 타임아웃 값을 3000 -> 100 으로 조정함.
        urlCon.setConnectTimeout(DEFAULT_CONNECT_TIME_OUT);
        
        try {
            oos = new ObjectOutputStream(urlCon.getOutputStream());
            oos.writeObject(command);
            oos.flush();
            if( logger.isDebugEnabled() ) {
                logger.debug("AgentUtil] SendCommand - " + urlCon.getURL().toString());                
            }

            ois = new ObjectInputStream(urlCon.getInputStream());
            response = (String) ois.readObject();

        } catch (ConnectException e) {
            logger.error("AgentUtil] sendCommand "+ command.getName()+ " ConnectException. - " + urlCon.getURL() + " can't Connect" + e.getMessage());
            throw e;
        } catch (IOException e2) {
            logger.error("AgentUtil] sendCommand "+ command.getName()+ " IOException. - " + urlCon.getURL() + " error=" + e2.getMessage());
            throw e2;
        } catch (Exception e) {
            logger.error("AgentUtil] sendCommand "+ command.getName()+ " Exception. - " + urlCon.getURL() + e.getMessage());
            throw e;
        } finally {
            try {
            	//취약점코드 수정 2019.08.22 SW
                if (oos != null) {
                    oos.close();
                }
               
                if(ois != null){
                    ois.close();
                }
            } catch (IOException e) {
                oos = null;
                logger.error("AgentUtil] sendCommand Error. - " + e.getMessage());
            }
        }

        return response;
    }

    /** 
    * 1. 기능 :  지정된  url의 URLConnection 생성
    * 2. 처리 개요 : 지정된  url의 URLConnection 생성
    * 3. 주의사항 
    *
    * @param    url     URLConnection을 생성을 위한 url
    * @return   URLConnection 생성된 URLConnection
    * @exception
    **/
    private URLConnection getURLConnection(String url) {
        URL u = null;
        try {
            u = new URL(url);
        } catch (MalformedURLException e) {
            logger.error( e );
        }

        HttpURLConnection conn = null;

        try {
            conn = (HttpURLConnection) u.openConnection();            
        } catch (ConnectException ce) {
            logger.error( ce );
        } catch (IOException ie) {
            logger.error( ie );
        } catch (Exception e) {
            logger.error( e );
        }

        if (conn != null) {
            conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.setUseCaches(false);
        } else {
            logger.error("AgentUtil] getURLConnection Error. - " + url + " 의 HttpURLConnection 을 생성할 수 없습니다.");
        }

        try {
            if (conn != null) {
                conn.setRequestMethod("POST");
            }
        } catch (ProtocolException pe) {
            logger.error("AgentUtil] getURLConnection ProtocolException. - " + pe.getMessage());
        }
        if (conn != null) {
            conn.setRequestProperty("Content-type", "application/octet-stream");
        }
        return conn;
    }

	@Override
	public HashMap<String, Object> broadcast(Command command, String instanceName) throws Exception {
		URLConnection urlCon;
		List<Map<String, String>> servers = getAllSvrUrl();

		HashMap returnValue = new HashMap();

		if (servers != null) {
			Iterator it = servers.iterator();
			String svrName = "";
			String url = "";
			String response = "";

			while (it.hasNext()) {
				Map<String, String> serverInfo = (Map<String, String>) it.next();
				svrName = serverInfo.get(FIELD_INST_NAME);
				url = serverInfo.get(FIELD_URL);
				if(!svrName.equals(instanceName)) continue; // 같은 이름일 경우만 처리

				urlCon = getURLConnection(url);
				String isCloud = serverInfo.get(FIELD_IS_CLOUD_PROXY);
				if("true".equals(isCloud)) {
					urlCon.setRequestProperty(FIELD_IS_CLOUD_PROXY, isCloud);
					urlCon.setRequestProperty(FIELD_INST_NAME, svrName);
					urlCon.setRequestProperty(FIELD_INTERNAL_URL, serverInfo.get(FIELD_INTERNAL_URL));
				}

				if (logger.isDebugEnabled()) {
					logger.debug("메모리동기화:cmd=" + command.getName() + " ,url="	+ url);
				}

				try {
					 if(urlCon != null) {  //취약점소스 수정 2020.08.25
						 response = sendCommand(urlCon, command);
					 }
					returnValue.put(svrName, response);
				} catch (Exception e) {
					logger.error("AgentUtil] broadcast command : "+ command.getName()+ " Excpetion - " + url + e.getMessage());
					returnValue.put(svrName, e.getMessage());
				}
			}
		} else {
			logger.error("AgentUtil] broadcast Error. - url is null");

		}
		return returnValue;
	}

}