package com.auth0.jwk;

import java.net.Proxy;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.auth0.jwk.UrlJwkProvider.urlForDomain;

/**
 * JwkProvider builder
 */
@SuppressWarnings("WeakerAccess")
public class JwkProviderBuilder {

    private final URL url;
    private Proxy proxy;
    private TimeUnit expiresUnit;
    private long expiresIn;
    private long cacheSize;
    private boolean cached;
    private BucketImpl bucket;
    private boolean rateLimited;
    private Map<String, String> headers;

    /**
     * Creates a new Builder with the given URL where to load the jwks from.
     *
     * @param url to load the jwks
     * @throws IllegalStateException if url is null
     */
    public JwkProviderBuilder(URL url) {
        if (url == null) {
            throw new IllegalStateException("Cannot build provider without url to jwks");
        }
        this.url = url;
        this.cached = true;
        this.expiresIn = 10;
        this.expiresUnit = TimeUnit.HOURS;
        this.cacheSize = 5;
        this.rateLimited = true;
        this.bucket = new BucketImpl(10, 1, TimeUnit.MINUTES);
    }

    /**
     * Creates a new Builder with a domain where to look for the jwks.
     * <br><br> It can be a url link 'https://samples.auth0.com' or just a domain 'samples.auth0.com'.
     * If the protocol (http or https) is not provided then https is used by default.
     * The default jwks path "/.well-known/jwks.json" is appended to the given string domain.
     * <br><br> For example, when the domain is "samples.auth0.com"
     * the jwks url that will be used is "https://samples.auth0.com/.well-known/jwks.json"
     * <br><br> Use {@link #JwkProviderBuilder(URL)} if you need to pass a full URL.
     *
     * @param domain where jwks is published
     * @throws IllegalStateException if domain is null
     * @see UrlJwkProvider#UrlJwkProvider(String)
     */
    public JwkProviderBuilder(String domain) {
        this(buildJwkUrl(domain));
    }

    private static URL buildJwkUrl(String domain) {
        if (domain == null) {
            throw new IllegalStateException("Cannot build provider without domain");
        }
        return urlForDomain(domain);
    }

    /**
     * Toggle the cache of Jwk. By default the provider will use cache.
     *
     * @param cached if the provider should cache jwks
     * @return the builder
     */
    public JwkProviderBuilder cached(boolean cached) {
        this.cached = cached;
        return this;
    }

    /**
     * Enable the cache specifying size and expire time.
     *
     * @param cacheSize number of jwk to cache
     * @param expiresIn amount of time the jwk will be cached
     * @param unit      unit of time for the expire of jwk
     * @return the builder
     */
    public JwkProviderBuilder cached(long cacheSize, long expiresIn, TimeUnit unit) {
        this.cached = true;
        this.cacheSize = cacheSize;
        this.expiresIn = expiresIn;
        this.expiresUnit = unit;
        return this;
    }

    /**
     * Toggle the rate limit of Jwk. By default the Provider will use rate limit.
     *
     * @param rateLimited if the provider should rate limit jwks
     * @return the builder
     */
    public JwkProviderBuilder rateLimited(boolean rateLimited) {
        this.rateLimited = rateLimited;
        return this;
    }

    /**
     * Enable the cache specifying size and expire time.
     *
     * @param bucketSize max number of jwks to deliver in the given rate.
     * @param refillRate amount of time to wait before a jwk can the jwk will be cached
     * @param unit       unit of time for the expire of jwk
     * @return the builder
     */
    public JwkProviderBuilder rateLimited(long bucketSize, long refillRate, TimeUnit unit) {
        bucket = new BucketImpl(bucketSize, refillRate, unit);
        return this;
    }

    /**
     * Sets the proxy to use for the connection.
     *
     * @param proxy proxy server to use when making this connection
     * @return the builder
     */
    public JwkProviderBuilder proxied(Proxy proxy) {
        this.proxy = proxy;
        return this;
    }

    /**
     * Sets the headers to send on the request. Any headers set here will override the default headers ("Accept": "application/json")
     *
     * @param headers a map of header keys to values to send on the request.
     * @return this builder instance
     */
    public JwkProviderBuilder headers(Map<String, String> headers) {
        this.headers = headers;
        return this;
    }

    /**
     * Creates a {@link JwkProvider}
     *
     * @return a newly created {@link JwkProvider}
     */
    public JwkProvider build() {
        JwkProvider urlProvider = new UrlJwkProvider(url, null, null, proxy, headers);
        if (this.rateLimited) {
            urlProvider = new RateLimitedJwkProvider(urlProvider, bucket);
        }
        if (this.cached) {
            urlProvider = new GuavaCachedJwkProvider(urlProvider, cacheSize, expiresIn, expiresUnit);
        }
        return urlProvider;
    }
}

@@ -362,24 +362,93 @@
 xpiresUnit;%0A
+    private Integer connectTimeout;%0A    private Integer readTimeout;%0A
     private 
@@ -4239,32 +4239,617 @@
 rn this;%0A    %7D%0A%0A
+    /**%0A     * Sets a custom connect and read timeout values.%0A     * When this method is not called, the default timeout values%0A     * will be those defined by the %7B@link UrlJwkProvider%7D implementation.%0A     *%0A     * @param connectTimeout connection timeout in milliseconds.%0A     * @param readTimeout read timeout in milliseconds.%0A     * @see UrlJwkProvider%0A     * @return the builder%0A     */%0A    public JwkProviderBuilder timeouts(int connectTimeout, int readTimeout) %7B%0A        this.connectTimeout = connectTimeout;%0A        this.readTimeout = readTimeout;%0A        return this;%0A    %7D%0A%0A
     /**%0A     * S
@@ -4974,16 +4974,16 @@
 /json%22)%0A
-
      *%0A 
@@ -5438,18 +5438,35 @@
 rl, 
-null, null
+connectTimeout, readTimeout
 , pr

allow builder to customize networking timeout values