package com.unionpay.acp.sdk.gm;

import static com.unionpay.acp.sdk.SDKConstants.POINT;
import static com.unionpay.acp.sdk.SDKConstants.UNIONPAY_CNNAME;
import static com.unionpay.acp.sdk.SDKUtil.isEmpty;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;

import com.unionpay.acp.sdk.CertUtil;


public class GmCertUtil{

    private final static Logger logger = Logger.getLogger(GmCertUtil.class);
	
	static class Key {

	    private String certId;
	    private PublicKey pubKey;
	    private PrivateKey priKey;

	    public String getCertId() {
	        return certId;
	    }

	    public PublicKey getPubKey() {
	        return pubKey;
	    }

	    public PrivateKey getPriKey() {
	        return priKey;
	    }

		public void setCertId(String certId) {
			this.certId = certId;
		}

		public void setPubKey(PublicKey pubKey) {
			this.pubKey = pubKey;
		}

		public void setPriKey(PrivateKey priKey) {
			this.priKey = priKey;
		}

		@Override
		public String toString() {
			return "key: certId="  + certId;
		}
	}

	private static ConcurrentHashMap<String, Key> verifyCertsSnMap = new ConcurrentHashMap<String, Key>();
	private static ConcurrentHashMap<String, Key> signCertsPathMap = new ConcurrentHashMap<String, Key>();
    private static ConcurrentHashMap<String, Key> signCertsSnMap = new ConcurrentHashMap<String, Key>();
	private static Key encryptCert = null;
	private static Key pinEncryptCert = null;
    private static ConcurrentHashMap<String, X509Certificate> verifyCerts510 = new ConcurrentHashMap<String, X509Certificate>();
	private static X509Certificate middleCert = null;
	private static X509Certificate rootCert = null;
	
	static{
		addProvider();
		init();
	}
	
	/**
	 * 添加签名，验签，加密算法提供者
	 */
	private static void addProvider(){
		if (Security.getProvider("BC") == null) {
			logger.info("add BC provider");
			Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
		} else {
			Security.removeProvider("BC"); //解决eclipse调试时tomcat自动重新加载时，BC存在不明原因异常的问题。
			Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
			logger.info("re-add BC provider");
		}
	}
	
    /**
     * 如果需要改用其他properties文件，可以调这个，默认用Config类默认使用调文件。
     * @param config
     */
	public static void init(){
		verifyCertsSnMap.clear();
		signCertsPathMap.clear();
	    signCertsSnMap.clear();
		getSignKey();
		initVerifySignCerts();
		initEncryptCert();
		initPinEncryptCert();
		initMiddleCert();//初始化验签证书的中级证书
		initRootCert();//初始化验签证书的根证书
	}

	/**
	 * 用配置文件acp_sdk.properties配置路径 加载5.1验签证书中级证书
	 */
	private static void initMiddleCert() {
        String path = GmSDKConfig.getConfig().getMiddleCertPath();
        if(isEmpty(path)){
            logger.warn(GmSDKConfig.SDK_MIDDLECERT_PATH + " is empty");
            return;
        }
        middleCert = CertUtil.readX509Cert(path);
        logger.info("加载中级证书==>" + path + (middleCert != null ?"成功":"失败"));
	}

	/**
     * 用配置文件acp_sdk.properties配置路径 加载5.1验签证书根证书
	 */
	private static void initRootCert() {
        String path = GmSDKConfig.getConfig().getRootCertPath();
        if(isEmpty(path)){
            logger.warn(GmSDKConfig.SDK_ROOTCERT_PATH + " is empty");
            return;
        }
        rootCert = CertUtil.readX509Cert(path);
        logger.info("加载根证书==>" + path + (rootCert != null ?"成功":"失败"));
	}
	
