package com.eactive.eai.rms.ext.meta.epb;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.eactive.eai.common.util.DatetimeUtil;
import com.eactive.eai.common.util.HttpUtil;
import com.eactive.eai.rms.common.base.BaseService;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceType;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * 2021. 11. 11
 * @author sunwook
 */
@Service("epbMetaSyncService")
public class EpbMetaService extends BaseService {
	
	@Autowired
	private MonitoringContext monitoringContext;
	
	@Autowired
	private EpbMetaApplSyncDao metaApplSyncDao;
	
	@Autowired
	private EpbMetaInstSyncDao metaInstSyncDao;
	
	//@Autowired
	//private EpbMetaExtBapSyncDao metaExtBapSyncDao;
	
	private HashMap<String, String> newParamMap(String serviceType, String inputDstCd) {
    	HashMap result = new HashMap();
    	result.put("serviceType", serviceType);
    	result.put("inputDstCd", inputDstCd);
		return result;
    }
    
    public void sync() throws Exception {
    	process(new EpbTaskInfo(EpbMetaVo.DOM_ID_APP));
    	process(new EpbTaskInfo(EpbMetaVo.DOM_ID_EXT));
    	process(new EpbTaskInfo(EpbMetaVo.DOM_ID_SYS));
    	process(new EpbTaskInfo(EpbMetaVo.DOM_ID_INST));
    	//process(new EpbTaskInfo(EpbMetaVo.DOM_ID_EXTBAP));
    }
    
    public void process(EpbTaskInfo taskInfo) throws Exception {
    	for(DataSourceType dataSourceType : taskInfo.getDatasourceList()) {
    		transactionalSync(taskInfo, dataSourceType);
    	}
    }
    
	private void transactionalSync(EpbTaskInfo taskInfo, DataSourceType dataSourceType) throws Exception {
		
		EpbMetaSyncDao metaSyncDao = taskInfo.getDao();
    	DataSourceContextHolder.setDataSourceType(dataSourceType);
    	
    	/** Get LastUpdateDate by (schema + inputDstcd) **/
    	Timestamp lastTimeStamp = metaSyncDao.selectLastUpdateDate(newParamMap(dataSourceType.getName(), taskInfo.getInputDstcd()));
    	if(lastTimeStamp == null) { lastTimeStamp = new Timestamp(DatetimeUtil.getLongTimeFormat("yyyyMMddHHmmss", "20000101010101")); } // 2000-01-01 01:01:01 dummyDate
    	String appLastUpdate = DatetimeUtil.getFormattedDate("yyyyMMddHHmmss", lastTimeStamp);
    	
    	/** HTTP REST TO EIMS **/
    	String url = monitoringContext.getStringProperty("eims.address") + "api/epb/v1/smeta/codeInfoList.json?domId=" + taskInfo.getDomId() + "&date=" + appLastUpdate;
    	String metaCodeListJsonString = HttpUtil.httpCall(url, "GET", "UTF-8");
    	logger.debug(url + " response : " + metaCodeListJsonString);
    	
    	if(metaCodeListJsonString == null) {
    		return;
    	}
    	
    	/** JSONArray -> EpbMetoVoList **/
    	ObjectMapper jacksonMapper = new ObjectMapper();
    	jacksonMapper.setSerializationInclusion(Include.NON_NULL);
    	List<EpbMetaVo> metaCodeList = jacksonMapper.readValue(metaCodeListJsonString, new TypeReference<List<EpbMetaVo>>(){});
    	List<EpbMetaVo> applCodeList = metaSyncDao.selectCdList(newParamMap(dataSourceType.getName(), taskInfo.getInputDstcd()));
    	
    	metaCodeList.forEach(metaVo -> subStrCodeDesc(metaVo));
    	
    	/** compare (meta - application) **/
    	List<EpbMetaVo> whatNewAdded = filterNewAddedList(metaCodeList, applCodeList);
    	List<EpbMetaVo> whatUpdated = filterUpdatedList(metaCodeList, applCodeList);
    	
    	if(whatNewAdded.size() == 0 && whatUpdated.size() == 0) {
    		logger.debug("No New Added And Updated Code");
    		return;
    	}
	
    	/** insert new added **/
    	for(EpbMetaVo epbMetaVo : whatNewAdded) {
    		logger.debug("New Added : " + epbMetaVo.toString());
    		
    		epbMetaVo.setSchemaId(DataSourceContextHolder.getDataSourceType().getSchema());
    		epbMetaVo.setInputDstcd(taskInfo.getInputDstcd());
    		epbMetaVo.setEaiBzwkDstcd(taskInfo.getPrefix() + epbMetaVo.getCodeValidValue());
    		metaSyncDao.insertCodeInfo(epbMetaVo); // insert CodeInfo
    	}
    	
    	/** update updated **/
    	for(EpbMetaVo epbMetaVo : whatUpdated) {
    		logger.debug("Updated : " + epbMetaVo.toString());
    		
    		epbMetaVo.setSchemaId(DataSourceContextHolder.getDataSourceType().getSchema());
    		epbMetaVo.setInputDstcd(taskInfo.getInputDstcd());
    		epbMetaVo.setEaiBzwkDstcd(taskInfo.getPrefix() + epbMetaVo.getCodeValidValue());
    		metaSyncDao.updateCodeInfo(epbMetaVo); // update CodeInfo
    	}
    	
    	metaSyncDao.mergeAuthToAdmin();
		
	}
	
