package com.devjob.ratelimit;

import tools.jackson.databind.ObjectMapper;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Refill;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;

/**
 * EP35: 로그인 API에 IP 기반 Rate Limit 적용.
 * - Bucket4j 토큰 버킷: IP당 분당 10회까지 허용, 초과 시 429.
 * - 메모리(ConcurrentHashMap)에 IP별 버킷을 보관한다. 트래픽이 커지면
 *   Redis 기반 분산 버킷으로 교체해야 무한히 늘어나는 메모리 문제를 막을 수 있다.
 */
public class RateLimitInterceptor implements HandlerInterceptor {

    private static final int CAPACITY = 10;
    private static final Duration REFILL_DURATION = Duration.ofMinutes(1);

    private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String clientIp = resolveClientIp(request);
        Bucket bucket = buckets.computeIfAbsent(clientIp, ip -> newBucket());

        if (bucket.tryConsume(1)) {
            return true;
        }

        response.setStatus(429); // Too Many Requests
        response.setHeader("Retry-After", String.valueOf(REFILL_DURATION.toSeconds()));
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(
                Map.of(
                        "success", false,
                        "errorCode", "TOO_MANY_REQUESTS",
                        "message", "요청이 너무 많습니다. 잠시 후 다시 시도해주세요."
                )
        ));
        return false;
    }

    private Bucket newBucket() {
        Bandwidth limit = Bandwidth.classic(CAPACITY, Refill.greedy(CAPACITY, REFILL_DURATION));
        return Bucket.builder().addLimit(limit).build();
    }

    /**
     * 로드밸런서/리버스 프록시 뒤에 있으면 request.getRemoteAddr()는 항상 프록시 IP(예: 127.0.0.1)이므로
     * X-Forwarded-For 헤더를 우선 사용한다.
     */
    private String resolveClientIp(HttpServletRequest request) {
        String forwarded = request.getHeader("X-Forwarded-For");
        if (forwarded != null && !forwarded.isBlank()) {
            return forwarded.split(",")[0].trim();
        }
        return request.getRemoteAddr();
    }
}
