package com.pharmacopoeia.security;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.pharmacopoeia.config.RateLimitProperties;
import jakarta.servlet.FilterChain;
import org.jetbrains.annotations.NotNull;
import org.slf4j.MDC;
import com.pharmacopoeia.util.IpUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.Deque;

/**
 * 用户/系统侧限流（按 IP 滑动窗口 60s）：
 * - chat POST（/chat/*）：perMinute（默认 60）—— 问答不能无限刷
 * - 埋点写入 POST（/api/v1/analytics/events）：analyticsPerMinute（默认 600，多系统调用给大）
 * - 埋点导出 GET（/api/v1/analytics/events/export）：exportPerMinute（默认 5，重查询阈值给小）
 * 时间戳队列 + Caffeine expireAfterAccess(60s) 自动淘汰空闲桶，无需手动清理。
 * 超限返回 429，不进入 Controller 层。
 */
@Component
public class RateLimitFilter extends OncePerRequestFilter {

    private static final long WINDOW_MS = 60_000L;

    private final RateLimitProperties props;
    private final Cache<String, Deque<Long>> chatBuckets;
    private final Cache<String, Deque<Long>> analyticsBuckets;
    private final Cache<String, Deque<Long>> exportBuckets;

    public RateLimitFilter(RateLimitProperties props) {
        this.props = props;
        this.chatBuckets = Caffeine.newBuilder().expireAfterAccess(Duration.ofSeconds(60)).build();
        this.analyticsBuckets = Caffeine.newBuilder().expireAfterAccess(Duration.ofSeconds(60)).build();
        this.exportBuckets = Caffeine.newBuilder().expireAfterAccess(Duration.ofSeconds(60)).build();
    }

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request,
                                    @NotNull HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String ip = IpUtils.getClientIp(request);
        MDC.put("ip", ip);
        try {
            String uri = request.getRequestURI();
            boolean post = "POST".equalsIgnoreCase(request.getMethod());
            boolean get = "GET".equalsIgnoreCase(request.getMethod());

            boolean limited = false;
            if (post && uri.contains("/chat/")) {
                limited = !allow(ip, chatBuckets, props.getPerMinute());
            } else if (post && uri.contains("/api/v1/analytics/events")) {
                limited = !allow(ip, analyticsBuckets, props.getAnalyticsPerMinute());
            } else if (get && uri.contains("/api/v1/analytics/events/export")) {
                limited = !allow(ip, exportBuckets, props.getExportPerMinute());
            }

            if (limited) {
                writeRateLimitError(response);
                return;
            }
            filterChain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }

    private boolean allow(String ip, Cache<String, Deque<Long>> buckets, int perMinute) {
        if (perMinute <= 0) return true; // 0/负 = 不限
        long now = System.currentTimeMillis();
        Deque<Long> dq = buckets.get(ip, k -> new ArrayDeque<>());
        synchronized (dq) {
            long cutoff = now - WINDOW_MS;
            while (!dq.isEmpty() && dq.peekFirst() < cutoff) {
                dq.pollFirst();
            }
            if (dq.size() >= perMinute) {
                return false;
            }
            dq.addLast(now);
            return true;
        }
    }

    private static void writeRateLimitError(HttpServletResponse response) throws IOException {
        response.setStatus(429);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(
                "{\"error\":\"请求过于频繁，请稍后再试\",\"detail\":\"请求过于频繁，请稍后再试\",\"code\":429}"
        );
    }
}