	private void subStrCodeDesc(EpbMetaVo metaMetaVo) {
    	
    	try {
    		int length = 200;
        	String codeDesc = metaMetaVo.getCodeDesc();
        	byte[] bytes = codeDesc.getBytes("UTF-8");
        	
        	if(bytes.length > length && bytes[bytes.length-1] < 0) {
        		
        		byte[] destination = new byte[length];
        		System.arraycopy(bytes, 0, destination, 0, length);
            	int lastPositive = 0;
        		for(int i = length-1; i > 0; i--) {
        			if(destination[i] > 0) {
        				lastPositive = i;
        				break;
        			}
        		}
        		int lastIdx = lastPositive + ((length-lastPositive)/3)*3;
        		byte[] result = new byte[lastIdx+1];
        		System.arraycopy(destination, 0, result, 0, lastIdx+1);
        		metaMetaVo.setCodeDesc(new String(result, "UTF-8"));
        	}
    	} catch (Exception e) {
    		logger.error(e.getMessage());
    	}
	}

	/**
     * applCodeList에는 존재하지만, metaCodeList에는 존재하지 않는 MetaVo의 List 반환
     */
    private List<EpbMetaVo> filterDeletedList(List<EpbMetaVo> metaCodeList, List<EpbMetaVo> applCodeList) {
    	return applCodeList.stream()
    			.filter(
    			appMetaVo -> metaCodeList.stream().map(metaMetaVo -> metaMetaVo.getCodeValidValue()).noneMatch(Predicate.isEqual(appMetaVo.getCodeValidValue()))
    			).collect(Collectors.toList());
    }
    
    /**
     * metaCodeList에는 존재하지만, applCodeList에는 존재하지 않는 MetaVo의 List 반환
     */
    private List<EpbMetaVo> filterNewAddedList(List<EpbMetaVo> metaCodeList, List<EpbMetaVo> applCodeList) {
    	
    	if(applCodeList == null) {
    		return metaCodeList;
    	}
    	
    	return metaCodeList.stream()
    			.filter(
    			metaMetaVo -> applCodeList.stream().map(appMetaVo -> appMetaVo.getCodeValidValue()).noneMatch(Predicate.isEqual(metaMetaVo.getCodeValidValue()))
    			).collect(Collectors.toList());
    }
    
