package com.fkzy.warn.common.conf;

import com.fkzy.warn.common.response.ApiResponse;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 返回值为 {@link ApiResponse} 的 Controller 幂等性请求切面
 */
//@Aspect
//@Component
public class IdempotentControllerAspect {
    private static Logger LOGGER = LoggerFactory.getLogger(IdempotentControllerAspect.class);

    private final ConcurrentHashMap<String, Long> requests = new ConcurrentHashMap<>();

    @Pointcut("execution(public * com.fxzy.warn.controller..*(..)) && @annotation(com.fkzy.warn.common.conf.Idempotent)")
    public void idempotentPointCut() {}

    @Around("idempotentPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 获取注解配置
        Idempotent idempotent = null;
        // 参数值列表
        Object[] args = null;
        String sign = null;
        boolean error = false;
        try {
            Signature signature = point.getSignature();
            Class<?> type = signature.getDeclaringType();
            String method = signature.getName();
            // 参数类型列表
            Class<?>[] paramTypes = ((MethodSignature) signature).getParameterTypes();
            // 获取原始方法上的 MapCache 注解
            Method declaredMethod = type.getDeclaredMethod(method, paramTypes);
            idempotent = declaredMethod.getAnnotation(Idempotent.class);
            args = point.getArgs();
            sign = getProcessSign(type, method, args);
            // LOGGER.info("ASPECT ... sign={}", sign);
        } catch (Exception e) {
            error = true;
            LOGGER.error("切面反射错误：", e);
        }
        if (idempotent == null || args == null || sign == null || error) {
            return point.proceed();
        }
        Long lastBeginTime = requests.get(sign);
        long now = System.currentTimeMillis();
        boolean canPass = false;
        // 这里可改为 redis 锁
        synchronized (sign) {
            canPass = requests.get(sign) == null
                    || (idempotent.interval() > 0 && now - lastBeginTime > idempotent.interval());
            if (canPass) {
                requests.put(sign, now);
            }
        }
        if (canPass) {
            try {
                return point.proceed();
            } finally {
                // 可能执行时间过了 interval 被别的请求更新了
                if (requests.get(sign) != null && requests.get(sign) == now) {
                    requests.remove(sign);
                }
            }
        } else {
            // 频繁提交不可执行
            ApiResponse res = new ApiResponse();
            res.recordError(idempotent.message());
            return res;
        }
    }

    private String getProcessSign(Class<?> type, String method, Object[] args) {
        StringBuilder sign = new StringBuilder();
        sign.append(type.getName())
                .append('.')
                .append(method);
        sign.append('(');
        for (int i = 0; i < args.length; i++) {
            sign.append(args[i]).append(",");
        }
        sign.append(')');
        return sign.toString().intern();
    }

}
