package com.test.bijihoudaun.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.bijihoudaun.common.response.R;
import com.test.bijihoudaun.common.response.ResultCode;
import com.test.bijihoudaun.util.MemoryProtector;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 限流拦截器 - 支持按 IP 和按用户双重限流，使用 ConcurrentHashMap 提高并发性能
 * 带定期清理机制防止内存泄漏
 */
@Slf4j
public class RateLimitInterceptor implements HandlerInterceptor {

    // 普通接口限流配置
    private static final int MAX_REQUESTS_PER_MINUTE = 60;
    private static final int MAX_REQUESTS_PER_MINUTE_USER = 100;
    // 登录接口限流配置
    private static final int MAX_LOGIN_REQUESTS_PER_MINUTE = 5;
    private static final int MAX_LOGIN_REQUESTS_PER_MINUTE_USER = 10;
    // 时间窗口（毫秒）
    private static final long WINDOW_SIZE_MS = 60_000;
    // 清理间隔（毫秒）：每5分钟清理一次
    private static final long CLEANUP_INTERVAL_MS = 5 * 60 * 1000;

    // 使用 ConcurrentHashMap + 原子操作，避免锁竞争
    private static final ConcurrentHashMap<String, RequestCounter> ipCounters = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, RequestCounter> ipLoginCounters = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, RequestCounter> userCounters = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, RequestCounter> userLoginCounters = new ConcurrentHashMap<>();

    // 定期清理线程池
    private ScheduledExecutorService cleanupScheduler;

    private static class RequestCounter {
        private final AtomicInteger count = new AtomicInteger(0);
        private volatile long windowStart;
        // 记录最后访问时间，用于清理
        private volatile long lastAccessTime;

        RequestCounter() {
            this.windowStart = System.currentTimeMillis();
            this.lastAccessTime = System.currentTimeMillis();
        }

        boolean incrementAndCheck(int maxRequests) {
            long now = System.currentTimeMillis();
            lastAccessTime = now;

            synchronized (this) {
                if (now - windowStart > WINDOW_SIZE_MS) {
                    // 进入新窗口
                    windowStart = now;
                    count.set(1);
                    return true;
                }
                int currentCount = count.incrementAndGet();
                return currentCount <= maxRequests;
            }
        }

        /**
         * 检查是否过期（超过2个时间窗口没有访问）
         */
        boolean isExpired() {
            return System.currentTimeMillis() - lastAccessTime > WINDOW_SIZE_MS * 2;
        }
    }

    @PostConstruct
    public void init() {
        // 启动定期清理任务
        cleanupScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "rate-limit-cleanup");
            t.setDaemon(true);
            return t;
        });
        cleanupScheduler.scheduleAtFixedRate(this::cleanupExpiredCounters,
                CLEANUP_INTERVAL_MS, CLEANUP_INTERVAL_MS, TimeUnit.MILLISECONDS);
        log.info("限流拦截器初始化完成，清理间隔：{}分钟", CLEANUP_INTERVAL_MS / 60000);
    }

    @PreDestroy
    public void destroy() {
        // 关闭清理线程池
        if (cleanupScheduler != null && !cleanupScheduler.isShutdown()) {
            cleanupScheduler.shutdown();
            try {
                if (!cleanupScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                    cleanupScheduler.shutdownNow();
                }
            } catch (InterruptedException e) {
                cleanupScheduler.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
        log.info("限流拦截器已销毁");
    }

    /**
     * 清理过期的计数器
     */
    private void cleanupExpiredCounters() {
        try {
            int removed = 0;
            removed += cleanupMap(ipCounters);
            removed += cleanupMap(ipLoginCounters);
            removed += cleanupMap(userCounters);
            removed += cleanupMap(userLoginCounters);
            if (removed > 0) {
                log.debug("限流计数器清理完成，移除 {} 个过期条目", removed);
            }
        } catch (Exception e) {
            log.error("限流计数器清理失败", e);
        }
    }

    /**
     * 清理单个 Map 中的过期条目
     * 使用 removeIf 方法避免并发修改异常
     */
    private int cleanupMap(ConcurrentHashMap<String, RequestCounter> map) {
        int sizeBefore = map.size();
        // 使用 ConcurrentHashMap 的 removeIf 方法，线程安全
        map.entrySet().removeIf(entry -> entry.getValue().isExpired());
        return sizeBefore - map.size();
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 检查内存状态
        if (MemoryProtector.isMemoryInsufficient()) {
            log.warn("内存不足，拒绝请求: {}", request.getRequestURI());
            MemoryProtector.writeMemoryInsufficientResponse(response);
            return false;
        }

        String clientIp = getClientIp(request);
        String requestUri = request.getRequestURI();
        String username = getCurrentUsername();

        boolean isLoginRequest = "/api/user/login".equals(requestUri);

        // 1. 检查 IP 限流
        if (!checkIpLimit(clientIp, isLoginRequest, response)) {
            return false;
        }

        // 2. 检查用户限流（仅对已登录用户）
        if (username != null && !checkUserLimit(username, isLoginRequest, response)) {
            return false;
        }

        return true;
    }

    /**
     * 检查 IP 级别限流
     */
    private boolean checkIpLimit(String clientIp, boolean isLoginRequest,
                                  HttpServletResponse response) throws Exception {
        ConcurrentHashMap<String, RequestCounter> counters = isLoginRequest ? ipLoginCounters : ipCounters;
        int maxRequests = isLoginRequest ? MAX_LOGIN_REQUESTS_PER_MINUTE : MAX_REQUESTS_PER_MINUTE;

        RequestCounter counter = counters.computeIfAbsent(clientIp, k -> new RequestCounter());

        if (!counter.incrementAndCheck(maxRequests)) {
            writeRateLimitResponse(response, "请求过于频繁，请稍后再试");
            return false;
        }
        return true;
    }

    /**
     * 检查用户级别限流
     */
    private boolean checkUserLimit(String username, boolean isLoginRequest,
                                    HttpServletResponse response) throws Exception {
        ConcurrentHashMap<String, RequestCounter> counters = isLoginRequest ? userLoginCounters : userCounters;
        int maxRequests = isLoginRequest ? MAX_LOGIN_REQUESTS_PER_MINUTE_USER : MAX_REQUESTS_PER_MINUTE_USER;

        RequestCounter counter = counters.computeIfAbsent(username, k -> new RequestCounter());

        if (!counter.incrementAndCheck(maxRequests)) {
            writeRateLimitResponse(response, "您的操作过于频繁，请稍后再试");
            return false;
        }
        return true;
    }

    /**
     * 获取当前登录用户名
     */
    private String getCurrentUsername() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()
                && !"anonymousUser".equals(authentication.getPrincipal())) {
            return authentication.getName();
        }
        return null;
    }

    /**
     * 写入限流响应
     */
    private void writeRateLimitResponse(HttpServletResponse response, String message) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(429);
        ObjectMapper mapper = new ObjectMapper();
        response.getWriter().write(mapper.writeValueAsString(
                R.fail(ResultCode.FAILED.getCode(), message)
        ));
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}
