/*
 * Sliding-window rate limiter that tracks request timestamps per key.
 * Used to limit login/register attempts by IP.
 *
 * Author: Harkamal
 * Date: April 2026
 */
package com.petshop.backend.security;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class RateLimiterService {

    private final Map<String, Deque<Instant>> buckets = new ConcurrentHashMap<>();

    /**
     * Checks whether a request identified by key is within the rate limit.
     * Uses a sliding window: keeps a queue of timestamps per key, drops any
     * that fall outside the window, then checks if there's room for one more.
     * @param key identifier for the rate limit bucket (e.g. IP address)
     * @param maxRequests max number of requests allowed in the window
     * @param window the time window to count requests in
     * @return true if the request is allowed, false if rate limited
     */
    public boolean isAllowed(String key, int maxRequests, Duration window) {
        Instant now = Instant.now();
        Instant windowStart = now.minus(window);

        Deque<Instant> timestamps = buckets.computeIfAbsent(key, k -> new ArrayDeque<>());
        synchronized (timestamps) {
            // Remove timestamps that are older than the window
            while (!timestamps.isEmpty() && timestamps.peekFirst().isBefore(windowStart)) {
                timestamps.pollFirst();
            }
            if (timestamps.size() >= maxRequests) {
                return false;
            }
            timestamps.addLast(now);
            return true;
        }
    }

    @Scheduled(fixedDelay = 300_000)
    public void evictStale() {
        Instant cutoff = Instant.now().minus(Duration.ofHours(2));
        buckets.entrySet().removeIf(entry -> {
            Deque<Instant> timestamps = entry.getValue();
            synchronized (timestamps) {
                return timestamps.isEmpty() || timestamps.peekLast().isBefore(cutoff);
            }
        });
    }
}