	private static void initVerifySignCerts(){
		String dir = GmSDKConfig.getConfig().getValidateCertDir();
		ConcurrentHashMap<String, Key> tmpVerifyCerts = new ConcurrentHashMap<String, Key>();
		logger.info("加载验证签名证书目录==>" + dir);
		if (dir == null || dir.trim().length() == 0) {
			logger.error("WARN: acpsdk.validateCert.dir is empty");
			return;
		}
		CertificateFactory cf = null;
		FileInputStream in = null;
		try {
			cf = CertificateFactory.getInstance("X.509", "BC");
		} catch (Exception e) {
			logger.error("LoadVerifyCert Error", e);
			return ;
		}
		File fileDir = new File(dir);
		File[] files = fileDir.listFiles(new CerFilter());
		for (int i = 0; i < files.length; i++) {
			File file = files[i];
			try {
				in = new FileInputStream(file.getAbsolutePath());
				X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
				if(cert == null) {
					logger.error("Load verify cert error, " + file.getAbsolutePath() + " has error cert content.");
					continue;
				}
				Key key = new Key();
				key.setCertId(cert.getSerialNumber().toString(10));
				key.setPubKey(cert.getPublicKey());
				tmpVerifyCerts.put(key.getCertId(), key);
				// 打印证书加载信息,供测试阶段调试
				logger.info("[" + file.getAbsolutePath() + "][CertId=" + key.getCertId() +"]");
			} catch (CertificateException e) {
				logger.error("LoadVerifyCert Error CertificateException", e);
			}catch (FileNotFoundException e) {
				logger.error("LoadVerifyCert Error File Not Found", e);
			}catch (Exception e) {
				logger.error("LoadVerifyCert Error", e);
			}finally {
				if (null != in) {
					try {
						in.close();
					} catch (IOException e) {
						logger.error(e.toString());
					}
				}
			}
		}
		logger.info("LoadVerifyCert Finish");
		if(tmpVerifyCerts != null && tmpVerifyCerts.size() > 0){
			verifyCertsSnMap = tmpVerifyCerts;
		}
	}
	
	/**
	 * 证书文件过滤器
	 * 
	 */
	static class CerFilter implements FilenameFilter {
		public boolean isCer(String name) {
			if (name.toLowerCase().endsWith(".cer")) {
				return true;
			} else {
				return false;
			}
		}
		public boolean accept(File dir, String name) {
			return isCer(name);
		}
	}
	
	private static Key getVerifySignKey(String certId){
		if(certId == null)
			throw new IllegalArgumentException("null argument");
		if(!verifyCertsSnMap.containsKey(certId))
			initVerifySignCerts();
		if(verifyCertsSnMap.containsKey(certId))
			return verifyCertsSnMap.get(certId);
		logger.error("cannot find this cert: " + certId);
		return null;
	}

	public static PublicKey getValidatePublicKey(String certId){
		return getVerifySignKey(certId).getPubKey();
	}
	
	private static void initEncryptCert(){
        String path = GmSDKConfig.getConfig().getEncryptCertPath();
        if(isEmpty(path)){
            logger.warn(GmSDKConfig.SDK_ENCRYPTCERT_PATH + " is empty");
            return;
        }
        encryptCert = readGmPubCert(path);
        logger.info("加载加密证书==>" + path + (encryptCert != null ?"成功":"失败"));
	}
	
	private static void initPinEncryptCert(){
        String path = GmSDKConfig.getConfig().getPinEncryptCertPath();
        if(isEmpty(path)){
            logger.warn(GmSDKConfig.SDK_PINENCRYPTCERT_PATH + " is empty");
            return;
        }
        pinEncryptCert = readGmPubCert(path);
        logger.info("加载6.0统一支付产品pin加密证书==>" + path + (pinEncryptCert != null ?"成功":"失败"));
	}
	
	
//	public static PublicKey getEncryptCertPublicKey(){
//		return getEncryptKey(GmSDKConfig.getConfig().getEncryptCertPath()).getPubKey();
//	}
//	
//	public static String getEncryptCertId(){
//		return getEncryptKey(GmSDKConfig.getConfig().getEncryptCertPath()).getCertId();
//	}
	
	public static Key getEncryptCert(){
    	if(GmCertUtil.encryptCert == null) {
            initEncryptCert();
    	}
    	return GmCertUtil.encryptCert;
	}
	
	/**
	 * 获取敏感信息加密证书PublicKey
	 * 
	 * @return
	 */
	public static Key getPinEncryptCert() {
    	if(GmCertUtil.pinEncryptCert == null) {
            initPinEncryptCert();
    	}
    	return GmCertUtil.pinEncryptCert;
	}

