package com.skr.mdm.util;

import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.digest.DigestUtil;
import com.skr.mdm.authentication.admin.AdminTimeCheckException;
import com.skr.mdm.authentication.admin.IllegalRequestException;
import com.skr.mdm.authentication.user.IllegalUserRequestException;
import com.skr.mdm.authentication.user.UserTimeCheckException;
import com.skr.mdm.constant.RequestHeaderConstants;
import com.skr.mdm.enums.MdmEncryptEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author nfq
 * @date 2020/7/22 17:17
 */
@Component
@Slf4j
public class AntiReplayUtil {

    private final String nonceParameter = RequestHeaderConstants.REQUEST_HEADER_NONCE;
    private final String timestampHeaderParameter = RequestHeaderConstants.REQUEST_HEADER_TIMESTAMP;
    private final String signParameter = RequestHeaderConstants.REQUEST_HEADER_SIGN;
    private final String androidParameter = RequestHeaderConstants.REQUEST_HEADER_USER_AGENT;
    private final String csrfToken = RequestHeaderConstants.REQUEST_HEADER_CSRF_TOKEN;
    @Value("${request.expire.seconds}")
    private Long requestExpire;
    @Value("${request.secret}")
    private String secret;

    @Value("${mdm.encrypt}")
    private String mdmEncrypt;
    @Autowired
    private RedisUtils redisUtils;

    /**
     * 防重放检查
     *
     * @param request
     */
    public void userAntiReplay(HttpServletRequest request) {
        String secretKey = request.getHeader("secretKey");
        log.debug("secretKey:{}", secretKey);
        String nonce = request.getHeader(this.nonceParameter);
        String timestampParameter = request.getHeader(this.timestampHeaderParameter);
        String sign = request.getHeader(this.signParameter);
        String android = request.getHeader(this.androidParameter);
        boolean checkParam = StringUtils.isBlank(nonce) || StringUtils.isBlank(timestampParameter) ||
                StringUtils.isBlank(sign) || StringUtils.isBlank(android);
        if (checkParam) {
            log.warn("请求头内容不完整 timestampParameter:{} sign:{} nonce:{}, android:{}  请求地址:{}", timestampParameter, sign, nonce, android, request.getRequestURI());
            throw new IllegalUserRequestException();
        }
        if (!StringUtils.equalsIgnoreCase("android", android)) {
            log.warn("非安卓请求:{} 请求地址:{}", android, request.getRequestURI());
            throw new IllegalUserRequestException();
        }

        long timestamp = Long.parseLong(timestampParameter);
        long expireTime = requestExpire * 1000L;
        boolean flag = timestamp > System.currentTimeMillis() && (timestamp - System.currentTimeMillis()) > requestExpire * 1000;
        if (flag || (System.currentTimeMillis() - timestamp) > expireTime) {
            log.warn("请求时间异常：{} 服务器时间：{} 请求地址:{}", timestamp, System.currentTimeMillis(), request.getRequestURI());
            throw new UserTimeCheckException();
        }
        // SM解密
        if (MdmEncryptEnum.SM.getMdmEncrypt().equals(mdmEncrypt)) {
            TreeMap<String, Object> treeMap = new TreeMap<>();
            Map<String, String[]> parameterMap = request.getParameterMap();
            log.debug("parameterMap:{}", parameterMap);
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                String[] values = entry.getValue();
                for (String value : values) {
                    log.debug("value:{}", value);
                    treeMap.put(entry.getKey(), value);
                }
            }
            log.debug("treeMap:{}", treeMap);
            StringBuffer stringBuffer = new StringBuffer();
            treeMap.forEach((k, v) -> {
                stringBuffer.append(k);
                stringBuffer.append(v);
            });
            log.debug("stringBuffer:{}", stringBuffer);
            String data = timestamp + nonce + secretKey + stringBuffer;
            log.debug("data:{}", data);
            String sm3 = SmUtil.sm3(data);
            log.debug("sm3:{}", sm3);
            SM2 sm2 = SmUtils.sm2Crypt();
            if (!sm2.verifyHex(sm3, sign)) {
                log.warn("签名错误:{} md5Hex:{} 请求地址:{}", sign, sm3, request.getRequestURI());
                throw new IllegalUserRequestException();
            }
        } else {
            String data = timestamp + nonce + secret;
            String md5Hex = DigestUtil.md5Hex(data);
            if (!md5Hex.equals(sign)) {
                // 请求被篡改，拒绝访问
                log.warn("签名错误:{} md5Hex:{} 请求地址:{}", sign, md5Hex, request.getRequestURI());
                throw new IllegalUserRequestException();
            }
        }

        // 校验随机数
        if (redisUtils.get(RedisGetKeyUtil.getAdminQuestNonceKey(nonce)) != null) {
            // 随机数重复，拒绝访问
            log.warn("随机数重复:{} 请求地址:{}", nonce, request.getRequestURI());
            throw new IllegalUserRequestException();
        }
        // 随机值放入缓存
        redisUtils.set(RedisGetKeyUtil.getAdminQuestNonceKey(nonce), nonce, requestExpire);
    }

    public void adminAntiReplay(HttpServletRequest request) {
        String nonce = request.getHeader(this.nonceParameter);
        String csrfToken = request.getHeader(this.csrfToken);
        String timestampParameter = request.getHeader(this.timestampHeaderParameter);
        String sign = request.getHeader(this.signParameter);
        if (StringUtils.isBlank(csrfToken) || StringUtils.isBlank(timestampParameter) || StringUtils.isBlank(sign) || StringUtils.isBlank(nonce)) {
            log.warn("请求头内容不完整 csrfToken:{} timestampParameter:{} sign:{} nonce:{} 请求地址:{}", csrfToken, timestampParameter, sign, nonce, request.getRequestURI());
            throw new IllegalRequestException();
        }
        long timestamp = Long.parseLong(timestampParameter);
        long expireTime = requestExpire * 1000L;
        boolean flag = timestamp > System.currentTimeMillis() && (timestamp - System.currentTimeMillis()) > expireTime;
        if (flag || (System.currentTimeMillis() - timestamp) > expireTime) {
            log.warn("请求时间异常：{} 服务器时间：{} 请求地址：{}", timestamp, System.currentTimeMillis(), request.getRequestURI());
            throw new AdminTimeCheckException();
        }
        // 校验签名
        String data = timestamp + nonce + secret;
        String md5Hex = DigestUtil.md5Hex(data);
        if (!md5Hex.equals(sign)) {
            // 请求被篡改，拒绝访问
            log.warn("签名错误:{} data:{}  请求地址:{}", sign, data, request.getRequestURI());
            throw new IllegalRequestException();
        }
        // 校验随机数
        if (redisUtils.get(RedisGetKeyUtil.getAdminQuestNonceKey(nonce)) != null) {
            // 随机数重复，拒绝访问
            log.warn("随机数重复:{} 请求地址:{}", nonce, request.getRequestURI());
            throw new IllegalRequestException();
        }
        // 随机值放入缓存
        redisUtils.set(RedisGetKeyUtil.getAdminQuestNonceKey(nonce), nonce, requestExpire);
    }
}
