package com.mosquito.project.interceptor;

import com.mosquito.project.exception.RateLimitExceededException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * 分布式速率限制拦截器
 * 生产环境强制使用Redis进行限流
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RateLimitInterceptor implements HandlerInterceptor {

    private final RedisTemplate<String, Object> redisTemplate;
    
    @Value("${app.rate-limit.per-minute:100}")
    private int perMinuteLimit;
    
    @Value("${app.rate-limit.window-size:1}")
    private int windowSizeMinutes;
    
    @Value("${spring.profiles.active:dev}")
    private String activeProfile;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 生产环境强制使用Redis
        if ("prod".equals(activeProfile)) {
            if (redisTemplate == null) {
                log.error("Production mode requires Redis for rate limiting, but Redis is not configured");
                throw new IllegalStateException("Production环境必须配置Redis进行速率限制");
            }
            return checkRateLimitWithRedis(request);
        } else {
            log.debug("Development mode: rate limiting using Redis (if available)");
            return checkRateLimitWithRedis(request);
        }
    }

    /**
     * 使用Redis进行分布式速率限制
     */
    private boolean checkRateLimitWithRedis(HttpServletRequest request) {
        String clientIp = getClientIp(request);
        String endpoint = request.getRequestURI();
        String key = String.format("rate_limit:%s:%s", clientIp, endpoint);
        
        try {
            // Redis原子操作：检查并设置
            Long currentCount = (Long) redisTemplate.opsForValue().increment(key);
            
            if (currentCount == 1) {
                // 第一次访问，设置过期时间
                redisTemplate.expire(key, windowSizeMinutes, TimeUnit.MINUTES);
                log.debug("Rate limit counter initialized for key: {}", key);
            }
            
            if (currentCount > perMinuteLimit) {
                log.warn("Rate limit exceeded for client: {}, endpoint: {}, count: {}", 
                    clientIp, endpoint, currentCount);
                throw new RateLimitExceededException(
                    String.format("请求过于频繁，请%d分钟后再试", windowSizeMinutes));
            }
            
            log.debug("Rate limit check passed for client: {}, count: {}", clientIp, currentCount);
            return true;
            
        } catch (Exception e) {
            log.error("Redis rate limiting failed, falling back to allow: {}", e.getMessage());
            // Redis故障时允许请求通过，但记录警告
            return true;
        }
    }

    /**
     * 获取客户端真实IP地址
     */
    private String getClientIp(HttpServletRequest request) {
        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.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        
        // 处理多个IP的情况（X-Forwarded-For可能包含多个IP）
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        
        return ip != null ? ip : "unknown";
    }
}