package io.tracee.binding.springws;

import io.tracee.Tracee;
import io.tracee.TraceeBackend;
import io.tracee.TraceeConstants;
import io.tracee.configuration.TraceeFilterConfiguration;
import io.tracee.testhelper.FieldAccessUtil;
import io.tracee.testhelper.SimpleTraceeBackend;
import io.tracee.transport.SoapHeaderTransport;
import org.hamcrest.MatcherAssert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.context.MessageContext;
import org.springframework.ws.soap.SoapHeader;
import org.springframework.ws.soap.SoapHeaderElement;
import org.springframework.ws.soap.SoapHeaderException;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.xml.transform.StringResult;
import org.springframework.xml.transform.StringSource;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static io.tracee.TraceeConstants.INVOCATION_ID_KEY;
import static io.tracee.TraceeConstants.SOAP_HEADER_QNAME;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class TraceeEndpointInterceptorTest {

	private TraceeBackend backend = spy(SimpleTraceeBackend.createNonLoggingAllPermittingBackend());

	private TraceeEndpointInterceptor unit = new TraceeEndpointInterceptor(backend, null);
	private MessageContext messageContext;

	@Before
	public void setup() {
		messageContext = mock(MessageContext.class);
		when(messageContext.getRequest()).thenReturn(mock(SoapMessage.class));
		when(messageContext.getResponse()).thenReturn(mock(SoapMessage.class));
		when(((SoapMessage) messageContext.getResponse()).getSoapHeader()).thenReturn(mock(SoapHeader.class));
	}

	@Test
	public void shouldCleanupBackendAfterInvocation() throws Exception {
		unit.afterCompletion(mock(MessageContext.class), new Object(), mock(Exception.class));
		verify(backend).clear();
	}

	@Test
	public void skipIncomingHeaderProcessingIfNoSoapHeaderIsPresentAndGenerateRequestId() throws Exception {
		when(((SoapMessage) messageContext.getRequest()).getSoapHeader()).thenReturn(null);

		unit.handleRequest(messageContext, new Object());
		assertThat(backend.copyToMap(), hasKey(INVOCATION_ID_KEY));
		assertThat(backend.size(), is(1));
	}

	@Test
	public void skipIncomingHeaderProcessingIfNoTpicHeaderIsPresentAndGenerateRequestId() throws Exception {
		final SoapHeader soapHeader = mock(SoapHeader.class);
		when(((SoapMessage) messageContext.getRequest()).getSoapHeader()).thenReturn(soapHeader);
		when(soapHeader.examineHeaderElements(eq(SOAP_HEADER_QNAME))).thenReturn(Collections.<SoapHeaderElement>emptyList().iterator());

		unit.handleRequest(messageContext, new Object());
		assertThat(backend.copyToMap(), hasKey(INVOCATION_ID_KEY));
		assertThat(backend.size(), is(1));
	}

	@Test
	public void parseTpicHeaderFromRequestToTraceeBackend() throws Exception {
		final Map<String, String> context = new HashMap<>();
		context.put("our key", "is our value");
		final StringResult result = new StringResult();
		new SoapHeaderTransport().renderSoapHeader(context, result);
		final Source source = new StringSource(result.toString());

		final SoapHeader soapHeader = mock(SoapHeader.class);
		when(((SoapMessage) messageContext.getRequest()).getSoapHeader()).thenReturn(soapHeader);
		final SoapHeaderElement element = mock(SoapHeaderElement.class);
		when(element.getSource()).thenReturn(source);
		when(soapHeader.examineHeaderElements(eq(SOAP_HEADER_QNAME))).thenReturn(singletonList(element).iterator());

		unit.handleRequest(messageContext, new Object());
		assertThat(backend.size(), is(2));
		assertThat(backend.copyToMap(), hasKey(INVOCATION_ID_KEY));
		assertThat(backend.copyToMap(), hasEntry("our key", "is our value"));
	}

	@Test
	public void handleSoapHeaderExceptionGraceful() {
		final SoapHeader soapHeader = mock(SoapHeader.class);
		when(soapHeader.examineHeaderElements(eq(SOAP_HEADER_QNAME))).thenThrow(new SoapHeaderException("test exception"));
		when(((SoapMessage) messageContext.getRequest()).getSoapHeader()).thenReturn(soapHeader);
		unit.handleRequest(messageContext, new Object());
		assertThat(backend.size(), is(1));
		assertThat(backend.copyToMap(), hasKey(INVOCATION_ID_KEY));
	}

	@Test
	public void doNotAddTpicHeaderToResponseIfBackendIsEmpty() throws Exception {
		unit.handleRequest(messageContext, new Object());
		verify(((SoapMessage) messageContext.getResponse()).getSoapHeader(), never()).addHeaderElement(eq(SOAP_HEADER_QNAME));
	}

	@Test
	public void catchJaxbExceptionOnRenderingAndReturnWithoutException() throws Exception {
		backend.put("our key", "is our value");
		when(((SoapMessage) messageContext.getResponse()).getSoapHeader()).thenReturn(mock(SoapHeader.class));
		unit.handleResponse(messageContext, new Object());
		verify(((SoapMessage) messageContext.getResponse()).getSoapHeader()).getResult();
	}

	@Test
	public void renderTpicContextToResultForResponses() throws Exception {
		backend.put("our key", "is our value");
		final StringWriter writer = new StringWriter();
		final SoapHeader soapHeader = mock(SoapHeader.class);
		when(((SoapMessage) messageContext.getResponse()).getSoapHeader()).thenReturn(soapHeader);
		when(soapHeader.getResult()).thenReturn(new StreamResult(writer));
		unit.handleResponse(messageContext, new Object());
		assertThat(writer.toString(), containsString("<entry key=\"our key\">is our value</entry>"));
	}

	@Test
	public void renderTpicContextToResultForFaults() throws Exception {
		backend.put("our key", "is our value");
		final StringWriter writer = new StringWriter();
		final SoapHeader soapHeader = mock(SoapHeader.class);
		when(((SoapMessage) messageContext.getResponse()).getSoapHeader()).thenReturn(soapHeader);
		when(soapHeader.getResult()).thenReturn(new StreamResult(writer));
		unit.handleFault(messageContext, new Object());
		assertThat(writer.toString(), containsString("<entry key=\"our key\">is our value</entry>"));
	}

	@Test
	public void skipProcessingWithWrongMessageTypeOnRequest() throws Exception {
		when(messageContext.getRequest()).thenReturn(mock(WebServiceMessage.class));
		unit.handleRequest(messageContext, new Object());
		assertThat(backend.copyToMap(), hasKey(TraceeConstants.INVOCATION_ID_KEY));
	}

	@Test
	public void skipProcessingWithWrongMessageTypeOnResponse() throws Exception {
		when(messageContext.getResponse()).thenReturn(mock(WebServiceMessage.class));
		unit.handleResponse(messageContext, new Object());
		assertThat(backend.isEmpty(), is(true));
	}

	@Test
	public void defaultConstructorUsesDefaultProfile() {
		final TraceeEndpointInterceptor interceptor = new TraceeEndpointInterceptor();
		MatcherAssert.assertThat((String) FieldAccessUtil.getFieldVal(interceptor, "profile"), is(TraceeFilterConfiguration.Profile.DEFAULT));
	}

	@Test
	public void defaultConstructorUsesTraceeBackend() {
		final TraceeEndpointInterceptor interceptor = new TraceeEndpointInterceptor();
		MatcherAssert.assertThat((TraceeBackend) FieldAccessUtil.getFieldVal(interceptor, "backend"), is(Tracee.getBackend()));
	}

	@Test
	public void constructorStoresProfileNameInternal() {
		final TraceeEndpointInterceptor interceptor = new TraceeEndpointInterceptor("testProf");
		MatcherAssert.assertThat((String) FieldAccessUtil.getFieldVal(interceptor, "profile"), is("testProf"));
	}
}