package org.raddatz.familienarchiv.auth;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class LoginRateLimiter {

    private final LoadingCache<String, Bucket> byIpEmail;
    private final LoadingCache<String, Bucket> byIp;
    private final int maxPerIpEmail;
    private final int maxPerIp;
    private final int windowMinutes;

    public LoginRateLimiter(RateLimitProperties props) {
        this.maxPerIpEmail = props.getMaxAttemptsPerIpEmail();
        this.maxPerIp = props.getMaxAttemptsPerIp();
        this.windowMinutes = props.getWindowMinutes();

        this.byIpEmail = Caffeine.newBuilder()
                .expireAfterAccess(windowMinutes, TimeUnit.MINUTES)
                .build(key -> newBucket(maxPerIpEmail, windowMinutes));

        this.byIp = Caffeine.newBuilder()
                .expireAfterAccess(windowMinutes, TimeUnit.MINUTES)
                .build(key -> newBucket(maxPerIp, windowMinutes));
    }

    // NOTE: This cache is node-local (in-memory). In a multi-replica deployment,
    // effective limits would be multiplied by replica count.
    // For the current single-VPS setup this is the correct, simplest implementation.

    public void checkAndConsume(String ip, String email) {
        long retryAfterSeconds = windowMinutes * 60L;
        String key = ip + ":" + email.toLowerCase(Locale.ROOT);
        if (!byIpEmail.get(key).tryConsume(1)) {
            throw DomainException.tooManyRequests(ErrorCode.TOO_MANY_LOGIN_ATTEMPTS,
                    "Too many login attempts from " + ip, retryAfterSeconds);
        }
        if (!byIp.get(ip).tryConsume(1)) {
            // Refund the ipEmail token so IP-level blocking does not erode the per-email quota.
            byIpEmail.get(key).addTokens(1);
            throw DomainException.tooManyRequests(ErrorCode.TOO_MANY_LOGIN_ATTEMPTS,
                    "Too many login attempts from " + ip, retryAfterSeconds);
        }
    }

    public void invalidateOnSuccess(String ip, String email) {
        byIpEmail.invalidate(ip + ":" + email.toLowerCase(Locale.ROOT));
        byIp.invalidate(ip);
    }

    private static Bucket newBucket(int limit, int minutes) {
        return Bucket.builder()
                .addLimit(Bandwidth.builder()
                        .capacity(limit)
                        .refillGreedy(limit, Duration.ofMinutes(minutes))
                        .build())
                .build();
    }
}
