package com.mmc.iuav.user.config;

import cn.hutool.core.lang.Assert;
import com.alibaba.fastjson2.JSONObject;
import com.mmc.iuav.response.ResultBody;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DigestUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author 23214
 */
@Aspect
@Component
@Slf4j
public class PreventDuplicationAspect {
    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.mmc.iuav.user.config.PreventDuplication)")
    public void preventDuplication() {
    }

    // 使用@Around注解定义一个环绕通知，拦截带有@PreventDuplication注解的方法
    @Around("preventDuplication()")
    public Object before(ProceedingJoinPoint joinPoint) {
        // 获取当前请求的属性
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        // 获取当前请求
        HttpServletRequest request = attributes.getRequest();
        // 确保请求不为空
        Assert.notNull(request, "request cannot be null.");

        // 获取被拦截方法的签名，并转换为Method对象
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 获取被拦截方法上的@PreventDuplication注解
        PreventDuplication annotation = method.getAnnotation(PreventDuplication.class);

        // 从请求头中获取token
        String token = request.getHeader("token");
        // 构造Redis的键，由前缀、token和方法签名组成
        String redisKey = "prevent_duplication_prefix".concat(token).concat(getMethodSign(method, joinPoint.getArgs()));
        // 构造Redis的值，由键、注解的值和一个固定字符串组成
        String redisValue = redisKey.concat(annotation.value()).concat("submit duplication");

        // 检查Redis中是否已经存在该键
        if (!redisTemplate.hasKey(redisKey)) {
            // 如果不存在，则将键值对存入Redis，并设置过期时间
            redisTemplate.opsForValue().set(redisKey, redisValue, annotation.expireSeconds(), TimeUnit.SECONDS);
            try {
                // 执行被拦截的方法
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                // 如果执行过程中发生异常，则从Redis中删除该键值对，并抛出异常
                redisTemplate.delete(redisKey);
                return ResultBody.error(throwable.getMessage());
            }
        } else {
            // 如果已经存在，则返回错误信息，防止重复提交
            return ResultBody.error("请勿重复提交");
        }
    }

    // 定义一个私有方法，用于获取方法的签名，包括方法名和参数列表
    private String getMethodSign(Method method, Object... args) {
        StringBuilder sb = new StringBuilder(method.toString());
        for (Object arg : args) {
            sb.append(toString(arg));
        }
        return DigestUtils.sha1DigestAsHex(sb.toString());
    }

    // 定义一个私有方法，用于将参数转换为字符串
    private String toString(Object arg) {
        if (Objects.isNull(arg)) {
            return "null";
        }
        if (arg instanceof Number) {
            return arg.toString();
        }
        return JSONObject.toJSONString(arg);
    }
}