	/**
	 * 
	 * @param certFilePath
	 * @param certPwd
	 */
	private static Key getSignKeyByPath(String certFilePath, String certPwd) {
		if(certFilePath == null || certPwd == null)
			throw new IllegalArgumentException("null argument");
//		logger.debug("read sign key from [" + certFilePath + "] pwd [" + certPwd + "]");
		if(!signCertsPathMap.containsKey(certFilePath)){
			Key key = readGmPriCert(certFilePath, certPwd);
			if(key != null) {
				logger.info("read sign key from [" + certFilePath + "] succeed [" + key.getCertId() + "]");
                signCertsPathMap.put(certFilePath, key);
                signCertsSnMap.put(key.getCertId(), key);
            }
		}
		return signCertsPathMap.get(certFilePath);
	}

    /**
     *
     * @return
     */
    private static Key getSignKey() {
		String path = GmSDKConfig.getConfig().getSignCertPath();
		String pwd = GmSDKConfig.getConfig().getSignCertPwd();
        if(path == null || pwd == null) {
        	logger.error("没有找到默认签名证书默认配置。");
            return null;
        }
        return getSignKeyByPath(path, pwd);
    }
    
    public static String getSignCertId() {
		return getSignKey().getCertId();
	}
    
    public static PrivateKey getSignCertPrivateKey() {
		return getSignKey().getPriKey();
	}
    
    public static String getCertIdByKeyStoreMap(String path, String pwd) {
		return getSignKeyByPath(path, pwd).getCertId();
	}
    
    public static PrivateKey getSignCertPrivateKey(String path, String pwd) {
		return getSignKeyByPath(path, pwd).getPriKey();
	}

