package com.changhu.interceptor;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.changhu.common.db.enums.IsEnable;
import com.changhu.common.exception.MessageException;
import com.changhu.common.utils.IpUtil;
import com.changhu.filter.CustomHttpServletRequestWrapper;
import com.changhu.pojo.entity.AccessKeys;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 20252
 * @createTime 2024/11/19 下午1:58
 * @desc 开放接口认证拦截器
 */
@Slf4j
public class OpenApiAuthInterceptor implements HandlerInterceptor {

    private static final String ACCESS_KEY = "Access-Key";//调用者身份唯一标识
    private static final String TIMESTAMP = "Time-Stamp";//时间戳
    private static final String SIGN = "Sign";//签名

    @Override
    public boolean preHandle(@NotNull HttpServletRequest request,
                             @NotNull HttpServletResponse response,
                             @NotNull Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            String ip = IpUtil.getIp(request);
            try {
                return checkSign(request);
            } catch (MessageException e) {
                log.error("开放接口访问失败:{} 访问时间:{} IP:{} 访问接口:{} ", e.getMessage(), LocalDateTime.now(), ip, request.getRequestURI());
                throw e;
            }
        }
        return false;
    }

    private boolean checkSign(HttpServletRequest request) throws MessageException {

        String accessKey = request.getHeader(ACCESS_KEY);
        String timestamp = request.getHeader(TIMESTAMP);
        String sign = request.getHeader(SIGN);

        if (StrUtil.isBlank(accessKey) || StrUtil.isBlank(timestamp) || StrUtil.isBlank(sign)) {
            throw new MessageException("请求体缺失");
        }

        AccessKeys accessKeyEntity = Db.lambdaQuery(AccessKeys.class)
                .eq(AccessKeys::getAccessKey, accessKey)
                .oneOpt()
                .orElseThrow(() -> new MessageException("无效的accessKey"));

        if (IsEnable.FALSE.equals(accessKeyEntity.getIsEnable())) {
            throw new MessageException("accessKey已禁用");
        }

        if (accessKeyEntity.getEffectiveTime() > 0 && System.currentTimeMillis() - Long.parseLong(timestamp) > accessKeyEntity.getEffectiveTime()) {
            throw new MessageException("请求已过期");
        }

        List<String> allowedResources = Optional.ofNullable(accessKeyEntity.getAllowedResources()).orElseThrow(() -> new MessageException("暂无允许访问的资源"));
        if (!allowedResources.contains(request.getRequestURI())) {
            throw new MessageException("无效的请求资源");
        }

        Map<String, Object> hashMap = new HashMap<>();
        hashMap.put(ACCESS_KEY, accessKey);
        hashMap.put(TIMESTAMP, timestamp);
        //添加请求url参数
        Optional.ofNullable(HttpUtil.decodeParamMap(request.getQueryString(), StandardCharsets.UTF_8))
                .ifPresent(hashMap::putAll);
        //添加body参数
        Optional.ofNullable(JSON.<Map<String, Object>>parseObject(((CustomHttpServletRequestWrapper) request).getBody(), Map.class))
                .ifPresent(hashMap::putAll);
        //生成签名
        String nowSign = generatedSign(hashMap, accessKeyEntity.getSecretKey());
        //比对签名
        if (!sign.equals(nowSign)) {
            throw new MessageException("签名错误");
        }
        return true;
    }

    /**
     * 获取签名
     *
     * @param map       参数结果
     * @param secretKey 密钥
     * @return 签名字符串
     */
    private String generatedSign(Map<String, Object> map, String secretKey) {
        List<Map.Entry<String, Object>> entries = new ArrayList<>(map.entrySet());
        String str = entries.stream()
                .filter(en -> null == en.getValue() || StrUtil.isNotBlank(en.getValue().toString()))
                .sorted(Comparator.comparing(o -> o.getKey().toLowerCase()))
                .map(en -> StrUtil.format("{}={}", en.getKey(), URLUtil.encodeAll(en.getValue() + "")))
                .collect(Collectors.joining("&"));
        str += ("&Secret-Key=" + secretKey);
        return MD5.create().digestHex(str).toUpperCase();
    }

}
