package com.heroku.api.connection;

import com.heroku.api.Heroku;
import com.heroku.api.command.Command;
import com.heroku.api.command.LoginCommand;
import com.heroku.api.command.login.LoginResponse;
import com.heroku.api.exception.HerokuAPIException;
import com.ning.http.client.*;
import com.ning.http.util.Base64;

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


public class AsyncHttpClientConnection implements Connection<ListenableFuture<?>> {

    private LoginCommand loginCommand;
    private LoginResponse loginResponse;
    private AsyncHttpClient httpClient;


    public AsyncHttpClientConnection(LoginCommand login) {
        this.loginCommand = login;
        AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder();
        builder.setSSLContext(Heroku.herokuSSLContext());
        builder.setHostnameVerifier(Heroku.herokuHostnameVerifier());
        httpClient = new AsyncHttpClient(builder.build());
        this.loginResponse = executeCommand(loginCommand);
    }

    private Request buildRequest(Command<?> command) {
        AsyncHttpClient.BoundRequestBuilder builder = prepareRequest(command);
        builder.setHeader(Heroku.ApiVersion.HEADER, String.valueOf(Heroku.ApiVersion.v2.version));
        builder.setHeader(command.getResponseType().getHeaderName(), command.getResponseType().getHeaderValue());
        if (loginResponse != null) {
            try {
                builder.setHeader("Authorization", "Basic " + Base64.encode((":" + loginResponse.api_key()).getBytes("UTF-8")));
            } catch (UnsupportedEncodingException e) {
                throw new HerokuAPIException("Unupported op exception while encoding api key");
            }
        }
        if (command.hasBody()) {
            builder.setBody(command.getBody());
        }
        return builder.build();
    }

    private AsyncHttpClient.BoundRequestBuilder prepareRequest(Command<?> command) {
        String req = Heroku.Config.ENDPOINT.value + command.getEndpoint();
        switch (command.getHttpMethod()) {
            case GET:
                return httpClient.prepareGet(req);
            case POST:
                return httpClient.preparePost(req);
            case PUT:
                return httpClient.preparePut(req);
            case DELETE:
                return httpClient.prepareDelete(req);
            default:
                throw new UnsupportedOperationException(command.getHttpMethod().name() + " is not supported");
        }
    }

    @Override
    public <T> T executeCommand(Command<T> command) {
        try {
            return executeCommandAsync(command).get(30L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new HerokuAPIException("request interrupted", e);
        } catch (ExecutionException e) {
            throw new HerokuAPIException("execution exception", e.getCause());
        } catch (TimeoutException e) {
            throw new HerokuAPIException("request timeout after 30 sec", e.getCause());
        }
    }

    @Override
    public <T> ListenableFuture<T> executeCommandAsync(final Command<T> command) {
        Request req = buildRequest(command);
        AsyncCompletionHandler<T> handler = new AsyncCompletionHandler<T>() {
            @Override
            public T onCompleted(Response response) throws Exception {
                return command.getResponse(response.getResponseBody("UTF-8").getBytes(), response.getStatusCode());
            }
        };
        try {
            return httpClient.executeRequest(req, handler);
        } catch (IOException e) {
            throw new HerokuAPIException("IOException while executing request", e);
        }
    }


}

@@ -146,59 +146,8 @@
 nd;%0A
-import com.heroku.api.command.login.LoginResponse;%0A
 impo
@@ -568,74 +568,21 @@
 ate 
-LoginCommand loginCommand;%0A    private LoginResponse loginResponse
+String apiKey
 ;%0A  
@@ -692,34 +692,273 @@
     
-this.loginCommand = login;
+httpClient = geHttpClient();%0A        this.apiKey = executeCommand(login).api_key();%0A    %7D%0A%0A    public AsyncHttpClientConnection(String apiKey) %7B%0A        httpClient = geHttpClient();%0A        this.apiKey = apiKey;%0A    %7D%0A%0A    protected AsyncHttpClient geHttpClient() %7B
 %0A   
@@ -1175,28 +1175,22 @@
         
-httpClient =
+return
  new Asy
@@ -1224,67 +1224,8 @@
 ));%0A
-        this.loginResponse = executeCommand(loginCommand);%0A
     
@@ -1586,29 +1586,22 @@
     if (
-loginResponse
+apiKey
  != null
@@ -1709,31 +1709,14 @@
 %22 + 
-loginResponse.api_key()
+apiKey
 ).ge

refactor AsyncHttpClient connection to support the two variants of constructor, LoginCommand or apiKey