package com.realexpayments.remote.sdk;

import static com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils.PAYMENT_RESPONSE_BASIC_ERROR_XML_PATH;
import static com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils.PAYMENT_RESPONSE_FULL_ERROR_XML_PATH;
import static com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils.PAYMENT_RESPONSE_XML_PATH;
import static com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils.THREE_D_SECURE_VERIFY_ENROLLED_RESPONSE_XML_PATH;
import static com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils.checkBasicResponseError;
import static com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils.checkFullResponseError;
import static com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils.checkUnmarshalledPaymentResponse;
import static com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils.checkUnmarshalledThreeDSecureEnrolledResponse;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.File;
import java.io.IOException;

import javax.xml.transform.stream.StreamSource;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicStatusLine;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Matchers;

import com.realexpayments.remote.sdk.domain.payment.PaymentRequest;
import com.realexpayments.remote.sdk.domain.payment.PaymentResponse;
import com.realexpayments.remote.sdk.domain.threeDSecure.ThreeDSecureRequest;
import com.realexpayments.remote.sdk.domain.threeDSecure.ThreeDSecureResponse;
import com.realexpayments.remote.sdk.http.HttpConfiguration;
import com.realexpayments.remote.sdk.utils.SampleXmlValidationUtils;

/**
 * Unit test class for {@link RealexClient}.
 * 
 * @author markstanford
 *
 */
public class RealexClientTest {

