package com.hubspot.singularity.auth.authenticator;

import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.hubspot.singularity.SingularityUser;
import com.hubspot.singularity.WebExceptions;
import com.hubspot.singularity.config.SingularityConfiguration;
import com.hubspot.singularity.config.WebhookAuthConfiguration;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.Response;

@Singleton
public class SingularityWebhookAuthenticator implements SingularityAuthenticator {
  private static final Logger LOG = LoggerFactory.getLogger(SingularityWebhookAuthenticator.class);

  private final AsyncHttpClient asyncHttpClient;
  private final WebhookAuthConfiguration webhookAuthConfiguration;
  private final Provider<HttpServletRequest> requestProvider;
  private final ObjectMapper objectMapper;
  private final Cache<String, SingularityUserPermissionsResponse> permissionsCache;

  @Inject
  public SingularityWebhookAuthenticator(AsyncHttpClient asyncHttpClient,
                                         SingularityConfiguration configuration,
                                         Provider<HttpServletRequest> requestProvider,
                                         ObjectMapper objectMapper) {
    this.asyncHttpClient = asyncHttpClient;
    this.webhookAuthConfiguration = configuration.getWebhookAuthConfiguration();
    this.requestProvider = requestProvider;
    this.objectMapper = objectMapper;
    this.permissionsCache = CacheBuilder.<String, SingularityUserPermissionsResponse>newBuilder()
        .expireAfterWrite(webhookAuthConfiguration.getCacheValidationMs(), TimeUnit.MILLISECONDS)
        .build();
  }

  @Override
  public Optional<SingularityUser> get() {
    String authHeaderValue = extractAuthHeader(requestProvider.get());

    SingularityUserPermissionsResponse permissionsResponse = verify(authHeaderValue);
    LOG.trace("Verified permissions for user {}", permissionsResponse);

    return permissionsResponse.getUser();
  }

  private String extractAuthHeader(HttpServletRequest request) {
    String authHeaderValue = request.getHeader("Authorization");

    if (Strings.isNullOrEmpty(authHeaderValue)) {
      throw WebExceptions.unauthorized("No Authorization header present, please log in first");
    } else {
      return authHeaderValue;
    }
  }

  private SingularityUserPermissionsResponse verify(String authHeaderValue) {
    SingularityUserPermissionsResponse maybeCachedPermissions = permissionsCache.getIfPresent(authHeaderValue);
    if (maybeCachedPermissions != null) {
      return maybeCachedPermissions;
    } else {
      try {
        Response response = asyncHttpClient.prepareGet(webhookAuthConfiguration.getAuthVerificationUrl())
            .addHeader("Authorization", authHeaderValue)
            .execute()
            .get();
        if (response.getStatusCode() > 299) {
          throw WebExceptions.unauthorized(String.format("Got status code %d when verifying jwt", response.getStatusCode()));
        } else {
          String responseBody = response.getResponseBody();
          SingularityUserPermissionsResponse permissionsResponse = objectMapper.readValue(responseBody, SingularityUserPermissionsResponse.class);
          if (!permissionsResponse.isAuthenticated()) {
            throw WebExceptions.unauthorized(String.format("User not authenticated (response: %s)", permissionsResponse));
          }
          if (!permissionsResponse.getUser().isPresent()) {
            throw WebExceptions.unauthorized(String.format("No user present in response %s", permissionsResponse));
          }
          permissionsCache.put(authHeaderValue, permissionsResponse);
          return permissionsResponse;
        }
      } catch (IOException|ExecutionException|InterruptedException e) {
        LOG.error("Exception while verifying jwt", e);
        throw WebExceptions.unauthorized(String.format("Exception while verifying jwt: %s", e.getMessage()));
      }
    }
  }
}

@@ -206,16 +206,60 @@
 Request;
+%0Aimport javax.ws.rs.WebApplicationException;
 %0A%0Aimport
@@ -2234,24 +2234,36 @@
 er%3E get() %7B%0A
+    try %7B%0A  
     String a
@@ -2318,24 +2318,26 @@
 er.get());%0A%0A
+  
     Singular
@@ -2410,24 +2410,26 @@
 Value);%0A    
+ 
+ 
 LOG.trace(%22V
@@ -2485,24 +2485,26 @@
 onse);%0A%0A    
+  
 return permi
@@ -2525,24 +2525,166 @@
 .getUser();%0A
+    %7D catch (WebApplicationException wae) %7B%0A      LOG.trace(%22Not authenticated: %7B%7D%22, wae.getMessage());%0A      return Optional.absent();%0A    %7D%0A
   %7D%0A%0A  priva

catch these exceptions and return absent