package vip.xiaonuo.biz.core.sign.interceptor;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import vip.xiaonuo.biz.core.sign.utils.GenerateSignatureUtil;
import vip.xiaonuo.biz.core.sign.utils.ServletUtils;
import vip.xiaonuo.biz.modular.openinterface.entity.BizOpenInterface;
import vip.xiaonuo.biz.modular.openinterface.service.BizOpenInterfaceService;
import vip.xiaonuo.common.cache.CommonCacheOperator;
import vip.xiaonuo.common.pojo.CommonResult;
import vip.xiaonuo.dev.api.DevConfigApi;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author 徐超
 * @Date 2021/4/20 15:53
 * @Version 1.0
 */
@Slf4j
public class SignAuthInterceptor implements HandlerInterceptor {

    private static final String NONCE_KEY_STR = "nonce:";

    private static final String OpenInterfaceKey = "open-interface-";

    private static final int apiExpireTime = 120;

    @Resource
    private BizOpenInterfaceService bizOpenInterfaceService;

    @Resource
    private CommonCacheOperator commonCacheOperator;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        /***
         * 1、获取请求参数appId
         */
        String appKey = request.getHeader("appKey");
        if (StringUtils.isBlank(appKey)) {
            log.info("appKey不能为空");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("appKey不能为空")));
            return false;
        }

        /***
         * 2、获取请求参数secret
         */
        String secret = request.getHeader("appSecret");
        if (StringUtils.isBlank(secret)){
            log.info("appSecret不能为空...........");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("appSecret不能为空")));
            return false;
        }

        /***
         * 3、 获取请求参数timestamp 时间戳
         */
        String timestamp = request.getHeader("timestamp");
        if (StringUtils.isBlank(timestamp)){
            log.info("timestamp不能为空...........");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("timestamp不能为空")));
            return false;
        }

        /** 4、 防止过期时间的提交
         *  从前端传递的timestamp 与服务器端当前系统时间之差大于120s，则此次请求的timestamp无效
         *  留出短时间考虑网络问题提交速度慢，若时间过长中间时间足以挟持篡改参数，所以折中考虑了120秒
         */
        Long time = System.currentTimeMillis()/1000;
        if (Math.abs(Long.valueOf(timestamp)-time) > apiExpireTime) {
            log.info("timestamp失效...........");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("timestamp失效")));
            return false;
        }

        /**
         *  5、获取请求参数nonce随机数，防止重复的暴力请求
         *  流程：1、获取当前提交的随机数，作为key前往redis 查询，若有值则为重复提交
         *       2、redis中查询不到结果，将当前随机数作为key，value为随机数，过期时间设置为120s
         */
        String nonce = request.getHeader("nonce");
        log.info("nonce:"+nonce);
        if (StringUtils.isBlank(nonce)) {
            log.info("nonce不能为空...........");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("随机数不能为空")));
            return false;
        } else {
            // 从缓存中取
            Object cacheValue = commonCacheOperator.get(NONCE_KEY_STR+appKey+"-"+nonce);
            if(ObjectUtil.isNotNull(cacheValue)) {
                String redisNonce =  Convert.toStr(cacheValue);
                log.info("redisNonce:"+redisNonce);
                log.info("缓存中nonce已存在，请勿重复提交...........");
                ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("随机数已存在，请勿重复提交")));
                return false;
            }else{
                log.info("redisNonce:"+nonce);
                log.info("缓存nonce..........."+NONCE_KEY_STR+appKey+"-"+nonce);
                commonCacheOperator.put(NONCE_KEY_STR+appKey+"-"+nonce, nonce, 120);
            }
        }

        /** 6、验证secret权限、来源是否合法
         *  此处可以用appId,条件为 已开启，未封禁等进行数据库合作机构表查询，有可能已经终止合作禁止了此有用访问,
         *  业务上达到一定条件的可以根据appId分配权限，选择不同的接口能力进行开放
         */
        BizOpenInterface openInterface;
        String openInterfaceStr = null;
        Object openInterfaceValue = commonCacheOperator.get(OpenInterfaceKey + appKey);
        if(ObjectUtil.isNotNull(openInterfaceValue)) {
            openInterfaceStr = Convert.toStr(openInterfaceValue);
        }
        if (ObjectUtil.isNotEmpty(openInterfaceStr)){
            ObjectMapper mapper = new ObjectMapper();
            openInterface = mapper.readValue(openInterfaceStr,BizOpenInterface.class);
        }else{
            openInterface = bizOpenInterfaceService.getOne(new QueryWrapper<BizOpenInterface>().eq("app_key",appKey));
            if (ObjectUtil.isNotEmpty(openInterface)) {
                commonCacheOperator.put(OpenInterfaceKey + appKey, JSON.toJSONString(openInterface));
            }
        }
        if (openInterface == null || !openInterface.getStatus().equals("ENABLE")) {
            log.info("appKey无法查询到合作项目信息或已被封禁...........");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("无法查询到appKey为【"+appKey+"】的应用信息或应用已关闭，请联系管理员")));
            return false;
        }

        /***
         * 获取secret 与数据库值对比，判断请求来源是否合法
         */
        if (!secret.equals(openInterface.getAppSecret())) {
            log.info("appSecret与接口提供方不一致...........");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("appSecret与接口提供方不一致")));
            return false;
        }

        /***
         * 7、获取请求sign签名参数
         */
        String sign = request.getHeader("sign");
        if (StringUtils.isBlank(sign)){
            log.info("sign不能为空...........");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("sign不能为空")));
            return false;
        }

        /***
         * 8、通过后台MD5重新签名校验与前端签名sign值比对，确认当前请求数据是否被篡改
         */
        // 从数组中取出参数放入Map中
        Map<String,String> param = new ConcurrentHashMap<>(5);
        param.put("appKey",request.getHeader("appKey"));
        param.put("appSecret",request.getHeader("appSecret"));
        param.put("timestamp",request.getHeader("timestamp"));
        param.put("nonce",request.getHeader("nonce"));
        param.put("sign",request.getHeader("sign"));
        boolean reuslt = GenerateSignatureUtil.isSignatureValid(param, secret);
        if (!reuslt){
            log.debug("sign签名校验失败...........");
            ServletUtils.renderString(response, JSON.toJSONString(CommonResult.error("sign签名校验失败")));
            return false;
        }
        log.info("签名校验通过，放行...........");
        /***
         *  获取sign签名，与服务端生成的sign 签名对比
         */
        log.info("sign ====================");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("SignAuthInterceptor postHandle======  ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("SignAuthInterceptor afterCompletion======  ");
    }
}