    private static Key readGmPriCert(String pfxkeyfile, String pwd) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(pfxkeyfile);
            byte[] bs = IOUtils.toByteArray(fis);
            bs = Base64.decodeBase64(bs);
            GmUtil.Sm2Cert cert = GmUtil.readSm2File(bs, pwd);
            Key key = new Key();
            key.setCertId(new BigInteger(cert.getCertId(), 10).toString(10));
            key.setPriKey(cert.getPrivateKey());
            return key;
        } catch (Exception e) {
            logger.error("getKeyInfo Error [" + pfxkeyfile + "]", e);
            return null;
        } finally {
            if(null != fis)
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
	
    private static Key readGmPubCert(String path) {
    	FileInputStream in = null;
    	try {
    		in = new FileInputStream(path);
    		CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
			X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
			if(cert == null) {
				logger.error("Load verify cert error, " + path + " has error cert content.");
				return null;
			}
			Key key = new Key();
			key.setCertId(cert.getSerialNumber().toString(10));
			key.setPubKey(cert.getPublicKey());
			// 打印证书加载信息,供测试阶段调试
			logger.info("[" + path + "][CertId=" + key.getCertId() +"]");
			return key;
		} catch (CertificateException e) {
			logger.error("LoadVerifyCert Error CertificateException", e);
		}catch (FileNotFoundException e) {
			logger.error("LoadVerifyCert Error File Not Found", e);
		}catch (Exception e) {
			logger.error("LoadVerifyCert Error", e);
		}finally {
			if (null != in) {
				try {
					in.close();
				} catch (IOException e) {
					logger.error(e.toString());
				}
			}
		}
		return null;
	}

    public static X509Certificate verifyAndGetVerifyPubKey(String x509CertString){

        if(isEmpty(x509CertString)) {
            logger.error("验签公钥证书传了空。");
            return null;
        }
        if(verifyCerts510.containsKey(x509CertString))
            return verifyCerts510.get(x509CertString);

        logger.debug("验签公钥证书：["+x509CertString+"]");
        X509Certificate x509Cert = CertUtil.genCertificateByStr(x509CertString);
        if (x509Cert == null) {
            logger.error("convert signPubKeyCert failed");
            return null;
        }
        // 验证证书链
        if (!GmCertUtil.verifyCertificate(x509Cert)) {
            logger.error("验证公钥证书失败，证书信息：[" + x509CertString + "]");
            return null;
        }
        logger.info("验证公钥验证成功：[" + x509Cert.getSerialNumber().toString(10) + "]");
        verifyCerts510.put(x509CertString, x509Cert);
        return x509Cert;
    }

    /**
     *
     * @param cert
     * @return
     */
    private static boolean verifyCertificate(X509Certificate cert) {
		
		if ( null == cert) {
			logger.error("cert must Not null");
			return false;
		}
		try {
			cert.checkValidity();//验证有效期
			if(!CertUtil.verifyCertificateChain(cert, middleCert, rootCert)){
				return false;
			}
		} catch (Exception e) {
			logger.error("verifyCertificate fail", e);
			return false;
		}
		
		if(GmSDKConfig.getConfig().isIfValidateCNName()){
			// 验证公钥是否属于银联
			if(!UNIONPAY_CNNAME.equals(CertUtil.getIdentitiesFromCertficate(cert))) {
				logger.error("cer owner is not CUP:" + CertUtil.getIdentitiesFromCertficate(cert));
				return false;
			}
		} else {
			// 验证公钥是否属于银联
			if(!UNIONPAY_CNNAME.equals(CertUtil.getIdentitiesFromCertficate(cert)) 
					&& !"ZunionpayTest".equals(CertUtil.getIdentitiesFromCertficate(cert))) {
				logger.error("cer owner is not CUP:" + CertUtil.getIdentitiesFromCertficate(cert));
				return false;
			}
		}
		return true;		
	}

    /**
	 * 重置敏感信息加密证书公钥
	 */
	public static int resetEncryptCertPublicKey(String strCert) {
        if (isEmpty(strCert)) {
            logger.error("传入证书信息为空。");
            return -1;
        }

        X509Certificate x509Cert = CertUtil.genCertificateByStr(strCert);
        // 没换，不需要更新
        if (GmCertUtil.getEncryptCert().certId.equals(
                x509Cert.getSerialNumber().toString(10))) {
            logger.info("返回证书和原证书一样，不用更新。");
            return 0;
        }

        final String localCertPath = GmSDKConfig.getConfig().getEncryptCertPath();
        if(isEmpty(localCertPath)){
            logger.error("未配置加密证书路径，无法执行此方法。");
            return -1;
        }
        File f = new File(localCertPath);
        if(!f.exists()) {
            logger.warn("原加密证书不存在：" + localCertPath);
        } else {
            // 将本地证书进行备份存储
            int i = localCertPath.lastIndexOf(POINT);
            String leftFileName = localCertPath.substring(0, i);
            String rightFileName = localCertPath.substring(i + 1);
            String newFileName = leftFileName + "_backup" + POINT + rightFileName;
            try {
                FileUtils.copyFile(f, new File(newFileName));
                logger.info("原加密证书备份成功。");
            } catch (IOException e) {
                logger.error("原加密证书备份失败，停止改证书。", e);
                return -1;
            }
        }
        // 备份成功,进行新证书的存储
        try {
            FileUtils.writeByteArrayToFile(f, strCert.getBytes(), false);
            logger.info("加密证书更新成功。");
            initEncryptCert();
            return 1;
        } catch (IOException e) {
            logger.error("加密证书更新失败。", e);
            return -1;
        }
    }

	/**
	 * 重置pin敏感信息加密证书公钥
	 */
	public static int resetPinEncryptCertPublicKey(String strCert) {
		if (isEmpty(strCert)) {
			logger.error("传入证书信息为空。");
			return -1;
		}

		X509Certificate x509Cert = CertUtil.genCertificateByStr(strCert);
		// 没换，不需要更新
		if (GmCertUtil.getPinEncryptCert().certId.equals(
				x509Cert.getSerialNumber().toString(10))) {
			logger.info("返回证书和原证书一样，不用更新。");
			return 0;
		}

		final String localCertPath = GmSDKConfig.getConfig().getPinEncryptCertPath();
		if(isEmpty(localCertPath)){
			logger.error("未配置加密证书路径，无法执行此方法。");
			return -1;
		}
		File f = new File(localCertPath);
		if(!f.exists()) {
			logger.warn("原加密证书不存在：" + localCertPath);
		} else {
			// 将本地证书进行备份存储
			int i = localCertPath.lastIndexOf(POINT);
			String leftFileName = localCertPath.substring(0, i);
			String rightFileName = localCertPath.substring(i + 1);
			String newFileName = leftFileName + "_backup" + POINT + rightFileName;
			try {
				FileUtils.copyFile(f, new File(newFileName));
				logger.info("原加密证书备份成功。");
			} catch (IOException e) {
				logger.error("原加密证书备份失败，停止改证书。", e);
				return -1;
			}
		}
		// 备份成功,进行新证书的存储
		try {
			FileUtils.writeByteArrayToFile(f, strCert.getBytes(), false);
			logger.info("加密证书更新成功。");
			initPinEncryptCert();
			return 1;
		} catch (IOException e) {
			logger.error("加密证书更新失败。", e);
			return -1;
		}
	}
	
}
