/*
 * Copyright (c) 2021 LG Electronics Inc.
 * SPDX-License-Identifier: AGPL-3.0-only 
 */

package oss.fosslight.service.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import oss.fosslight.CoTopComponent;
import oss.fosslight.common.CoCodeManager;
import oss.fosslight.common.CoConstDef;
import oss.fosslight.common.CommonFunction;
import oss.fosslight.domain.CoMail;
import oss.fosslight.domain.CoMailManager;
import oss.fosslight.domain.CommentsHistory;
import oss.fosslight.domain.OssMaster;
import oss.fosslight.domain.T2CodeDtl;
import oss.fosslight.domain.Vulnerability;
import oss.fosslight.repository.CodeMapper;
import oss.fosslight.repository.OssMapper;
import oss.fosslight.repository.VulnerabilityMapper;
import oss.fosslight.service.CommentService;
import oss.fosslight.service.OssService;
import oss.fosslight.service.VulnerabilityService;

@Service
@Slf4j
public class VulnerabilityServiceImpl extends CoTopComponent implements VulnerabilityService {
	private static final Logger scheduler_log = LoggerFactory.getLogger("SCHEDULER_LOG");

	@Autowired Environment env;
	@Autowired VulnerabilityMapper vulnerabilityMapper;
	@Autowired OssService ossService;
	@Autowired OssMapper ossMapper;
	@Autowired CodeMapper codeMapper;
	@Autowired CommentService commentService;
	
	@Override
	public HashMap<String, Object> getVulnerabilityList(Vulnerability vulnerability, boolean exportFlag) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		List<String> convertOssNicknameList = new ArrayList<>();
		List<String> vendorProductCheckList = new ArrayList<>();
		
		// Table alias 지정이 필요한 칼럼
		Map<String, String> ambiguousMap = new HashMap<>();
		ambiguousMap.put("CVE_ID", "T1");
		String filterCondition = CommonFunction.getFilterToString(vulnerability.getFilters(), ambiguousMap);
		if (!isEmpty(filterCondition)) {
			vulnerability.setFilterCondition(filterCondition);
		}
		
		if (isEmpty(vulnerability.getOssNameAllSearchFlag())) {
			vulnerability.setOssNameAllSearchFlag(CoConstDef.FLAG_NO);
		}
		
		if (CoConstDef.FLAG_YES.equals(vulnerability.getOssNameAllSearchFlag()) && "PRODUCT".equals(vulnerability.getSidx().toUpperCase())) {
			vulnerability.setSidx("version");
			vulnerability.setSord("desc");
		}
		
		if (vulnerability.getProduct() != null) {
			String searchProduct = vulnerability.getProduct();
			if (searchProduct.contains(" ") || searchProduct.contains("_")) {
				if (searchProduct.contains("_")) {
					vulnerability.setSpecialCharactersProduct(vulnerability.getProduct().replaceAll("_", " "));
				} else {
					vulnerability.setSpecialCharactersProduct(vulnerability.getProduct().replaceAll(" ", "_"));
				}
			}
			
			if (searchProduct.contains("-")) {
				vendorProductCheckList.add(searchProduct);
			}
		}
		
		String[] nicknameList = ossService.getOssNickNameListByOssName(vulnerability.getProduct());
		if (nicknameList != null) {
			for (String nickname : nicknameList) {
				if (nickname.contains(" ")) {
					convertOssNicknameList.add(nickname.replaceAll(" ", "_"));
				} else if (nickname.contains("_")) {
					convertOssNicknameList.add(nickname.replaceAll("_", " "));
				}
				
				if (nickname.contains("-")) {
					vendorProductCheckList.add(nickname);
				}
			}
			
			if (convertOssNicknameList.size() > 0) {
				convertOssNicknameList.addAll(Arrays.asList(nicknameList));
				convertOssNicknameList = convertOssNicknameList.stream().distinct().collect(Collectors.toList());
				vulnerability.setOssNicknames(convertOssNicknameList.toArray(new String[convertOssNicknameList.size()]));
			} else {
				vulnerability.setOssNicknames(nicknameList);
			}
		} else {
			if (convertOssNicknameList.size() > 0) {
				convertOssNicknameList = convertOssNicknameList.stream().distinct().collect(Collectors.toList());
				vulnerability.setOssNicknames(convertOssNicknameList.toArray(new String[convertOssNicknameList.size()]));
			} else {
				vulnerability.setOssNicknames(nicknameList);
			}
		}
		
		if (!vendorProductCheckList.isEmpty()) {
			vendorProductCheckList = vendorProductCheckList.stream().distinct().collect(Collectors.toList());
			vulnerability.setOssNicknameArr(vendorProductCheckList.toArray(new String[vendorProductCheckList.size()]));
		}
		
		int records = vulnerabilityMapper.selectVulnerabilityTotalCount(vulnerability);
		vulnerability.setTotListSize(records);

		List<Vulnerability> list = null;
		if (exportFlag) {
			list = vulnerabilityMapper.selectVulnerabilityExportList(vulnerability);
		}else {
			list = vulnerabilityMapper.selectVulnerabilityList(vulnerability);
			List<String> replaceVendor = new ArrayList<>();
			for (Vulnerability vuln : list) {
				String vendor = vuln.getVendor();
				for (String vendorNm : vendor.split(",")) {
					if (!isEmpty(vendorNm)) {
						replaceVendor.add(vendorNm);
					}
				}
				vendor = "";
				if (replaceVendor.size() > 0) {
					for (String reNm : replaceVendor) {
						vendor += reNm + ",";
					}
					vuln.setVendor(vendor.substring(0, vendor.length()-1));
				} else {
					vuln.setVendor("");
				}
				replaceVendor.clear();
			}
		}

