package com.seeyon.apps.esign.po.esignapi;

import com.seeyon.apps.mplus.util.Base64;
import org.apache.commons.lang.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

public class EsignApiHeader {
    private String xTsignOpenAppId;
    private String xTsignOpenAuthMode;
    private String xTsignOpenCaSignature;
    private String xTsignOpenCaTimestamp;
    private String accept;
    private String contentType = "application/json; charset=UTF-8";
    private String contentMd5;
    private String httpMethod;
    private String date;
    private String headers;
    private String pathAndParameters;
    private String token;

    public String getxTsignOpenAppId() {
        return xTsignOpenAppId;
    }

    public void setxTsignOpenAppId(String xTsignOpenAppId) {
        this.xTsignOpenAppId = xTsignOpenAppId;
    }

    public String getX_Tsign_Open_Auth_Mode() {
        return xTsignOpenAuthMode;
    }

    public void setX_Tsign_Open_Auth_Mode(String x_Tsign_Open_Auth_Mode) {
        this.xTsignOpenAuthMode = x_Tsign_Open_Auth_Mode;
    }

    public String getxTsignOpenCaSignature() {
        return xTsignOpenCaSignature;
    }

    public void setxTsignOpenCaSignature(String xTsignOpenCaSignature) {
        this.xTsignOpenCaSignature = xTsignOpenCaSignature;
    }

    public String getxTsignOpenCaTimestamp() {
        return xTsignOpenCaTimestamp;
    }

    public void setxTsignOpenCaTimestamp(String xTsignOpenCaTimestamp) {
        this.xTsignOpenCaTimestamp = xTsignOpenCaTimestamp;
    }

    public String getAccept() {
        return accept;
    }

    public void setAccept(String accept) {
        this.accept = accept;
    }

    public String getContentType() {
        return contentType;
    }

    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    public String getContentMd5() {
        return contentMd5;
    }

    public void setContentMd5(String contentMd5) {
        this.contentMd5 = contentMd5;
    }

    public static EsignApiHeader build() {
        EsignApiHeader header = new EsignApiHeader();
        header.setAccept("*/*");
        header.setX_Tsign_Open_Auth_Mode("Signature");
        return header;
    }

    public EsignApiHeader pathAndParameters(String pathAndParameters) {
        this.pathAndParameters = pathAndParameters;
        return this;
    }

    public EsignApiHeader appId(String appId) {
        this.xTsignOpenAppId = appId;
        return this;
    }

    public EsignApiHeader heads(String headers) {
        this.headers = headers;
        return this;
    }

    public EsignApiHeader timeStamp() {
        this.xTsignOpenCaTimestamp = System.currentTimeMillis() + "";
        return this;
    }

    public EsignApiHeader token(String token) {
        this.token = token;
        return this;
    }

    public EsignApiHeader contentMD5(String body) {
        this.contentMd5 = getBodyContentMD5(body);
        return this;
    }

    public EsignApiHeader httpMethod(String httpMethod) {
        this.httpMethod = httpMethod;
        return this;
    }

    public EsignApiHeader signature(String secret) throws Exception {
        StringBuffer strBuff = new StringBuffer();
        strBuff.append(httpMethod).append("\n").append(accept).append("\n").append(contentMd5).append("\n")
                .append(contentType).append("\n").append("").append("\n");
        if ("".equals(headers)) {
            strBuff.append(headers).append(pathAndParameters);
        } else {
            strBuff.append(headers).append("\n").append(pathAndParameters);
        }
        String StringToSign = strBuff.toString();
        this.setxTsignOpenCaSignature(doSignatureBase64(StringToSign, secret));
        return this;
    }


    public String doSignatureBase64(String message, String secret) throws Exception {
        String algorithm = "HmacSHA256";
        Mac hmacSha256;
        String digestBase64 = null;
        try {
            hmacSha256 = Mac.getInstance(algorithm);
            byte[] keyBytes = secret.getBytes("UTF-8");
            byte[] messageBytes = message.getBytes("UTF-8");
            hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, algorithm));
            // 使用HmacSHA256对二进制数据消息Bytes计算摘要
            byte[] digestBytes = hmacSha256.doFinal(messageBytes);
            // 把摘要后的结果digestBytes使用Base64进行编码
            digestBase64 = new String(Base64.encodeBase64(digestBytes), "UTF-8");
        } catch (NoSuchAlgorithmException e) {
            String msg = MessageFormat.format("不支持此算法: {0}", e.getMessage());
            Exception ex = new Exception(msg);
            ex.initCause(e);
            throw ex;
        } catch (UnsupportedEncodingException e) {
            String msg = MessageFormat.format("不支持的字符编码: {0}", e.getMessage());
            Exception ex = new Exception(msg);
            ex.initCause(e);
            throw ex;
        } catch (InvalidKeyException e) {
            String msg = MessageFormat.format("无效的密钥规范: {0}", e.getMessage());
            Exception ex = new Exception(msg);
            ex.initCause(e);
            throw ex;
        }
        return digestBase64;
    }

    /***
     * 计算请求Body体的Content-MD5
     * @param bodyData 请求Body体数据
     * @return
     */
    public String getBodyContentMD5(String bodyData) {
        if("".equals(bodyData)) {
            return bodyData;
        }
        // 获取Body体的MD5的二进制数组（128位）
        byte[] bytes = getBodyMD5Bytes128(bodyData);
        // 对Body体MD5的二进制数组进行Base64编码
        return new String(Base64.encodeBase64(bytes));
    }

    public byte[] getBodyMD5Bytes128(String bodyData) {
        byte[] md5Bytes = null;
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(bodyData.getBytes(StandardCharsets.UTF_8));
            md5Bytes = md5.digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return md5Bytes;
    }

    public Map<String,String> convert2Headers(){
        Map<String,String> headers = new HashMap<>();
        if(xTsignOpenAppId != null) {
            headers.put("X-Tsign-Open-App-Id", xTsignOpenAppId);
        }
        if(token != null) {
            headers.put("X-Tsign-Open-Token", token);
        }
        if(contentType != null) {
            headers.put("Content-Type", contentType);
        }
        if(!StringUtils.isBlank(contentMd5)) {
            headers.put("Content-MD5", contentMd5);
        }
        return headers;
    }
}
