package com.sap.cloud.security.xsuaa.client;

import static com.sap.cloud.security.xsuaa.client.OidcConfigurationService.DISCOVERY_ENDPOINT_DEFAULT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.springframework.http.HttpMethod;

import com.sap.cloud.security.xsuaa.util.HttpClientTestFactory;

public class DefaultOidcConfigurationServiceTest {
	public static final URI CONFIG_ENDPOINT_URI = URI.create("https://sub.myauth.com" + DISCOVERY_ENDPOINT_DEFAULT);

	private final String jsonOidcConfiguration;

	private CloseableHttpClient httpClientMock;
	private DefaultOidcConfigurationService cut;

	public DefaultOidcConfigurationServiceTest() throws IOException {
		jsonOidcConfiguration = IOUtils.resourceToString("/oidcConfiguration.json", StandardCharsets.UTF_8);
	}

	@Before
	public void setUp() {
		httpClientMock = Mockito.mock(CloseableHttpClient.class);
		cut = new DefaultOidcConfigurationService(httpClientMock);
	}

	@Test
	public void httpClient_isNull_throwsException() {
		assertThatThrownBy(() -> new DefaultOidcConfigurationService(null))
				.isInstanceOf(IllegalArgumentException.class);
	}

	@Test
	public void retrieveEndpoints_parameterIsNull_throwsException() {
		assertThatThrownBy(() -> cut.retrieveEndpoints(null))
				.isInstanceOf(IllegalArgumentException.class);
	}

	@Test
	public void retrieveEndpoints_badRequest_throwsException() throws IOException {
		String errorDescription = "Something wen't wrong";
		CloseableHttpResponse response = HttpClientTestFactory
				.createHttpResponse(errorDescription, HttpStatus.SC_BAD_REQUEST);
		when(httpClientMock.execute(any())).thenReturn(response);

		assertThatThrownBy(() -> retrieveEndpoints())
				.isInstanceOf(OAuth2ServiceException.class)
				.hasMessageContaining(errorDescription);
	}

	@Test
	public void retrieveEndpoints_executesHttpGetRequestWithCorrectURI() throws IOException {
		mockResponse();

		retrieveEndpoints();

		Mockito.verify(httpClientMock, times(1)).execute(argThat(isHttpGetAndContainsCorrectURI()));

	}

	@Test
	public void retrieveEndpoints_errorOccurs_throwsServiceException() throws IOException {
		String errorMessage = "useful error message";
		when(httpClientMock.execute(any())).thenThrow(new IOException(errorMessage));

		assertThatThrownBy(() -> retrieveEndpoints())
				.isInstanceOf(OAuth2ServiceException.class)
				.hasMessageContaining(errorMessage);
	}

	@Test
	public void retrieveIssuerEndpoints_executesHttpGetRequestWithCorrectURI() throws IOException {
		URI discoveryEndpoint1 = DefaultOidcConfigurationService
				.getDiscoveryEndpointUri("https://sub.myauth.com");
		URI discoveryEndpoint2 = DefaultOidcConfigurationService
				.getDiscoveryEndpointUri("https://sub.myauth.com/");
		URI discoveryEndpoint3 = DefaultOidcConfigurationService
				.getDiscoveryEndpointUri("https://sub.myauth.com/path");
		URI discoveryEndpoint4 = DefaultOidcConfigurationService
				.getDiscoveryEndpointUri("https://sub.myauth.com//path");
		URI discoveryEndpoint5 = DefaultOidcConfigurationService
				.getDiscoveryEndpointUri("sub.myauth.com/path");

		assertThat(discoveryEndpoint1.toString()).isEqualTo("https://sub.myauth.com/.well-known/openid-configuration");
		assertThat(discoveryEndpoint2.toString()).isEqualTo("https://sub.myauth.com/.well-known/openid-configuration");
		assertThat(discoveryEndpoint3.toString())
				.isEqualTo("https://sub.myauth.com/path/.well-known/openid-configuration");
		assertThat(discoveryEndpoint4.toString())
				.isEqualTo("https://sub.myauth.com/path/.well-known/openid-configuration");
		assertThat(discoveryEndpoint5.toString())
				.isEqualTo("https://sub.myauth.com/path/.well-known/openid-configuration");
	}

	@Test
	public void retrieveEndpoints_containsBothKeys() throws IOException {
		mockResponse();

		OAuth2ServiceEndpointsProvider result = retrieveEndpoints();

		assertThat(result.getTokenEndpoint().toString()).isEqualTo("http://localhost/oauth/token");
		assertThat(result.getJwksUri().toString()).isEqualTo("http://localhost/token_keys");
		assertThat(result.getAuthorizeEndpoint().toString()).isEqualTo("http://localhost/oauth/authorize");
	}

	private CloseableHttpResponse mockResponse() throws IOException {
		CloseableHttpResponse response = HttpClientTestFactory.createHttpResponse(jsonOidcConfiguration);
		when(httpClientMock.execute(any())).thenReturn(response);
		return response;
	}

	private OAuth2ServiceEndpointsProvider retrieveEndpoints() throws OAuth2ServiceException {
		return cut.retrieveEndpoints(CONFIG_ENDPOINT_URI);
	}

	private ArgumentMatcher<HttpUriRequest> isHttpGetAndContainsCorrectURI() {
		return (httpGet) -> {
			boolean hasCorrectURI = httpGet.getURI().equals(CONFIG_ENDPOINT_URI);
			boolean correctMethod = httpGet.getMethod().equals(HttpMethod.GET.toString());
			return hasCorrectURI && correctMethod;
		};
	}
}