		map.put("page", vulnerability.getCurPage());
		map.put("total", vulnerability.getTotBlockSize());
		map.put("records", records);
		map.put("rows", list);
		return map;
	}
	
	@Override
	@Cacheable(value="autocompleteVulnerabilityCache", key="{#root.methodName}")
	public HashMap<String, Object> getVulnerabilityAutoComplete() {
		
		HashMap<String, Object> map = new HashMap<String, Object>();
		
		List<String> nameList = vulnerabilityMapper.selectVulnerabilityOSSNameAutoComplete();
		List<String> cveIdList = vulnerabilityMapper.selectVulnerabilityCveIdAutoComplete();
		
		map.put("nameList", nameList);
		map.put("cveIdList", cveIdList);

		return map;
	}

	@Override
	public List<String> getVulnerabilityVersionAutoComplete(String name) {

		List<String> versionList = vulnerabilityMapper.selectVulnerabilityOSSVersionAutoComplete(name);
		
		return versionList;
	}
	
	@Override
	public Map<String, Object> getVulnListByOssName(OssMaster bean) {
		Map<String, Object> result = new HashMap<String, Object>();
		boolean underscoreFlag = false;
		
		if ("N/A".equals(bean.getOssVersion())) {
			bean.setOssVersion("");
		}
		
		String key = (bean.getOssName() + "_" + avoidNull(bean.getOssVersion())).toUpperCase();
		String ossName = bean.getOssName();
		String refOssName = "";
		if (CoCodeManager.OSS_INFO_UPPER.containsKey(key)) {
			OssMaster ossInfo = CoCodeManager.OSS_INFO_UPPER.get(key);
			if (!ossInfo.getOssName().equals(ossInfo.getOssNameTemp())) {
				refOssName = ossInfo.getOssNameTemp();
			}
		} else {
			if (ossName.contains("_")) {
				underscoreFlag = true;
				String chkKey = (bean.getOssName().replaceAll("_", " ") + "_" + avoidNull(bean.getOssVersion())).toUpperCase();
				if (CoCodeManager.OSS_INFO_UPPER.containsKey(chkKey)) {
					OssMaster ossInfo = CoCodeManager.OSS_INFO_UPPER.get(chkKey);
					if (!ossInfo.getOssName().equals(ossInfo.getOssNameTemp())) {
						refOssName = ossInfo.getOssNameTemp();
					} else {
						ossName = bean.getOssName().replaceAll("_", " ");
					}
				}
			}
		}
		
		List<Vulnerability> list = null;
		String[] nicknameList = null;
		int records = 0;
		
		try {
			nicknameList = ossService.getOssNickNameListByOssName(isEmpty(refOssName) ? ossName : refOssName);
			if (underscoreFlag) {
				if (nicknameList != null) {
					List<String> nicknameList2 = new ArrayList<>();
					nicknameList2.addAll(Arrays.asList(nicknameList));
					nicknameList2.add(bean.getOssName());
					nicknameList = nicknameList2.toArray(new String[nicknameList2.size()]);
				} else {
					nicknameList = new String[] {bean.getOssName()};
				}
			}
			bean.setSchOssName(ossName);
			bean.setOssName(!isEmpty(refOssName) ? refOssName : ossName);
			bean.setOssNicknames(nicknameList);
			records = vulnerabilityMapper.getVulnListByOssNameCnt(bean);
			bean.setTotListSize(records);
			list = vulnerabilityMapper.getVulnListByOssName(bean);
		} catch (Exception e) {
			log.error(e.getMessage());
		}
		
		list = checkVulnData(list, nicknameList);
		if (bean.getVulnerabilityCheckFlag() != null) list = sortDataForVulnerabilityResolution(list);
		
		result.put("page", bean.getCurPage());
		result.put("total", bean.getTotBlockSize());
		result.put("records", records); // total cnt를 cveCnt에 담아두고 관리함.
		result.put("rows", list);
		
		return result;
	}
	
	private List<Vulnerability> sortDataForVulnerabilityResolution(List<Vulnerability> list) {
		List<Vulnerability> vrFixedDataList = new ArrayList<>();
		List<Vulnerability> vrNotFixedDataList = new ArrayList<>();
		
		for (Vulnerability vuln : list) {
			if (vuln.getVulnerabilityResolution() != null && vuln.getVulnerabilityResolution().equals("Fixed")) {
				vrFixedDataList.add(vuln);
			} else {
				vrNotFixedDataList.add(vuln);
			}
		}
		
		if (vrFixedDataList.isEmpty()) {
			return list;
		} else {
			List<Vulnerability> sortedDataList = new ArrayList<>();
			sortedDataList.addAll(vrNotFixedDataList);
			sortedDataList.addAll(vrFixedDataList);
			return sortedDataList;
		}
	}

	public List<Vulnerability> checkVulnData(List<Vulnerability> list, String[] nicknameList){
		List<Vulnerability> result = new ArrayList<Vulnerability>();
				
		for (Vulnerability bean : list) {
			bean.setOssNameAllSearchFlag(CoConstDef.FLAG_YES);
			if (nicknameList != null) {
				bean.setOssNicknames(nicknameList);
			}
			int vulnCnt = vulnerabilityMapper.checkVulnDataCnt(bean);
			if (vulnCnt > 0) {
				result.add(bean);
			}
		}
		
		return result;
	}
	

	public void doSyncOSSNvdInfo() {

		vulnerabilityMapper.updateOssRecheckVulnFlag();

		List<String> notUsedOssList = new ArrayList<>();
		Map<String, OssMaster> reCalcOssInfoMap = new HashMap<>();
		Map<String, Map<String, OssMaster>> reCalcCommentsOssInfoMap = new HashMap<>();
		Map<String, List<Map<String, Object>>> discoveredCommentsOssInfoMap = new HashMap<>();
		List<String> _filteredNvdData = new ArrayList<>();
		List<String> _prjMailList = new ArrayList<>();
		List<String> _reCalcPrjMailList = new ArrayList<>();
		List<String> _removeReCalcPrjMailList = new ArrayList<>();
		Map<String, OssMaster> ossInfoMap = CoCodeManager.OSS_INFO_BY_ID;
		Map<String, String> ossInfoNamesMap = CoCodeManager.OSS_INFO_UPPER_NAMES;
		Map<String, OssMaster> _notMatchedVendorProduct = new HashMap<>();
		List<String> checkedOssList = new ArrayList<>();
		List<Map<String, Object>> _nvdInfoDiffVendorList = new ArrayList<>();
		boolean discoveredMailingFlag = false;
		boolean hasNvdData = vulnerabilityMapper.selectNvdTotalCount() > 0;
		T2CodeDtl codeDetail = codeMapper.getCodeDetail(CoConstDef.CD_VULNERABILITY_MAILING_SCORE, CoConstDef.CD_VULNERABILITY_MAILING_SCORE_STANDARD);
		String standardScore = codeDetail.getCdDtlExp();
		
		List<String> nvdInfoKeyList = new ArrayList<>();
		List<String> nvdInfoValueList = null;
		Map<String, List<String>> diffVendorList = new HashMap<>();
		
		scheduler_log.info("doSyncOSSNvdInfo standard cvss score : " + standardScore);
		
		// vendor-product matching
		for (String ossKey : ossInfoMap.keySet()) {
			OssMaster ossBean = ossInfoMap.get(ossKey);
			ossBean.setOssVersion(avoidNull(ossBean.getOssVersion()));
			ossBean.setStandardScore(Float.valueOf(standardScore));
			List<Map<String, Object>> vendorProductInfoList = null;
			
			if (!isEmpty(ossBean.getOssVersion())) {
				vendorProductInfoList = vulnerabilityMapper.selectVendorProductInfo(ossBean);
				if (vendorProductInfoList != null && !vendorProductInfoList.isEmpty()) {
					List<Map<String, Object>> filteredNvdDataInfoList = filteredNvdData(_filteredNvdData, vendorProductInfoList, true);
					if (filteredNvdDataInfoList != null && !filteredNvdDataInfoList.isEmpty()) {
						List<Map<String, Object>> rtnNvdInfoSndMailList = insertDataForDiscoveredMailing(ossBean, standardScore, true);
						Map<String, Object> map = filteredNvdDataInfoList.get(0);
						updateVulnAndMailing(ossBean, map, standardScore, _prjMailList, _reCalcPrjMailList, notUsedOssList, reCalcOssInfoMap, reCalcCommentsOssInfoMap, discoveredCommentsOssInfoMap, rtnNvdInfoSndMailList);
						String key = ((String) map.get("PRODUCT") + "_" + (String) map.get("VERSION") + "_" + (String) map.get("VENDOR") + "_" + (String) map.get("CVE_ID")).toUpperCase();
						if (!_filteredNvdData.contains(key)) {
							_filteredNvdData.add(key);
						}
						if (!diffVendorList.containsKey(ossBean.getOssName())) {
							customDiffVendor(ossBean, nvdInfoKeyList, nvdInfoValueList, diffVendorList, ossInfoNamesMap);
						}
					} else {
						_notMatchedVendorProduct.put(ossKey, ossBean);
					}
				} else {
					_notMatchedVendorProduct.put(ossKey, ossBean);
				}
			} else {
				vendorProductInfoList = vulnerabilityMapper.selectVendorProductInfoWithOurVer(ossBean);
				if (vendorProductInfoList != null && !vendorProductInfoList.isEmpty()) {
					List<Map<String, Object>> filteredNvdDataInfoList = filteredNvdData(_filteredNvdData, vendorProductInfoList, true);
					if (filteredNvdDataInfoList != null && !filteredNvdDataInfoList.isEmpty()) {
						List<Map<String, Object>> rtnNvdInfoSndMailList = insertDataForDiscoveredMailing(ossBean, standardScore, true);
						Map<String, Object> map = filteredNvdDataInfoList.get(0);
						updateVulnAndMailing(ossBean, map, standardScore, _prjMailList, _reCalcPrjMailList, notUsedOssList, reCalcOssInfoMap, reCalcCommentsOssInfoMap, discoveredCommentsOssInfoMap, rtnNvdInfoSndMailList);
						String key = ((String) map.get("PRODUCT") + "_" + (String) map.get("VERSION") + "_" + (String) map.get("VENDOR") + "_" + (String) map.get("CVE_ID")).toUpperCase();
						if (!_filteredNvdData.contains(key)) {
							_filteredNvdData.add(key);
						}
						if (!diffVendorList.containsKey(ossBean.getOssName())) {
							customDiffVendor(ossBean, nvdInfoKeyList, nvdInfoValueList, diffVendorList, ossInfoNamesMap);
						}
					} else {
						_notMatchedVendorProduct.put(ossKey, ossBean);
					}
				} else {
					_notMatchedVendorProduct.put(ossKey, ossBean);
				}
			}
		}
		
		if (_notMatchedVendorProduct != null && !_notMatchedVendorProduct.isEmpty()) {
			for (String ossKey : _notMatchedVendorProduct.keySet()) {
				OssMaster ossBean = _notMatchedVendorProduct.get(ossKey);
				if (checkedOssList.contains(ossBean.getOssId())) {
					continue;
				}
				
				checkedOssList.add(ossBean.getOssId());
				ossBean.setOssVersion(avoidNull(ossBean.getOssVersion()));
				ossBean.setStandardScore(Float.valueOf(standardScore));
				boolean reCheckNVDInfo = false;
				
				if (!isEmpty(ossBean.getOssVersion())) {
					List<Map<String, Object>> nvdInfoList = vulnerabilityMapper.selectNvdInfo(ossBean);
					if (nvdInfoList != null && !nvdInfoList.isEmpty()) {
						List<Map<String, Object>> filteredNvdDataInfoList = filteredNvdData(_filteredNvdData, nvdInfoList, false);
						if (filteredNvdDataInfoList != null && !filteredNvdDataInfoList.isEmpty()) {
							List<Map<String, Object>> rtnNvdInfoSndMailList = insertDataForDiscoveredMailing(ossBean, standardScore, false);
							Map<String, Object> map = filteredNvdDataInfoList.get(0);
							updateVulnAndMailing(ossBean, map, standardScore, _prjMailList, _reCalcPrjMailList, notUsedOssList, reCalcOssInfoMap, reCalcCommentsOssInfoMap, discoveredCommentsOssInfoMap, rtnNvdInfoSndMailList);
						} else {
							// NVD 삭제 여부를 체크한다.
							reCheckNVDInfo = true;
						}
					} else {
						// NVD 삭제 여부를 체크한다.
						reCheckNVDInfo = true;
					}
				} else {
					Map<String, Object> nvdInfo = vulnerabilityMapper.selectNvdInfoWithOutVer(ossBean);
					if (nvdInfo != null && !nvdInfo.isEmpty()) {
						String key = ((String) nvdInfo.get("PRODUCT") + "_" + (String) nvdInfo.get("VERSION") + "_" + (String) nvdInfo.get("VENDOR") + "_" + (String) nvdInfo.get("CVE_ID")).toUpperCase();
						if (!_filteredNvdData.contains(key)) {
							List<Map<String, Object>> filteredNvdDataInfoList = new ArrayList<>();
							filteredNvdDataInfoList.add(nvdInfo);
							List<Map<String, Object>> rtnNvdInfoSndMailList = insertDataForDiscoveredMailing(ossBean, standardScore, false);
							updateVulnAndMailing(ossBean, nvdInfo, standardScore, _prjMailList, _reCalcPrjMailList, notUsedOssList, reCalcOssInfoMap, reCalcCommentsOssInfoMap, discoveredCommentsOssInfoMap, rtnNvdInfoSndMailList);
						} else {
							// NVD 삭제 여부를 체크한다.
							reCheckNVDInfo = true;
						}
					} else {
						// NVD 삭제 여부를 체크한다.
						reCheckNVDInfo = true;
					}
				}
				
				// NVD 정보는 없지만 oss list에 nvd 정보가 이미 설정되어 있는 경우
				// NVD 사이트에서 Excluding 되었을 가능성이 높다 (삭제됨)
				if (reCheckNVDInfo) {
					// 혹시 BATCH에서 오류가 발생하여 NVD_DATA_SCORE_V3 테이블에 DATA 가 등록되지 않은 경우 
					// 모든 NVD DATA가 삭제되는 것을 방지하기 위해서 건수가 0건이상인지 확인한다.
					if (hasNvdData && !isEmpty(ossBean.getCveId())) {
						scheduler_log.info("Vulnerability deleteOssVulnInfo OSS ID : " + ossBean.getOssId());
						// oss list에서 NVD 정보를 삭제한다.
						ossBean.setCvssScoreTo("0");
						ossBean.setCveIdTo("");
						vulnerabilityMapper.insertNvdOssHis(ossBean);
						vulnerabilityMapper.deleteOssVulnInfo(ossBean.getOssId());
						
						BigDecimal bdOrgMasterScore = new BigDecimal(String.valueOf(ossBean.getCvssScore()));
						
						// vulnerability mail > base score changed from 9.0 to 8.0
						if (bdOrgMasterScore.compareTo(new BigDecimal(standardScore)) > -1) {
							String[] nickNameList = ossMapper.checkNickNameRegOss(ossBean.getOssName()).toArray(new String[0]);
							ossBean.setOssNames(nickNameList);
							List<String> _prjList = vulnerabilityMapper.selectUsedVulnerabilityOssProject(ossBean);
							if (_prjList != null && !_prjList.isEmpty()) {
								_removeReCalcPrjMailList.addAll(_prjList);
							}
							
							reCalcOssInfoMap.put(ossBean.getOssName().toUpperCase()+"_"+ossBean.getOssVersion(), ossBean);
						}
					}
				}
				
				// check same product diff vendor
				if (!reCheckNVDInfo && !diffVendorList.containsKey(ossBean.getOssName())) {
					customDiffVendor(ossBean, nvdInfoKeyList, nvdInfoValueList, diffVendorList, ossInfoNamesMap);
				}
			}
		}
		
		if (!diffVendorList.isEmpty()) {
			String vendor = "";
			String cveId = "";
			
			for (String diffVendorKey : diffVendorList.keySet()) {
				List<String> diffVendor = diffVendorList.get(diffVendorKey);
				diffVendor = diffVendor.stream().distinct().collect(Collectors.toList());
				
				if (diffVendor != null && !diffVendor.isEmpty()) {
					Map<String, Object> map = new HashMap<>();
					for (int i=0; i < diffVendor.size(); i++) {
						String vendorProduct = " (" + diffVendor.get(i).split("[|]")[0] + "-" + diffVendor.get(i).split("[|]")[1] + ")";
						vendor += diffVendor.get(i).split("[|]")[0];
						vendor += vendorProduct;
						cveId += diffVendor.get(i).split("[|]")[2];
						
						if (i < diffVendor.size()-1) {
							vendor += "<br/>";
							cveId += "<br/>";
						}
					}
					map.put("product", diffVendorKey);
					map.put("cveId", cveId);
					map.put("vendor", vendor);
					
					_nvdInfoDiffVendorList.add(map);
					vendor = "";
					cveId = "";
				}
			}
		}
		
		CoCodeManager.getInstance().refreshOssInfo();
		
		int discoveredOssCnt = 0;
		String securityComment;
		if (_prjMailList != null && !_prjMailList.isEmpty()) {
			discoveredMailingFlag = true;
			
			List<String> prjMailList = new ArrayList<String>(new HashSet<String>(_prjMailList));
			scheduler_log.info("Vulnerability prj COUNT : " + prjMailList.size());
			
			for (String prjId : prjMailList) {
				CoMail mailBean = new CoMail(CoConstDef.CD_MAIL_TYPE_VULNERABILITY_PROJECT);
				mailBean.setParamPrjId(prjId);
				CoMailManager.getInstance().sendMail(mailBean);

				// security tab comment history
				securityComment = "";
				if (discoveredCommentsOssInfoMap.containsKey(prjId)) {
					List<Map<String, Object>> discoveredCommentsInfoList = discoveredCommentsOssInfoMap.get(prjId);
					if (discoveredCommentsInfoList != null && !discoveredCommentsInfoList.isEmpty()) {
						List<String> duplicatedOssNameList = new ArrayList<>();
						securityComment += "<p><strong>Vulnerability discovered</strong>";
						for (Map<String, Object> commentsInfo : discoveredCommentsInfoList) {
							OssMaster om = ossInfoMap.get(commentsInfo.get("OSS_ID"));
							String key = (om.getOssName() + "_" + avoidNull(om.getOssVersion(), "N/A") + "_" + (String) commentsInfo.get("CVE_ID")).toUpperCase();
							if (!duplicatedOssNameList.contains(key)) {
								securityComment += "<br />" + om.getOssName() + " (" + avoidNull(om.getOssVersion(), "N/A") + ") : " + CommonFunction.convertCveIdToLink((String) commentsInfo.get("CVE_ID"));
								duplicatedOssNameList.add(key);
							}
						}
					}
				}
				
				if (discoveredCommentsOssInfoMap.containsKey(prjId) && reCalcCommentsOssInfoMap.containsKey(prjId)) {
					Map<String, OssMaster> reCalcCommentsInfo = reCalcCommentsOssInfoMap.get(prjId);
					if (!MapUtils.isEmpty(reCalcCommentsInfo)) {
						List<String> duplicatedOssNameList = new ArrayList<>();
						securityComment += "<br /><br />";
						securityComment += "<strong>Vulnerability recalculated</strong>";
						
						for(String reCalcInfo : reCalcCommentsInfo.keySet() ){
							OssMaster om = reCalcCommentsInfo.get(reCalcInfo);
							String key = (om.getOssName() + "_" + avoidNull(om.getOssVersion(), "N/A") + "_" + om.getCveId()).toUpperCase();
							if (!duplicatedOssNameList.contains(key)) {
								securityComment += "<br />" + om.getOssName() + " (" + avoidNull(om.getOssVersion(), "N/A") + ") : " + CommonFunction.convertCveIdToLink(om.getCveId());
								duplicatedOssNameList.add(key);
							}
						}
					}
				}
				
				if (!isEmpty(securityComment)) {
					securityComment += "</p>";
					
					CommentsHistory commHisBean = new CommentsHistory();
					commHisBean.setReferenceDiv(CoConstDef.CD_DTL_COMMENT_SECURITY_HIS);
					commHisBean.setReferenceId(prjId);
					commHisBean.setContents(securityComment);
					commHisBean.setLoginUserName("oscAdmin");
					
					commentService.registComment(commHisBean, false);
				}
			}
		}
		
		if (_reCalcPrjMailList != null && !_reCalcPrjMailList.isEmpty()) {
			List<String> prjMailList = new ArrayList<String>(new HashSet<String>(_reCalcPrjMailList));
			scheduler_log.info("Vulnerability risk recalculated prj COUNT : " + _reCalcPrjMailList.size());
			for (String prjId : prjMailList) {
				CoMail mailBean = new CoMail(CoConstDef.CD_MAIL_TYPE_VULNERABILITY_PROJECT_RECALCULATED);
				mailBean.setParamPrjId(prjId);
				mailBean.setParamStandardScore(standardScore);
				mailBean.setParamOssInfoMap(reCalcOssInfoMap);
				CoMailManager.getInstance().sendMail(mailBean);
				
				// security tab comment history
				securityComment = "";
				if (!discoveredCommentsOssInfoMap.containsKey(prjId) && reCalcCommentsOssInfoMap.containsKey(prjId)) {
					Map<String, OssMaster> reCalcCommentsInfo = reCalcCommentsOssInfoMap.get(prjId);
					
					if (!MapUtils.isEmpty(reCalcCommentsInfo)) {
						securityComment += "<p><strong>Vulnerability recalculated</strong>";
						for(String key : reCalcCommentsInfo.keySet() ){
							OssMaster om = reCalcCommentsInfo.get(key);
							securityComment += "<br />" + om.getOssName() + " (" + avoidNull(om.getOssVersion(), "N/A") + ") : " + CommonFunction.convertCveIdToLink(om.getCveId());
						}
						securityComment += "</p>";
						
						CommentsHistory commHisBean = new CommentsHistory();
						commHisBean.setReferenceDiv(CoConstDef.CD_DTL_COMMENT_SECURITY_HIS);
						commHisBean.setReferenceId(prjId);
						commHisBean.setContents(securityComment);
						commHisBean.setLoginUserName("oscAdmin");
						
						commentService.registComment(commHisBean, false);
					}
				}
			}
			
		}
		
		if (_removeReCalcPrjMailList != null && !_removeReCalcPrjMailList.isEmpty()) {
			List<String> prjMailList = new ArrayList<String>(new HashSet<String>(_removeReCalcPrjMailList));
			scheduler_log.info("Vulnerability risk remove > recalculated prj COUNT : " + _removeReCalcPrjMailList.size());
			for (String prjId : prjMailList) {
				CoMail mailBean = new CoMail(CoConstDef.CD_MAIL_TYPE_VULNERABILITY_PROJECT_REMOVE_RECALCULATED);
				mailBean.setParamPrjId(prjId);
				mailBean.setParamStandardScore(standardScore);
				mailBean.setParamOssInfoMap(reCalcOssInfoMap);
				CoMailManager.getInstance().sendMail(mailBean);
			}
			
		}
		
		if (!notUsedOssList.isEmpty()) {
			discoveredMailingFlag = true;
			discoveredOssCnt = notUsedOssList.size();
			
			try {
				scheduler_log.info("Vulnerability notUsedOssList COUNT : " + notUsedOssList.size());
				CoMail mailBean = new CoMail(CoConstDef.CD_MAIL_TYPE_VULNERABILITY_OSS);
				mailBean.setParamOssKey(notUsedOssList);
				CoMailManager.getInstance().sendMail(mailBean);
			} catch (Exception e) {
				scheduler_log.error(e.getMessage(), e);
			}
		}
		
		if (reCalcOssInfoMap.keySet().size() > 0) {
			try {
				scheduler_log.info("Vulnerability reCalculated All COUNT : " + reCalcOssInfoMap.keySet().size());
				CoMail mailBean = new CoMail(CoConstDef.CD_MAIL_TYPE_VULNERABILITY_PROJECT_RECALCULATED_ALL);
				ArrayList<String> keyList = new ArrayList<String>(reCalcOssInfoMap.keySet());
				mailBean.setParamOssKey(keyList);
				CoMailManager.getInstance().sendMail(mailBean);
			} catch (Exception e) {
				scheduler_log.error(e.getMessage(), e);
			}
		}
		
		if (_nvdInfoDiffVendorList != null && _nvdInfoDiffVendorList.size() > 0) {
			try {
				scheduler_log.info("Vulnerability nvdInfoDiffVendorList COUNT : " + _nvdInfoDiffVendorList.size());
				CoMail mailBean = new CoMail(CoConstDef.CD_MAIL_TYPE_VULNERABILITY_NVDINFO_DIFF);
				mailBean.setParamList(_nvdInfoDiffVendorList);
				CoMailManager.getInstance().sendMail(mailBean);
			} catch (Exception e) {
				scheduler_log.error(e.getMessage(), e);
			}
		}
		
		if (discoveredMailingFlag) {
			scheduler_log.info("Vulnerability Discovered OSS COUNT : " + discoveredOssCnt);
			try {
				vulnerabilityMapper.updateOssDiscoveredSndEmail();
			} catch (Exception e) {
				scheduler_log.error(e.getMessage(), e);
			}
		}
	}

	private void customDiffVendor(OssMaster ossBean, List<String> nvdInfoKeyList, List<String> nvdInfoValueList, Map<String, List<String>> diffVendorList, Map<String, String> ossInfoNamesMap) {
		List<Map<String, Object>> checkDiffVendor = vulnerabilityMapper.selectDiffVendorForProduct(ossBean);
		List<Map<String, Object>> diffVendorProductList = checkDiffVendor.stream().filter(e -> !ossInfoNamesMap.containsKey((e.get("VENDOR") + "-" + e.get("PRODUCT")).toUpperCase())).collect(Collectors.toList());
		if (diffVendorProductList != null && !diffVendorProductList.isEmpty()) {
			nvdInfoValueList = new ArrayList<>();
			String nvdInfoKey = "";
			for (Map<String, Object> nvdInfo : diffVendorProductList) {
				nvdInfoKey = ((String) nvdInfo.get("VENDOR") + "|" + (String) nvdInfo.get("PRODUCT")).toUpperCase();
				if (!nvdInfoKeyList.contains(nvdInfoKey)) {
					nvdInfoKeyList.add(nvdInfoKey);
					nvdInfoValueList.add((String) nvdInfo.get("VENDOR") + "|" + (String) nvdInfo.get("PRODUCT") + "|" + (String) nvdInfo.get("CVE_ID"));
				}
			}
			
			if (nvdInfoValueList.size() > 1) {
				diffVendorList.put(ossBean.getOssName(), nvdInfoValueList);
			}
			
			nvdInfoValueList = null;
		}
	}

	private List<Map<String, Object>> insertDataForDiscoveredMailing(OssMaster ossBean, String standardScore, boolean vendorProductFlag) {
		List<Map<String, Object>> rtnNvdInfoSndMailList = new ArrayList<>();
		List<Map<String, Object>> nvdInfoSndMailList = null;
		if (vendorProductFlag) {
			nvdInfoSndMailList = vulnerabilityMapper.selectVendorProductSndMailList(ossBean);
		} else {
			nvdInfoSndMailList = vulnerabilityMapper.selectNvdInfoSndMailList(ossBean);
		}
		
		ossBean.setSndMailCheckFlag(CoConstDef.FLAG_YES);
		List<Map<String, Object>> discoveredMailSendList = vulnerabilityMapper.selectDiscoveredSndMailList(ossBean);
		if (discoveredMailSendList != null && !discoveredMailSendList.isEmpty()) {
			List<Map<String, Object>> deduplicatedMailingCheckList = nvdInfoSndMailList.stream()
																	.filter(e -> 
																			discoveredMailSendList.stream()
																			.filter(o -> 
																					((String) e.get("PRODUCT") + "|" + (String) e.get("VERSION") + "|"	+ (String) e.get("CVE_ID") + "|" + ((Float) e.get("CVSS_SCORE")).toString())
																					.equalsIgnoreCase(((String) o.get("PRODUCT") + "|" + (String) o.get("VERSION") + "|" + (String) o.get("CVE_ID") + "|" + ((Float) o.get("CVSS_SCORE")).toString()))
																			).collect(Collectors.toList()).size() == 0
																	).collect(Collectors.toList());
			
			if (deduplicatedMailingCheckList != null && !deduplicatedMailingCheckList.isEmpty()) {
				for (Map<String, Object> mailingInfo : deduplicatedMailingCheckList) {
					mailingInfo.put("OSS_ID", ossBean.getOssId());
					vulnerabilityMapper.insertOssDiscoveredSndEmail(mailingInfo);
					rtnNvdInfoSndMailList.add(mailingInfo);
				}
			}
		} else {
			for (Map<String, Object> mailingInfo : nvdInfoSndMailList) {
				mailingInfo.put("OSS_ID", ossBean.getOssId());
				vulnerabilityMapper.insertOssDiscoveredSndEmail(mailingInfo);
				rtnNvdInfoSndMailList.add(mailingInfo);
			}
		}
		
		return rtnNvdInfoSndMailList;
	}

	private List<Map<String, Object>> filteredNvdData(List<String> _filteredNvdData, List<Map<String, Object>> nvdDataList, boolean fileCheckFlag) {
		List<Map<String, Object>> filteredNvdDataList = new ArrayList<>();
		
		if (fileCheckFlag) {
			filteredNvdDataList.addAll(nvdDataList);
		} else {
			List<Map<String, Object>> checkNvdDataList = new ArrayList<>();
			checkNvdDataList.addAll(nvdDataList);
			for (Map<String, Object> info : checkNvdDataList) {
				String checkKey = ((String) info.get("PRODUCT") + "_" + (String) info.get("VERSION") + "_" + (String) info.get("VENDOR") + "_" + (String) info.get("CVE_ID")).toUpperCase();
				if (!_filteredNvdData.contains(checkKey)) {
					filteredNvdDataList.add(info);
				}
			}
		}
		
		if (filteredNvdDataList != null && !filteredNvdDataList.isEmpty()) {
			if (filteredNvdDataList.size() > 1) {
				Collections.sort(filteredNvdDataList, new Comparator<Map<String, Object>>() {
					@Override
					public int compare(Map<String, Object> o1, Map<String, Object> o2) {
						if (new BigDecimal(o1.get("CVSS_SCORE").toString()).compareTo(new BigDecimal(o2.get("CVSS_SCORE").toString())) > 0) {
							return -1;
						}else {
							return 1;
						}
					}
				});
			}
		}
		
		return filteredNvdDataList;
	}

	@Override
	public Map<String, Object> getCveInfo(String cveId) {
		return vulnerabilityMapper.getCveInfo(cveId);
	}
	
	private void updateVulnAndMailing(OssMaster ossBean, Map<String, Object> nvdInfo, String standardScore, List<String> _prjMailList, List<String> _reCalcPrjMailList, List<String> notUsedOssList,
			Map<String, OssMaster> reCalcOssInfoMap, Map<String, Map<String, OssMaster>> reCalcCommentsOssInfoMap, Map<String, List<Map<String, Object>>> discoveredCommentsOssInfoMap, List<Map<String, Object>> rtnNvdInfoSndMailList) {
		BigDecimal bdScore = new BigDecimal(String.valueOf(nvdInfo.get("CVSS_SCORE")));
		BigDecimal bdOrgMasterScore = new BigDecimal(CommonFunction.avoidNull(ossBean.getCvssScore(), "0"));
		String cveId = String.valueOf(nvdInfo.get("CVE_ID"));
		
		// 현재 등록되어 있는 score와 cve_id가 일치하지 않으면 업데이트
		if ( !bdScore.equals(bdOrgMasterScore) || !cveId.equals(ossBean.getCveId())) {
			scheduler_log.info("Vulnerability updateOssVulnInfo OSS ID : " + ossBean.getOssId());
			ossBean.setCvssScoreTo(String.valueOf(nvdInfo.get("CVSS_SCORE")));
			ossBean.setCveIdTo(String.valueOf(nvdInfo.get("CVE_ID")));
			vulnerabilityMapper.insertNvdOssHis(ossBean);
			
			OssMaster newNVD = new OssMaster();
			newNVD.setOssId(ossBean.getOssId());
			newNVD.setCvssScore(ossBean.getCvssScoreTo());
			newNVD.setCveId(ossBean.getCveIdTo());
			vulnerabilityMapper.updateOssVulnInfoNew(newNVD);
		}

		// score가 7.0 이상인 경우만 메일을 발송
		// 처음으로 9.0 이상 Score가 등록된 경우 (기존 Score가 9.0 미만이고 NVD update score가 9.0 이상인 경우)
		// vulnerability mail > base score changed from 9.0 to 8.0
		// condition change > if the cvss score value matching ossName + ossVersion is 8.0 or higher (new or modify)
//		if (bdScore.compareTo(new BigDecimal(standardScore)) > -1 && bdOrgMasterScore.compareTo(new BigDecimal(standardScore)) < 0) {
		ossBean.setSndMailCheckFlag(CoConstDef.FLAG_NO);
		if (vulnerabilityMapper.checkDiscoveredSndMailCnt(ossBean) > 0) {
			String[] nickNameList = ossMapper.checkNickNameRegOss(ossBean.getOssName()).toArray(new String[0]);
			ossBean.setOssNames(nickNameList);
			List<String> _prjList = vulnerabilityMapper.selectUsedVulnerabilityOssProject(ossBean);
			
			if (_prjList != null && !_prjList.isEmpty()) {
				_prjMailList.addAll(_prjList);
				for (String prj : _prjList) {
					if (discoveredCommentsOssInfoMap.containsKey(prj)) {
						List<Map<String, Object>> discoveredCommentsOssInfoList = discoveredCommentsOssInfoMap.get(prj);
						discoveredCommentsOssInfoList.addAll(rtnNvdInfoSndMailList);
						discoveredCommentsOssInfoMap.put(prj, discoveredCommentsOssInfoList);
					} else {
						discoveredCommentsOssInfoMap.put(prj, rtnNvdInfoSndMailList);
					}
				}
			}
			notUsedOssList.add(ossBean.getOssName().toUpperCase()+"_"+ossBean.getOssVersion());
		}
		
		// NVD Score가 9.0 미만이면서 기존 Score가 9.0 이상인 경우, Score가 변경되었음을 메일로 통지
		// vulnerability mail > base score changed from 9.0 to 8.0
		if (bdScore.compareTo(new BigDecimal(standardScore)) < 0 && bdOrgMasterScore.compareTo(new BigDecimal(standardScore)) > -1) {
			String[] nickNameList = ossMapper.checkNickNameRegOss(ossBean.getOssName()).toArray(new String[0]);
			ossBean.setOssNames(nickNameList);
			List<String> _prjList = vulnerabilityMapper.selectUsedVulnerabilityOssProject(ossBean);
			if (_prjList != null && !_prjList.isEmpty()) {
				_reCalcPrjMailList.addAll(_prjList);
				for (String prj : _prjList) {
					Map<String, OssMaster> reCalcCommentsOssInfo = null;
					if (reCalcCommentsOssInfoMap.containsKey(prj)) {
						reCalcCommentsOssInfo = reCalcCommentsOssInfoMap.get(prj);
					} else {
						reCalcCommentsOssInfo = new HashMap<>();
					}
					
					reCalcCommentsOssInfo.put(ossBean.getOssName().toUpperCase()+"_"+ossBean.getOssVersion(), ossBean);
					reCalcCommentsOssInfoMap.put(prj, reCalcCommentsOssInfo);
				}
			}
			reCalcOssInfoMap.put(ossBean.getOssName().toUpperCase()+"_"+ossBean.getOssVersion(), ossBean);
		}
	}

	@Override
	public List<String> getPatchLinkForNvdData(String cveId) {
		return vulnerabilityMapper.getPatchLinkForNvdData(cveId);
	}

	@Override
	public List<Vulnerability> getSecurityVulnListByOssName(OssMaster bean) {
		if ("N/A".equals(bean.getOssVersion()) || isEmpty(bean.getOssVersion())) {
			bean.setOssVersion("-");
		}
		
		String securityStandardScore = codeMapper.getCodeDetail(CoConstDef.CD_SECURITY_VULNERABILITY_SCORE, CoConstDef.CD_SECURITY_VULNERABILITY_DETAIL_SCORE).getCdDtlExp();
		List<Vulnerability> list = null;
		String[] nicknameList = null;
		List<String> convertNicknameList = null;
		List<String> vendorProductSearchList = new ArrayList<>();
		boolean convertFlag = false;
		String ossName = null;
		
		try {
			nicknameList = ossService.getOssNickNameListByOssName(bean.getOssName());
			if(nicknameList.length == 0) {
				if (bean.getOssName().contains("_")) {
					ossName = bean.getOssName();
					bean.setOssName(bean.getOssName().replaceAll("_", " "));
				}
				nicknameList = ossService.getOssNickNameListByOssName(bean.getOssName());
			}
			
			if (bean.getOssName().contains("-")) {
				vendorProductSearchList.add(bean.getOssName());
				vendorProductSearchList.add(ossName);
			}
			
			if(nicknameList.length > 0) {
				for (String nick : nicknameList) {
					if (nick.contains(" ")) {
						if (!convertFlag) {
							convertNicknameList = new ArrayList<>();
							convertFlag = true;
						}
						convertNicknameList.add(nick.replaceAll(" ", "_"));
					}
					
					if (nick.contains("-")) {
						vendorProductSearchList.add(nick);
					}
				}
				
				if (convertNicknameList != null && !convertNicknameList.isEmpty()) {
					if (bean.getOssName().contains(" ")) convertNicknameList.add(bean.getOssName().replaceAll(" ", "_"));
					convertNicknameList.addAll(Arrays.asList(nicknameList));
					convertNicknameList = convertNicknameList.stream().distinct().collect(Collectors.toList());
					nicknameList = convertNicknameList.toArray(new String[convertNicknameList.size()]);
				} else {
					if (bean.getOssName().contains(" ")) {
						convertNicknameList = new ArrayList<>();
						convertNicknameList.add(bean.getOssName().replaceAll(" ", "_"));
						convertNicknameList.addAll(Arrays.asList(nicknameList));
						convertNicknameList = convertNicknameList.stream().distinct().collect(Collectors.toList());
						nicknameList = convertNicknameList.toArray(new String[convertNicknameList.size()]);
					}
				}
			} else {
				if (bean.getOssName().contains(" ")) {
					nicknameList = new String[] {bean.getOssName().replaceAll(" ", "_")};
				}
			}
			
			if (vendorProductSearchList != null && !vendorProductSearchList.isEmpty()) {
				bean.setOssNicknameArr(vendorProductSearchList.toArray(new String[vendorProductSearchList.size()]));
			} else {
				bean.setOssNicknameArr(null);
			}
			
			bean.setOssNicknames(nicknameList);
			bean.setSecurityStandardScore(Float.valueOf(securityStandardScore));
			list = vulnerabilityMapper.getSecurityVulnListByOssName(bean);
			list = list.stream().filter(CommonFunction.distinctByKey(e->e.getCveId()+e.getCvssScore())).sorted(Comparator.comparing(Vulnerability::getCvssScore).reversed()).collect(Collectors.toList());
		} catch (Exception e) {
			log.error(e.getMessage());
		}
		
		return list;
	}

	@Override
	public List<Map<String, Object>> getCpeInfoAndRange(String cveId, String ossName) {
		return vulnerabilityMapper.getCpeInfoAndRange(cveId, ossName);
	}

	@Override
	public Vulnerability selectNotFixedCveInfo(OssMaster bean) {
		if ("N/A".equals(bean.getOssVersion()) || isEmpty(bean.getOssVersion())) {
			bean.setOssVersion("-");
		}
		
		Vulnerability vuln = null;
		String[] nicknameList = null;
		List<String> convertNicknameList = null;
		List<String> vendorProductSearchList = new ArrayList<>();
		boolean convertFlag = false;
		String ossName = null;
		
		try {
			nicknameList = ossService.getOssNickNameListByOssName(bean.getOssName());
			if(nicknameList.length == 0) {
				if (bean.getOssName().contains("_")) {
					ossName = bean.getOssName();
					bean.setOssName(bean.getOssName().replaceAll("_", " "));
				}
				nicknameList = ossService.getOssNickNameListByOssName(bean.getOssName());
			}
			
			if (bean.getOssName().contains("-")) {
				vendorProductSearchList.add(bean.getOssName());
				vendorProductSearchList.add(ossName);
			}
			
			if(nicknameList.length > 0) {
				for (String nick : nicknameList) {
					if (nick.contains(" ")) {
						if (!convertFlag) {
							convertNicknameList = new ArrayList<>();
							convertFlag = true;
						}
						convertNicknameList.add(nick.replaceAll(" ", "_"));
					}
					
					if (nick.contains("-")) {
						vendorProductSearchList.add(nick);
					}
				}
				
				if (convertNicknameList != null && !convertNicknameList.isEmpty()) {
					if (bean.getOssName().contains(" ")) convertNicknameList.add(bean.getOssName().replaceAll(" ", "_"));
					convertNicknameList.addAll(Arrays.asList(nicknameList));
					convertNicknameList = convertNicknameList.stream().distinct().collect(Collectors.toList());
					nicknameList = convertNicknameList.toArray(new String[convertNicknameList.size()]);
				} else {
					if (bean.getOssName().contains(" ")) {
						convertNicknameList = new ArrayList<>();
						convertNicknameList.add(bean.getOssName().replaceAll(" ", "_"));
						convertNicknameList.addAll(Arrays.asList(nicknameList));
						convertNicknameList = convertNicknameList.stream().distinct().collect(Collectors.toList());
						nicknameList = convertNicknameList.toArray(new String[convertNicknameList.size()]);
					}
				}
			} else {
				if (bean.getOssName().contains(" ")) {
					nicknameList = new String[] {bean.getOssName().replaceAll(" ", "_")};
				}
			}
			
			if (vendorProductSearchList != null && !vendorProductSearchList.isEmpty()) {
				bean.setOssNicknameArr(vendorProductSearchList.toArray(new String[vendorProductSearchList.size()]));
				vuln = vulnerabilityMapper.selectNotFixedCveInfo(bean);
			}
			
			if (vuln == null) {
				bean.setOssNicknames(nicknameList);
				vuln = vulnerabilityMapper.selectNotFixedCveInfo(bean);
			}
		} catch (Exception e) {
			log.error(e.getMessage());
		}
		
		return vuln;
	}

	@Override
	public boolean getCpeMatchForCpeInfoCnt(Map<String, Object> paramMap) {
		return vulnerabilityMapper.getCpeMatchForCpeInfoCnt(paramMap) > 0 ? true : false;
	}
}