	/**
	 * Test sending a payment request and receiving a payment response. 
	 */
	@Test
	public void sendTest() throws ClientProtocolException, IOException {

		//get sample response XML
		File file = new File(this.getClass().getResource(PAYMENT_RESPONSE_XML_PATH).getPath());
		StreamSource source = new StreamSource(file);
		PaymentResponse fromXmlResponse = new PaymentResponse().fromXml(source);

		//mock HttpResponse
		HttpResponse httpResponseMock = mock(HttpResponse.class);
		when(httpResponseMock.getEntity()).thenReturn(new StringEntity(fromXmlResponse.toXml()));
		when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null));

		//create empty request 
		PaymentRequest request = new PaymentRequest();

		//create configuration
		HttpConfiguration httpConfiguration = new HttpConfiguration();
		httpConfiguration.setOnlyAllowHttps(false);

		//mock HttpClient instance
		HttpClient httpClientMock = mock(HttpClient.class);
		when(httpClientMock.execute(Matchers.any(HttpUriRequest.class))).thenReturn(httpResponseMock);

		//execute send on client
		RealexClient realexClient = new RealexClient(SampleXmlValidationUtils.SECRET, httpClientMock, httpConfiguration);
		PaymentResponse response = realexClient.send(request);

		//validate response
		checkUnmarshalledPaymentResponse(response);
	}

	/**
	 * Test sending a payment request and receiving a payment response error. 
	 */
	@Test
	public void sendWithShortErrorResponseTest() throws ClientProtocolException, IOException {

		//get sample response XML
		File file = new File(this.getClass().getResource(PAYMENT_RESPONSE_BASIC_ERROR_XML_PATH).getPath());
		StreamSource source = new StreamSource(file);
		PaymentResponse fromXmlResponse = new PaymentResponse().fromXml(source);

		//mock HttpResponse
		HttpResponse httpResponseMock = mock(HttpResponse.class);
		when(httpResponseMock.getEntity()).thenReturn(new StringEntity(fromXmlResponse.toXml()));
		when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null));

		//create empty request 
		PaymentRequest request = new PaymentRequest();

		//create configuration
		HttpConfiguration httpConfiguration = new HttpConfiguration();
		httpConfiguration.setOnlyAllowHttps(false);

		//mock HttpCLient instance
		HttpClient httpClientMock = mock(HttpClient.class);
		when(httpClientMock.execute(Matchers.any(HttpUriRequest.class))).thenReturn(httpResponseMock);

		//execute send on client
		RealexClient realexClient = new RealexClient(SampleXmlValidationUtils.SECRET, httpClientMock, httpConfiguration);

		try {
			realexClient.send(request);
			Assert.fail("RealexException should have been thrown before this point.");
		} catch (RealexServerException ex) {
			//validate error
			checkBasicResponseError(ex);
		}
	}

	/**
	 * Test sending a payment request and receiving a payment response error. 
	 */
	@Test
	public void sendWithLongErrorResponseTest() throws ClientProtocolException, IOException {

		//get sample response XML
		File file = new File(this.getClass().getResource(PAYMENT_RESPONSE_FULL_ERROR_XML_PATH).getPath());
		StreamSource source = new StreamSource(file);
		PaymentResponse fromXmlResponse = new PaymentResponse().fromXml(source);

		//mock HttpResponse
		HttpResponse httpResponseMock = mock(HttpResponse.class);
		when(httpResponseMock.getEntity()).thenReturn(new StringEntity(fromXmlResponse.toXml()));
		when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null));

		//create empty request 
		PaymentRequest request = new PaymentRequest();

		//create configuration
		HttpConfiguration httpConfiguration = new HttpConfiguration();
		httpConfiguration.setOnlyAllowHttps(false);

		//mock HttpCLient instance
		HttpClient httpClientMock = mock(HttpClient.class);
		when(httpClientMock.execute(Matchers.any(HttpUriRequest.class))).thenReturn(httpResponseMock);

		//execute send on client
		RealexClient realexClient = new RealexClient(SampleXmlValidationUtils.SECRET, httpClientMock, httpConfiguration);

		PaymentResponse response = realexClient.send(request);
		checkFullResponseError(response);

	}

	/**
	 * Test sending a payment request and receiving a payment response error. 
	 */
	@Test
	public void sendWithErrorResponseInvalidCodeTest() throws ClientProtocolException, IOException {

		//get sample response XML
		File file = new File(this.getClass().getResource(PAYMENT_RESPONSE_BASIC_ERROR_XML_PATH).getPath());
		StreamSource source = new StreamSource(file);
		PaymentResponse fromXmlResponse = new PaymentResponse().fromXml(source);
		fromXmlResponse.setResult("invalid");

		//mock HttpResponse
		HttpResponse httpResponseMock = mock(HttpResponse.class);
		when(httpResponseMock.getEntity()).thenReturn(new StringEntity(fromXmlResponse.toXml()));
		when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null));

		//create empty request 
		PaymentRequest request = new PaymentRequest();

		//create configuration
		HttpConfiguration httpConfiguration = new HttpConfiguration();

		//mock HttpCLient instance
		HttpClient httpClientMock = mock(HttpClient.class);
		when(httpClientMock.execute(Matchers.any(HttpUriRequest.class))).thenReturn(httpResponseMock);

		//execute send on client
		RealexClient realexClient = new RealexClient(SampleXmlValidationUtils.SECRET, httpClientMock, httpConfiguration);

		boolean correctExceptionThrown = false;

		try {
			realexClient.send(request);
			Assert.fail("RealexException should have been thrown before this point.");
		} catch (RealexServerException ex) {
			Assert.fail("Incorrect exception thrown. Expected RealexException as result code is invalid.");
		} catch (RealexException ex) {
			correctExceptionThrown = true;
		}

		Assert.assertTrue("Incorrect exception thrown.", correctExceptionThrown);
	}

	/**
	 * Test receiving a response which has an invalid hash. 
	 */
	@Test(expected = RealexException.class)
	public void sendInvalidResponseHashTest() throws ClientProtocolException, IOException {
		//get sample response XML
		File file = new File(this.getClass().getResource(PAYMENT_RESPONSE_XML_PATH).getPath());
		StreamSource source = new StreamSource(file);
		PaymentResponse fromXmlResponse = new PaymentResponse().fromXml(source);

		//add invalid hash
		fromXmlResponse.setHash("invalid hash");

		//mock HttpResponse
		HttpResponse httpResponseMock = mock(HttpResponse.class);
		when(httpResponseMock.getEntity()).thenReturn(new StringEntity(fromXmlResponse.toXml()));
		when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null));

		//create empty request 
		PaymentRequest request = new PaymentRequest();

		//create configuration
		HttpConfiguration httpConfiguration = new HttpConfiguration();

		//mock HttpCLient instance
		HttpClient httpClientMock = mock(HttpClient.class);
		when(httpClientMock.execute(Matchers.any(HttpUriRequest.class))).thenReturn(httpResponseMock);

		//execute send on client
		RealexClient realexClient = new RealexClient(SampleXmlValidationUtils.SECRET, httpClientMock, httpConfiguration);
		realexClient.send(request);

		//shouldn't get this far
		Assert.fail("RealexException should have been thrown before this point.");
	}

	/**
	 * Test sending a ThreeDSecure Verify Enrolled request and receiving a ThreeDSecure Verify Enrolled response. 
	 */
	@Test
	public void sendThreeDSecureVerifyEnrolledTest() throws ClientProtocolException, IOException {

		//get sample response XML
		File file = new File(this.getClass().getResource(THREE_D_SECURE_VERIFY_ENROLLED_RESPONSE_XML_PATH).getPath());
		StreamSource source = new StreamSource(file);
		ThreeDSecureResponse fromXmlResponse = new ThreeDSecureResponse().fromXml(source);

		//mock HttpResponse
		HttpResponse httpResponseMock = mock(HttpResponse.class);
		when(httpResponseMock.getEntity()).thenReturn(new StringEntity(fromXmlResponse.toXml()));
		when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null));

		//create empty request 
		ThreeDSecureRequest request = new ThreeDSecureRequest();

		//create configuration
		HttpConfiguration httpConfiguration = new HttpConfiguration();
		httpConfiguration.setOnlyAllowHttps(false);

		//mock HttpClient instance
		HttpClient httpClientMock = mock(HttpClient.class);
		when(httpClientMock.execute(Matchers.any(HttpUriRequest.class))).thenReturn(httpResponseMock);

		//execute send on client
		RealexClient realexClient = new RealexClient(SampleXmlValidationUtils.SECRET, httpClientMock, httpConfiguration);
		ThreeDSecureResponse response = realexClient.send(request);

		//validate response
		checkUnmarshalledThreeDSecureEnrolledResponse(response);
	}

	/**
	 * Test receiving a response which has an invalid hash. 
	 */
	@Test(expected = RealexException.class)
	public void sendThreeDSecureInvalidResponseHashTest() throws ClientProtocolException, IOException {
		//get sample response XML
		File file = new File(this.getClass().getResource(THREE_D_SECURE_VERIFY_ENROLLED_RESPONSE_XML_PATH).getPath());
		StreamSource source = new StreamSource(file);
		ThreeDSecureResponse fromXmlResponse = new ThreeDSecureResponse().fromXml(source);

		//add invalid hash
		fromXmlResponse.setHash("invalid hash");

		//mock HttpResponse
		HttpResponse httpResponseMock = mock(HttpResponse.class);
		when(httpResponseMock.getEntity()).thenReturn(new StringEntity(fromXmlResponse.toXml()));
		when(httpResponseMock.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, null));

		//create empty request 
		ThreeDSecureRequest request = new ThreeDSecureRequest();

		//create configuration
		HttpConfiguration httpConfiguration = new HttpConfiguration();
		httpConfiguration.setOnlyAllowHttps(false);

		//mock HttpClient instance
		HttpClient httpClientMock = mock(HttpClient.class);
		when(httpClientMock.execute(Matchers.any(HttpUriRequest.class))).thenReturn(httpResponseMock);

		//execute send on client
		RealexClient realexClient = new RealexClient(SampleXmlValidationUtils.SECRET, httpClientMock, httpConfiguration);
		realexClient.send(request);

		//shouldn't get this far
		Assert.fail("RealexException should have been thrown before this point.");
	}
}