    /**
     * metaCodeList에 존재하고 applCodeList에 존재하면서, 그 내용이 서로 일치하지 않는 MetaVo의 List 반환
     */
    private List<EpbMetaVo> filterUpdatedList(List<EpbMetaVo> metaCodeList, List<EpbMetaVo> applCodeList) {
    	
    	/** TSAEIRM28은 Desc없음 **/
    	boolean noDesc = applCodeList.stream().map(epbMetaVo -> Optional.ofNullable(epbMetaVo.getCodeDesc())).noneMatch(optDesc-> optDesc.isPresent());
    	if(noDesc) {
    		return filterUpdatedListNoDesc(metaCodeList, applCodeList);
    	}
    	
    	return metaCodeList.stream()
    			.filter(
				metaMetaVo -> applCodeList.stream().map(appMetaVo -> appMetaVo.getCodeValidValue()).anyMatch(Predicate.isEqual(metaMetaVo.getCodeValidValue()))
				).filter(
				metaMetaVo -> applCodeList.stream().noneMatch(appMetaVo -> appMetaVo.equals(metaMetaVo))
				).collect(Collectors.toList());
    }
    
    /**
     * filterUpdatedList와 기능 동일, codeDesc field는 체크안함
     */
    private List<EpbMetaVo> filterUpdatedListNoDesc(List<EpbMetaVo> metaCodeList, List<EpbMetaVo> applCodeList) {
		
		return metaCodeList.stream()
    			.filter(
				metaMetaVo -> applCodeList.stream().map(appMetaVo -> appMetaVo.getCodeValidValue()).anyMatch(Predicate.isEqual(metaMetaVo.getCodeValidValue()))
				).filter(
				metaMetaVo -> applCodeList.stream().noneMatch(appMetaVo -> appMetaVo.equalsNoDesc(metaMetaVo))
				).collect(Collectors.toList());
	}

    /**
     * 2021. 12. 03.
     * domId별로 process구분 
     * @author sunwook
     */
	private class EpbTaskInfo {
    	
    	private String domId;
    	private String inputDstcd;
    	private String prefix;
    	private List<DataSourceType> datasourceList;
    	private EpbMetaSyncDao dao;
    	
    	private EpbTaskInfo(String domId) throws Exception {
    		this.domId = domId;
    		switch(domId) {
    			case EpbMetaVo.DOM_ID_APP :
    				this.inputDstcd = EpbMetaVo.INPUTDSTCD_APP;
    				this.prefix 	= EpbMetaVo.PREFIX_APP;
    				this.datasourceList = DataSourceTypeManager.getDynamicDataSourceTypes();
    				this.dao = metaApplSyncDao;
    				break;
    			case EpbMetaVo.DOM_ID_EXT :
    				this.inputDstcd = EpbMetaVo.INPUTDSTCD_EXT;
    				this.prefix 	= EpbMetaVo.PREFIX_EXT;
    				this.datasourceList = DataSourceTypeManager.getDynamicDataSourceTypes();
    				this.dao = metaApplSyncDao;
    				break;
    			case EpbMetaVo.DOM_ID_SYS :
    				this.inputDstcd = EpbMetaVo.INPUTDSTCD_SYS;
    				this.prefix 	= EpbMetaVo.PREFIX_SYS;
    				this.datasourceList = DataSourceTypeManager.getDynamicDataSourceTypes();
    				this.dao = metaApplSyncDao;
    				break;
    			case EpbMetaVo.DOM_ID_INST :
    				this.inputDstcd = EpbMetaVo.INPUTDSTCD_INST;
    				this.datasourceList = new ArrayList<>();
    				this.datasourceList.add(DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));
    				this.dao = metaInstSyncDao;
    				break;
    			case EpbMetaVo.DOM_ID_EXTBAP :
    				this.datasourceList = new ArrayList<>();
    				this.datasourceList.add(DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.BAP));
    				//this.dao = metaExtBapSyncDao;
    				break;
    			default : 
    				throw new Exception("지원하지 않는 DomId입니다.");
    		}
    	}
    	
    	public String getDomId() { return domId; }
    	public String getInputDstcd() {	return inputDstcd; }
    	public String getPrefix() {	return prefix; }
		public List<DataSourceType> getDatasourceList() { return datasourceList; }
		public EpbMetaSyncDao getDao() { return dao; }

    }
    
}
