package shop.alien.config.filter;

import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import shop.alien.config.redis.BaseRedisService;
import shop.alien.entity.result.R;
import shop.alien.util.common.JwtUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 防止重复提交拦截器
 * 
 * @author lingma
 * @version 1.0
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class NoRepeatSubmitInterceptor implements HandlerInterceptor {
    
    private final BaseRedisService baseRedisService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            NoRepeatSubmit noRepeatSubmit = method.getAnnotation(NoRepeatSubmit.class);
            
            if (noRepeatSubmit != null) {
                String key = generateKey(request, method);
                if (!lock(key, noRepeatSubmit)) {
                    // 重复提交
                    handleRepeatSubmit(response, noRepeatSubmit.message());
                    return false;
                }
            }
        }
        return true;
    }
    
    /**
     * 生成锁的键值
     * key 值组成： repeat_submit:方法所在类名：方法名：用户ID（如果已登录）或IP地址：请求URI的MD5摘
     * @param request 请求
     * @param method  方法
     * @return 键值
     */
    private String generateKey(HttpServletRequest request, Method method) {
        StringBuilder sb = new StringBuilder("repeat_submit:");
        sb.append(method.getDeclaringClass().getName());
        sb.append(":");
        sb.append(method.getName());
        sb.append(":");
        
        // 添加用户标识
        JSONObject userInfo = JwtUtil.getCurrentUserInfo();
        if (userInfo != null) {
            sb.append(userInfo.getInteger("userId"));
        } else {
            sb.append(request.getRemoteAddr());
        }
        
        // 添加请求参数
        sb.append(":");
        sb.append(DigestUtils.md5DigestAsHex(request.getRequestURI().getBytes()));
        
        return sb.toString();
    }
    
    /**
     * 加锁 分布式锁实现
     * 如果键不存在则设置成功并返回 true，否则返回 false
     * @param key 键
     * @param noRepeatSubmit 注解
     * @return 是否加锁成功
     */
    private boolean lock(String key, NoRepeatSubmit noRepeatSubmit) {
        try {
            // 使用Redis的SET命令的NX和EX选项实现分布式锁
            // 键的过期时间由 @NoRepeatSubmit 注解中的参数决定
            Boolean result = baseRedisService.setStringIfAbsent(key, "1", 
                noRepeatSubmit.timeUnit().toSeconds(noRepeatSubmit.expireTime()));
            return result != null && result;
        } catch (Exception e) {
            log.error("防重复提交加锁异常", e);
            return true; // 出现异常不阻止用户操作
        }
    }
    
    /**
     * 处理重复提交
     * 
     * @param response 响应
     * @param message 提示信息
     * @throws IOException IO异常
     */
    private void handleRepeatSubmit(HttpServletResponse response, String message) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        R<Object> fail = R.fail(message);
        writer.write(JSONObject.toJSONString(fail));
        writer.flush();
        writer.close();
    }
}