package com.neusoft.smart.pos.payment.utils;

import com.neusoft.smart.pos.framework.utils.FileUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;

public class UnionPayUtils {

    /**
     * 密钥算法
     */
    private static final String SIGN_CERT_TYPE = "PKCS12";

    private static final String encoding = "UTF-8";

    /**
     * 算法常量： SHA256
     */
    private static final String ALGORITHM_SHA256 = "SHA-256";

    /**
     * 算法常量：SHA256withRSA
     */
    private static final String BC_PROV_ALGORITHM_SHA256RSA = "SHA256withRSA";

    /**
     * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, byte[] cert,String certPassword){
        // 设置签名证书序列号
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = coverMap2String(data);
        byte[] byteSign = null;
        String stringSign = null;
        try {
            // 通过SHA256进行摘要并转16进制
            byte[] signDigest = sha256X16(stringData, encoding);
            byteSign = base64Encode(signBySoft256(getSignCertPrivateKey(cert,certPassword), signDigest));
            stringSign = new String(byteSign);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return stringSign;
    }

    /**
     * @param privateKey
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] signBySoft256(PrivateKey privateKey, byte[] data)
            throws Exception {
        byte[] result = null;
        Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA256RSA, "BC");
        st.initSign(privateKey);
        st.update(data);
        result = st.sign();
        return result;
    }

    /**
     * sha256计算后进行16进制转换
     *
     * @param data
     *            待计算的数据
     * @param encoding
     *            编码
     * @return 计算结果
     */
    public static byte[] sha256X16(String data, String encoding) {
        byte[] bytes = sha256(data, encoding);
        StringBuilder sha256StrBuff = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
                sha256StrBuff.append("0").append(
                        Integer.toHexString(0xFF & bytes[i]));
            } else {
                sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
            }
        }
        try {
            return sha256StrBuff.toString().getBytes(encoding);
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    /**
     * sha256计算
     *
     * @param datas
     *            待计算的数据
     * @param encoding
     *            字符集编码
     * @return
     */
    private static byte[] sha256(String datas, String encoding) {
        try {
            return sha256(datas.getBytes(encoding));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }

    /**
     * sha256计算.
     *
     * @param data
     *            待计算的数据
     * @return 计算结果
     */
    private static byte[] sha256(byte[] data) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance(ALGORITHM_SHA256);
            md.reset();
            md.update(data);
            return md.digest();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * BASE64编码
     *
     * @param inputByte
     *            待编码数据
     * @return 解码后的数据
     * @throws IOException
     */
    public static byte[] base64Encode(byte[] inputByte) throws IOException {
        return Base64.encodeBase64(inputByte);
    }

    /**
     * 将Map中的数据转换成key1=value1&key2=value2的形式 不包含签名域signature
     *
     * @param data
     *            待拼接的Map数据
     * @return 拼接好后的字符串
     */
    public static String coverMap2String(Map<String, String> data) {
        TreeMap<String, String> tree = new TreeMap<String, String>();
        Iterator<Map.Entry<String, String>> it = data.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            if ("signature".equals(en.getKey().trim())) {
                continue;
            }
            tree.put(en.getKey(), en.getValue());
        }
        it = tree.entrySet().iterator();
        StringBuffer sf = new StringBuffer();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            sf.append(en.getKey() + "=" + en.getValue()+ "&");
        }
        return sf.substring(0, sf.length() - 1);
    }

    /**
     * 通过keyStore 获取私钥签名证书PrivateKey对象
     *
     * @return
     */
    public static PrivateKey getSignCertPrivateKey(byte[] cert, String certPassword) {
        try {
            KeyStore keyStore = getKeyStore(cert,certPassword,SIGN_CERT_TYPE);
            Enumeration<String> aliasenum = keyStore.aliases();
            String keyAlias = null;
            if (aliasenum.hasMoreElements()) {
                keyAlias = aliasenum.nextElement();
            }
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias,certPassword.toCharArray());
            return privateKey;
        } catch (KeyStoreException e) {
            e.printStackTrace();
            return null;
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
            return null;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 用配置文件acp_sdk.properties中配置的私钥路径和密码 加载签名证书
     */
    private static KeyStore getKeyStore(byte[] cert, String certPassword,String type) {
        KeyStore keyStore = null;
        try {
            keyStore = getKeyInfo(cert,certPassword, type);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return keyStore;
    }

    /**
     * 将签名私钥证书文件读取为证书存储对象
     *
     * @param cert
     *            证书文件字节数组
     * @param keypwd
     *            证书密码
     * @param type
     *            证书类型
     * @return 证书对象
     * @throws IOException
     */
    private static KeyStore getKeyInfo(byte[] cert, String keypwd,String type) throws IOException {
        try {
            addProvider();
            KeyStore ks = KeyStore.getInstance(type, "BC");
            char[] nPassword = null;
            nPassword = null == keypwd || "".equals(keypwd.trim()) ? null: keypwd.toCharArray();
            if (null != ks) {
                ks.load(FileUtils.convertByteArrayToInputStream(cert), nPassword);
            }
            return ks;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 添加签名，验签，加密算法提供者
     */
    private static void addProvider(){
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } else {
            Security.removeProvider("BC"); //解决eclipse调试时tomcat自动重新加载时，BC存在不明原因异常的问题。
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        }
    }

//    /**
//     * 获取签名证书ID
//     * @return
//     */
//    public static String getSignCertId(){
//        return "68759663125";
//    }

    /**
     * 获取配置文件acp_sdk.properties中配置的签名私钥证书certId
     *
     * @return 证书的物理编号
     */
    public static String getSignCertId(byte[] certByte, String certPassword) {
        try {
            KeyStore keyStore = getKeyStore(certByte,certPassword,SIGN_CERT_TYPE);
            Enumeration<String> aliasenum = keyStore.aliases();
            String keyAlias = null;
            if (aliasenum.hasMoreElements()) {
                keyAlias = aliasenum.nextElement();
            }
            X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
            return cert.getSerialNumber().toString();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 将形如key=value&key=value的字符串转换为相应的Map对象
     *
     * @param result
     * @return
     */
    public static Map<String, String> convertResultStringToMap(String result) {
        Map<String, String> map =null;
        try {

            if (StringUtils.isNotBlank(result)) {
                if (result.startsWith("{") && result.endsWith("}")) {
                    result = result.substring(1, result.length() - 1);
                }
                map = parseQString(result);
            }

        } catch (UnsupportedEncodingException e) {
        }
        return map;
    }

    /**
     * 解析应答字符串，生成应答要素
     *
     * @param str
     *            需要解析的字符串
     * @return 解析的结果map
     * @throws UnsupportedEncodingException
     */
    public static Map<String, String> parseQString(String str)
            throws UnsupportedEncodingException {

        Map<String, String> map = new HashMap<String, String>();
        int len = str.length();
        StringBuilder temp = new StringBuilder();
        char curChar;
        String key = null;
        boolean isKey = true;
        boolean isOpen = false;//值里有嵌套
        char openName = 0;
        if(len>0){
            for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
                curChar = str.charAt(i);// 取当前字符
                if (isKey) {// 如果当前生成的是key

                    if (curChar == '=') {// 如果读取到=分隔符
                        key = temp.toString();
                        temp.setLength(0);
                        isKey = false;
                    } else {
                        temp.append(curChar);
                    }
                } else  {// 如果当前生成的是value
                    if(isOpen){
                        if(curChar == openName){
                            isOpen = false;
                        }

                    }else{//如果没开启嵌套
                        if(curChar == '{'){//如果碰到，就开启嵌套
                            isOpen = true;
                            openName ='}';
                        }
                        if(curChar == '['){
                            isOpen = true;
                            openName =']';
                        }
                    }
                    if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域，这时将map里添加
                        putKeyValueToMap(temp, isKey, key, map);
                        temp.setLength(0);
                        isKey = true;
                    }else{
                        temp.append(curChar);
                    }
                }

            }
            putKeyValueToMap(temp, isKey, key, map);
        }
        return map;
    }

    private static void putKeyValueToMap(StringBuilder temp, boolean isKey,
                                         String key, Map<String, String> map)
            throws UnsupportedEncodingException {
        if (isKey) {
            key = temp.toString();
            if (key.length() == 0) {
                throw new RuntimeException("QString format illegal");
            }
            map.put(key, "");
        } else {
            if (key.length() == 0) {
                throw new RuntimeException("QString format illegal");
            }
            map.put(key, temp.toString());
        }
    }

    /**
     * 获取银联校验参数
     * @param reqParam
     * @return
     */
    public static Map<String,String> getValidateData(Map<String,String> reqParam){
        Map<String, String> valideData = null;
        if (null != reqParam && !reqParam.isEmpty()) {
            Iterator<Map.Entry<String, String>> it = reqParam.entrySet().iterator();
            valideData = new HashMap<String, String>(reqParam.size());
            while (it.hasNext()) {
                Map.Entry<String, String> e = it.next();
                String key = (String) e.getKey();
                String value = (String) e.getValue();

                valideData.put(key, value);
            }
        }
        return valideData;
    }

    /**
     * 判断签名是否正确
     *
     * @return 签名是否正确
     */
    public static boolean isSignatureValid(Map<String,String> resData) {
        Map<String, String> valideData =getValidateData(resData);
        // 1.从返回报文中获取公钥信息转换成公钥对象
        String strCert = resData.get("signPubKeyCert");

        addProvider();
        X509Certificate x509Cert = genCertificateByStr(strCert);
        if(x509Cert == null) {
            return false;
        }
        // 2.验证证书链
//        if (!CertUtil.verifyCertificate(x509Cert)) {
//            return false;
//        }

        // 3.验签
        String stringSign = resData.get("signature");
        // 将Map信息转换成key1=value1&key2=value2的形式
        String stringData = coverMap2String(resData);
        try {
            // 验证签名需要用银联发给商户的公钥证书.
            boolean result = validateSignBySoft256(x509Cert.getPublicKey(),
                                base64Decode(stringSign.getBytes(encoding)),
                                sha256X16(stringData, encoding));
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将字符串转换为X509Certificate对象.
     *
     * @param x509CertString
     * @return
     */
    public static X509Certificate genCertificateByStr(String x509CertString) {
        X509Certificate x509Cert = null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
            InputStream tIn = new ByteArrayInputStream(
                    x509CertString.getBytes("ISO-8859-1"));
            x509Cert = (X509Certificate) cf.generateCertificate(tIn);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return x509Cert;
    }

    public static boolean validateSignBySoft256(PublicKey publicKey,byte[] signData, byte[] srcData) throws Exception {
        Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA256RSA, "BC");
        st.initVerify(publicKey);
        st.update(srcData);
        return st.verify(signData);
    }

    /**
     * BASE64解码
     *
     * @param inputByte
     *            待解码数据
     * @return 解码后的数据
     * @throws IOException
     */
    public static byte[] base64Decode(byte[] inputByte) throws IOException {
        return Base64.decodeBase64(inputByte);
    }
}
