package com.marcosbarbero.zuul.filters.pre.ratelimit;

import com.marcosbarbero.zuul.filters.pre.ratelimit.config.Policy;
import com.marcosbarbero.zuul.filters.pre.ratelimit.config.Rate;
import com.marcosbarbero.zuul.filters.pre.ratelimit.config.RateLimitProperties;
import com.marcosbarbero.zuul.filters.pre.ratelimit.config.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.AllArgsConstructor;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.http.HttpStatus;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;

import static com.marcosbarbero.zuul.filters.pre.ratelimit.config.Policy.Type;
import static com.marcosbarbero.zuul.filters.pre.ratelimit.config.Policy.Type.ORIGIN;
import static com.marcosbarbero.zuul.filters.pre.ratelimit.config.Policy.Type.URL;
import static com.marcosbarbero.zuul.filters.pre.ratelimit.config.Policy.Type.USER;

/**
 * @author Marcos Barbero
 * @author Michal Šváb
 */
@AllArgsConstructor
public class RateLimitFilter extends ZuulFilter {

    private final RateLimiter limiter;
    private final RateLimitProperties properties;
    private final RouteLocator routeLocator;
    private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper();

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return -1;
    }

    @Override
    public boolean shouldFilter() {
        return this.properties.isEnabled() && policy() != null;
    }

    public Object run() {
        final RequestContext ctx = RequestContext.getCurrentContext();
        final HttpServletResponse response = ctx.getResponse();
        final HttpServletRequest request = ctx.getRequest();
        Optional.ofNullable(policy()).ifPresent(policy -> {

            final Rate rate = this.limiter.consume(policy, key(request, policy.getType()));
            response.setHeader(Headers.LIMIT, rate.getLimit().toString());
            response.setHeader(Headers.REMAINING, String.valueOf(Math.max(rate.getRemaining(), 0)));
            response.setHeader(Headers.RESET, rate.getReset().toString());
            if (rate.getRemaining() < 0) {
                ctx.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
                ctx.put("rateLimitExceeded", "true");
                throw new RuntimeException(HttpStatus.TOO_MANY_REQUESTS.toString());
            }
        });
        return null;
    }

    /**
     * Get the requestURI from request.
     *
     * @return The request URI
     */
    private String requestURI() {
        return URL_PATH_HELPER.getPathWithinApplication(RequestContext.getCurrentContext().getRequest());
    }

    /**
     * Return the requestedContext from request.
     *
     * @return The requestedContext
     */
    private Route route() {
        return this.routeLocator.getMatchingRoute(this.requestURI());
    }

    private Policy policy() {
        return (route() != null) ? this.properties.getPolicies().get(route().getId()) : null;
    }

    private String key(final HttpServletRequest request, final List<Type> types) {
        final Route route = route();
        final StringBuilder builder = new StringBuilder(route.getId());
        if (types.contains(URL)) {
            builder.append(":").append(route.getPath());
        }
        if (types.contains(ORIGIN)) {
            builder.append(":").append(getRemoteAddr(request));
        }
        if (types.contains(USER)) {
            builder.append(":").append((request.getUserPrincipal() != null) ? request.getUserPrincipal().getName() : "anonymous");
        }
        return builder.toString();
    }

    private String getRemoteAddr(final HttpServletRequest request) {
        final String remoteAddr;
        if (this.properties.isBehindProxy() && request.getHeader("X-FORWARDED-FOR") != null) {
            remoteAddr = request.getHeader("X-FORWARDED-FOR");
        } else {
            remoteAddr = request.getRemoteAddr();
        }
        return remoteAddr;
    }

    interface Headers {
        String LIMIT = "X-RateLimit-Limit";
        String REMAINING = "X-RateLimit-Remaining";
        String RESET = "X-RateLimit-Reset";
    }
}

@@ -420,41 +420,8 @@
 xt;%0A
-import lombok.AllArgsConstructor;
 %0Aimp
@@ -642,16 +642,67 @@
 elper;%0A%0A
+import java.util.List;%0Aimport java.util.Optional;%0A%0A
 import j
@@ -790,56 +790,41 @@
 se;%0A
+%0A
 import 
-java.util.List;%0Aimport java.util.Optional
+lombok.AllArgsConstructor
 ;%0A%0Ai
@@ -1279,24 +1279,229 @@
 ulFilter %7B%0A%0A
+    private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper();%0A    private static final String X_FORWARDED_FOR = %22X-FORWARDED-FOR%22;%0A    private static final String ANONYMOUS = %22anonymous%22;%0A%0A
     private 
@@ -1625,86 +1625,8 @@
 tor;
-%0A    private static final UrlPathHelper URL_PATH_HELPER = new UrlPathHelper();
 %0A%0A  
@@ -3977,28 +3977,46 @@
 Name() :
- %22anonymous%22
+%0A                    ANONYMOUS
 );%0A     
@@ -4221,35 +4221,34 @@
 quest.getHeader(
-%22X-
+X_
 FORWARDED-FOR%22) 
@@ -4240,21 +4240,20 @@
 ORWARDED
--
+_
 FOR
-%22
 ) != nul
@@ -4300,19 +4300,18 @@
 tHeader(
-%22X-
+X_
 FORWARDE
@@ -4311,21 +4311,20 @@
 ORWARDED
--
+_
 FOR
-%22
 );%0A     

Use constants instead of literal values