package cn.iocoder.yudao.module.server.utils;

import cn.iocoder.yudao.framework.common.exception.ServiceException;
import com.anji.captcha.service.CaptchaCacheService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
public class MultiRateLimitAspect {

    @Resource
    private CaptchaCacheService redisService;

    // Redis key前缀
    private static final String RATE_LIMIT_PREFIX = "rate:limit:multi:";

    // 时间格式
    private static final DateTimeFormatter MINUTE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
    private static final DateTimeFormatter HOUR_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHH");
    private static final DateTimeFormatter DAY_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");

    @Around("@annotation(multiRateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, MultiRateLimit multiRateLimit) throws Throwable {
        String identifier = getIdentifier(multiRateLimit.dimension());
        if (identifier == null || "unknown".equals(identifier)) {
            return joinPoint.proceed();
        }

        // 检查各级频率限制
        String minuteError = checkMinuteLimit(identifier, multiRateLimit);
        if (minuteError != null) {
            throw new ServiceException(10002, minuteError);
        }

        String hourError = checkHourLimit(identifier, multiRateLimit);
        if (hourError != null) {
            throw new ServiceException(10002, hourError);
        }

        String dayError = checkDayLimit(identifier, multiRateLimit);
        if (dayError != null) {
            throw new ServiceException(10002, dayError);
        }

        // 记录本次请求
        recordRequest(identifier, multiRateLimit);

        try {
            return joinPoint.proceed();
        } catch (Exception e) {
            // 如果业务执行失败，可以考虑回滚计数（可选）
            log.error("业务执行失败", e);
            throw e;
        }
    }

    /**
     * 检查1分钟频率限制
     */
    private String checkMinuteLimit(String identifier, MultiRateLimit multiRateLimit) {
        String currentMinute = LocalDateTime.now().format(MINUTE_FORMAT);
        String minuteKey = buildKey(identifier, "minute", currentMinute);

        String countStr = redisService.get(minuteKey);
        int minuteCount = (countStr == null) ? 0 : Integer.parseInt(countStr);

        if (minuteCount >= multiRateLimit.minuteLimit()) {
            // 计算下一分钟的时间
            LocalDateTime nextMinute = LocalDateTime.now().plusMinutes(1)
                    .withSecond(0)
                    .withNano(0);
            long waitSeconds = java.time.Duration.between(LocalDateTime.now(), nextMinute).getSeconds();
            waitSeconds = Math.max(waitSeconds, 1); // 最少1秒

            return String.format("1分钟内请求次数已达上限（%d次），请等待 %d 秒后重试",
                    multiRateLimit.minuteLimit(), waitSeconds);
        }

        return null;
    }

    /**
     * 检查1小时频率限制
     */
    private String checkHourLimit(String identifier, MultiRateLimit multiRateLimit) {
        String currentHour = LocalDateTime.now().format(HOUR_FORMAT);
        String hourKey = buildKey(identifier, "hour", currentHour);

        String countStr = redisService.get(hourKey);
        int hourCount = (countStr == null) ? 0 : Integer.parseInt(countStr);

        if (hourCount >= multiRateLimit.hourLimit()) {
            // 计算下一小时的时间
            LocalDateTime nextHour = LocalDateTime.now().plusHours(1)
                    .withMinute(0)
                    .withSecond(0)
                    .withNano(0);
            long waitMinutes = java.time.Duration.between(LocalDateTime.now(), nextHour).toMinutes();
            waitMinutes = Math.max(waitMinutes, 1); // 最少1分钟

            return String.format("1小时内请求次数已达上限（%d次），请等待 %d 分钟后重试",
                    multiRateLimit.hourLimit(), waitMinutes);
        }

        return null;
    }

    /**
     * 检查24小时频率限制
     */
    private String checkDayLimit(String identifier, MultiRateLimit multiRateLimit) {
        String currentDay = LocalDateTime.now().format(DAY_FORMAT);
        String dayKey = buildKey(identifier, "day", currentDay);

        String countStr = redisService.get(dayKey);
        int dayCount = (countStr == null) ? 0 : Integer.parseInt(countStr);

        if (dayCount >= multiRateLimit.dayLimit()) {
            // 计算明天的时间
            LocalDateTime tomorrow = LocalDateTime.now().plusDays(1)
                    .withHour(0)
                    .withMinute(0)
                    .withSecond(0)
                    .withNano(0);
            long waitHours = java.time.Duration.between(LocalDateTime.now(), tomorrow).toHours();
            waitHours = Math.max(waitHours, 1); // 最少1小时

            return String.format("24小时内请求次数已达上限（%d次），请等待 %d 小时后重试",
                    multiRateLimit.dayLimit(), waitHours);
        }

        return null;
    }

    /**
     * 记录请求计数
     */
    private void recordRequest(String identifier, MultiRateLimit multiRateLimit) {
        LocalDateTime now = LocalDateTime.now();

        // 1分钟计数
        String minuteKey = buildKey(identifier, "minute", now.format(MINUTE_FORMAT));
        Long minuteCount = redisService.increment(minuteKey, 1);
        if (minuteCount == 1) {
            // 第一次设置时，设置过期时间（1分钟 + 5秒缓冲）
            redisService.set(minuteKey, "1", 65); // 65秒过期
        } else if (minuteCount > 1) {
            // 更新计数，但保持过期时间
            redisService.set(minuteKey, String.valueOf(minuteCount), 65);
        }

        // 1小时计数
        String hourKey = buildKey(identifier, "hour", now.format(HOUR_FORMAT));
        Long hourCount = redisService.increment(hourKey, 1);
        if (hourCount == 1) {
            // 第一次设置时，设置过期时间（1小时 + 5分钟缓冲）
            redisService.set(hourKey, "1", 3900); // 65分钟过期
        } else if (hourCount > 1) {
            redisService.set(hourKey, String.valueOf(hourCount), 3900);
        }

        // 24小时计数
        String dayKey = buildKey(identifier, "day", now.format(DAY_FORMAT));
        Long dayCount = redisService.increment(dayKey, 1);
        if (dayCount == 1) {
            // 第一次设置时，设置过期时间（24小时 + 1小时缓冲）
            redisService.set(dayKey, "1", 90000); // 25小时过期
        } else if (dayCount > 1) {
            redisService.set(dayKey, String.valueOf(dayCount), 90000);
        }

        log.debug("记录请求计数 - IP: {}, 分钟计数: {}, 小时计数: {}, 天计数: {}",
                identifier, minuteCount, hourCount, dayCount);
    }

    /**
     * 构建Redis key
     */
    private String buildKey(String identifier, String level, String timeSlot) {
        return RATE_LIMIT_PREFIX + identifier + ":" + level + ":" + timeSlot;
    }

    /**
     * 获取标识符（IP或用户ID）
     */
    private String getIdentifier(String dimension) {
        if ("IP".equals(dimension)) {
            return getClientIp();
        }
        return "unknown";
    }

    /**
     * 获取客户端IP（简化版）
     */
    private String getClientIp() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return "unknown";
            }

            HttpServletRequest request = attributes.getRequest();
            String ip = request.getHeader("X-Forwarded-For");

            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }

            // 多个IP时取第一个
            if (ip != null && ip.contains(",")) {
                ip = ip.split(",")[0].trim();
            }

            // 处理本地IP
            if ("0:0:0:0:0:0:0:1".equals(ip) || "127.0.0.1".equals(ip)) {
                ip = "127.0.0.1";
            }

            return ip == null ? "unknown" : ip;
        } catch (Exception e) {
            log.warn("获取客户端IP失败", e);
            return "unknown";
        }
    }
}