package ca.ryangreen.apigateway.generic;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.http.AmazonHttpClient;
import com.amazonaws.http.HttpMethodName;
import com.amazonaws.http.apache.client.impl.SdkHttpClient;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import java.util.Arrays;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.protocol.HttpContext;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.times;

public class GenericApiGatewayClientTest {
    private GenericApiGatewayClient client;
    private SdkHttpClient mockClient;
    @Before
    public void setUp() throws IOException {
        AWSCredentialsProvider credentials = new AWSStaticCredentialsProvider(new BasicAWSCredentials("foo", "bar"));

        mockClient = Mockito.mock(SdkHttpClient.class);
        HttpResponse resp = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, 200, "OK"));
        BasicHttpEntity entity = new BasicHttpEntity();
        entity.setContent(new ByteArrayInputStream("test payload".getBytes()));
        resp.setEntity(entity);
        Mockito.doReturn(resp).when(mockClient).execute(any(HttpUriRequest.class), any(HttpContext.class));

        ClientConfiguration clientConfig = new ClientConfiguration();

        client = new GenericApiGatewayClientBuilder()
                .withClientConfiguration(clientConfig)
                .withCredentials(credentials)
                .withEndpoint("https://foobar.execute-api.us-east-1.amazonaws.com")
                .withRegion(Region.getRegion(Regions.fromName("us-east-1")))
                .withApiKey("12345")
                .withHttpClient(new AmazonHttpClient(clientConfig, mockClient, null))
                .build();
    }

    @Test
    public void testExecute_happy() throws IOException {
        Map<String, String> headers = new HashMap<>();
        headers.put("Account-Id", "fubar");
        headers.put("Content-Type", "application/json");
        GenericApiGatewayResponse response = client.execute(
                new GenericApiGatewayRequestBuilder()
                        .withBody(new ByteArrayInputStream("test request".getBytes()))
                        .withHttpMethod(HttpMethodName.POST)
                        .withHeaders(headers)
                        .withResourcePath("/test/orders").build());

        assertEquals("Wrong response body", "test payload", response.getBody());
        assertEquals("Wrong response status", 200, response.getHttpResponse().getStatusCode());

        Mockito.verify(mockClient, times(1)).execute(argThat(new LambdaMatcher<>(
                        x -> (x.getMethod().equals("POST")
                                && x.getFirstHeader("Account-Id").getValue().equals("fubar")
                                && x.getFirstHeader("x-api-key").getValue().equals("12345")
                                && x.getFirstHeader("Authorization").getValue().startsWith("AWS4")
                                && x.getURI().toString().equals("https://foobar.execute-api.us-east-1.amazonaws.com/test/orders")))),
                any(HttpContext.class));
    }

    @Test
    public void testExecute_happy_parameters() throws IOException {
        Map<String, String> headers = new HashMap<>();
        headers.put("Account-Id", "fubar");
        headers.put("Content-Type", "application/json");
        Map<String,List<String>> parameters = new HashMap<>();
        parameters.put("MyParam", Arrays.asList("MyParamValue"));
        GenericApiGatewayResponse response = client.execute(
            new GenericApiGatewayRequestBuilder()
                .withBody(new ByteArrayInputStream("test request".getBytes()))
                .withHttpMethod(HttpMethodName.POST)
                .withHeaders(headers)
                .withParameters(parameters)
                .withResourcePath("/test/orders").build());

        assertEquals("Wrong response body", "test payload", response.getBody());
        assertEquals("Wrong response status", 200, response.getHttpResponse().getStatusCode());

        Mockito.verify(mockClient, times(1)).execute(argThat(new LambdaMatcher<>(
                                                         x -> (x.getMethod().equals("POST")
                                                             && x.getFirstHeader("Account-Id").getValue().equals("fubar")
                                                             && x.getFirstHeader("x-api-key").getValue().equals("12345")
                                                             && x.getFirstHeader("Authorization").getValue().startsWith("AWS4")
                                                             && x.getURI().toString().equals("https://foobar.execute-api.us-east-1.amazonaws.com/test/orders?MyParam=MyParamValue")))),
                                                     any(HttpContext.class));
    }

    @Test
    public void testExecute_noApiKey_noCreds() throws IOException {
        client = new GenericApiGatewayClientBuilder()
                .withEndpoint("https://foobar.execute-api.us-east-1.amazonaws.com")
                .withRegion(Region.getRegion(Regions.fromName("us-east-1")))
                .withClientConfiguration(new ClientConfiguration())
                .withHttpClient(new AmazonHttpClient(new ClientConfiguration(), mockClient, null))
                .build();

        GenericApiGatewayResponse response = client.execute(
                new GenericApiGatewayRequestBuilder()
                        .withBody(new ByteArrayInputStream("test request".getBytes()))
                        .withHttpMethod(HttpMethodName.POST)
                        .withResourcePath("/test/orders").build());

        assertEquals("Wrong response body", "test payload", response.getBody());
        assertEquals("Wrong response status", 200, response.getHttpResponse().getStatusCode());

        Mockito.verify(mockClient, times(1)).execute(argThat(new LambdaMatcher<>(
                        x -> (x.getMethod().equals("POST")
                                && x.getFirstHeader("x-api-key") == null
                                && x.getFirstHeader("Authorization") == null
                                && x.getURI().toString().equals("https://foobar.execute-api.us-east-1.amazonaws.com/test/orders")))),
                any(HttpContext.class));
    }

    @Test
    public void testExecute_non2xx_exception() throws IOException {
        HttpResponse resp = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, 404, "Not found"));
        BasicHttpEntity entity = new BasicHttpEntity();
        entity.setContent(new ByteArrayInputStream("{\"message\" : \"error payload\"}".getBytes()));
        resp.setEntity(entity);
        Mockito.doReturn(resp).when(mockClient).execute(any(HttpUriRequest.class), any(HttpContext.class));

        Map<String, String> headers = new HashMap<>();
        headers.put("Account-Id", "fubar");
        headers.put("Content-Type", "application/json");

        try {
            client.execute(
                    new GenericApiGatewayRequestBuilder()
                            .withBody(new ByteArrayInputStream("test request".getBytes()))
                            .withHttpMethod(HttpMethodName.POST)
                            .withHeaders(headers)
                            .withResourcePath("/test/orders").build());

            Assert.fail("Expected exception");
        } catch (GenericApiGatewayException e) {
            assertEquals("Wrong status code", 404, e.getStatusCode());
            assertEquals("Wrong exception message", "{\"message\":\"error payload\"}", e.getErrorMessage());
        }
    }


}