package com.alttd.altitudeweb.services.limits;

import java.time.Duration;
import java.time.Instant;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class RequestCounter {
    private final Queue<Instant> requestTimestamps = new ConcurrentLinkedQueue<>();

    /**
     * Attempts to acquire a permit for a request within the specified rate limit.
     * This method checks if the number of requests made within the given duration
     * is less than the specified limit. If the limit has not been exceeded, the
     * current request is recorded and the method will return true. Otherwise, it
     * will return false.
     *
     * @param limit the maximum number of requests allowed within the specified duration
     * @param duration the time window during which requests are counted against the limit
     * @return true if the request is successfully acquired without exceeding the rate limit, false otherwise
     */
    public synchronized boolean tryAcquire(int limit, Duration duration) {
        Instant currentTime = Instant.now();
        Instant cutoffTime = currentTime.minus(duration);

        while (!requestTimestamps.isEmpty() && requestTimestamps.peek().isBefore(cutoffTime)) {
            requestTimestamps.poll();
        }

        if (requestTimestamps.size() < limit) {
            requestTimestamps.add(currentTime);
            return true;
        }

        return false;
    }

    public int getCurrentCount(Duration duration) {
        Instant cutoffTime = Instant.now().minus(duration);

        return (int) requestTimestamps.stream()
                .filter(timestamp -> !timestamp.isBefore(cutoffTime))
                .count();
    }

    public Duration getNextResetTime(Duration duration) {
        if (requestTimestamps.isEmpty()) {
            return Duration.ZERO;
        }

        Instant oldestTimestamp = requestTimestamps.peek();
        Instant resetTime = oldestTimestamp.plus(duration);
        Duration timeUntilReset = Duration.between(Instant.now(), resetTime);

        return timeUntilReset.isNegative() ? Duration.ZERO : timeUntilReset;
    }
}
