package com.jeesharp.business.modules.pay.weixinpay.v3.auth;

import java.io.IOException;
import java.net.URI;
import java.security.SecureRandom;

import com.jeesharp.business.modules.pay.weixinpay.v3.Credentials;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * 签名生成
 * 微信支付API v3要求商户对请求进行签名。微信支付会在收到请求后进行签名的验证。如果签名验证不通过，微信支付API v3将会拒绝处理请求，并返回401 Unauthorized。
 *
 * @author qiyuan
 *
 */
@Slf4j
public class WxPayCredentials implements Credentials {
    private static final String SYMBOLS =
            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final SecureRandom RANDOM = new SecureRandom();
    protected String merchantId;
    protected Signer signer;

    public WxPayCredentials(String merchantId, Signer signer) {
        this.merchantId = merchantId;
        this.signer = signer;
    }

    public String getMerchantId() {
        return merchantId;
    }

    protected long generateTimestamp() {
        return System.currentTimeMillis() / 1000;
    }

    protected String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }

    /**
     *  认证类型
     *
     * 微信支付商户API v3要求请求通过HTTPAuthorization头来传递签名。Authorization由认证类型和签名信息两个部分组成。
     * Authorization: 认证类型 签名信息
     *      具体组成为：
     *          认证类型，目前为WECHATPAY2-SHA256-RSA2048
     *      签名信息
     *          发起请求的商户（包括直连商户、服务商或渠道商）的商户号mchid
     *          商户API证书序列号serial_no，用于声明所使用的证书
     *          请求随机串nonce_str
     *          时间戳timestamp
     *          签名值signature
     * 注：以上五项签名信息，无顺序要求。
     * Authorization头的示例如下：（注意，示例因为排版可能存在换行，实际数据应在一行）
     *      Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900009191",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"
     * @return
     */
    @Override
    public final String getSchema() {
        return "WECHATPAY2-SHA256-RSA2048";
    }

    /**
     * 签名信息
     *
     * @param request
     * @return
     * @throws IOException
     */
    @Override
    public final String getToken(HttpUriRequest request) throws IOException {
        String nonceStr = generateNonceStr();
        long timestamp = generateTimestamp();

        String message = buildMessage(nonceStr, timestamp, request);
        log.debug("authorization message=[{}]", message);

        Signer.SignatureResult signature = signer.sign(message.getBytes("utf-8"));

        String token = "mchid=\"" + getMerchantId() + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + signature.certificateSerialNumber + "\","
                + "signature=\"" + signature.sign + "\"";
        log.debug("authorization token=[{}]", token);

        return token;
    }

    /**
     * 构造的请求签名串为：
     * @param nonce
     * @param timestamp
     * @param request
     * @return
     * @throws IOException
     */
    protected final String buildMessage(String nonce, long timestamp, HttpUriRequest request)
            throws IOException {
        URI uri = request.getURI();
        String canonicalUrl = uri.getRawPath();
        if (uri.getQuery() != null) {
            canonicalUrl += "?" + uri.getRawQuery();
        }

        String body = "";
        // PATCH,POST,PUT
        if (request instanceof HttpEntityEnclosingRequestBase) {
            body = EntityUtils.toString(((HttpEntityEnclosingRequestBase) request).getEntity());
        }

        return request.getRequestLine().getMethod() + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonce + "\n"
                + body + "\n";
    }

}
