package eu.dnetlib.enabling.is.sn;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.io.StringReader;
import java.io.StringWriter;

import javax.annotation.Resource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Endpoint;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryException;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.is.sn.rmi.ISSNException;
import eu.dnetlib.enabling.is.sn.rmi.ISSNService;
import eu.dnetlib.enabling.is.store.rmi.ISStoreException;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.soap.EndpointReferenceBuilder;
import eu.dnetlib.test.AbstractIntegrationContainerTest;
import eu.dnetlib.test.SpringContextSharer;

/**
 *
 * Test various real world scenarios for subscription notification.
 *
 * @author marko
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
// @ContextConfiguration
public class ISSNServiceClientTest extends AbstractIntegrationContainerTest {
	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(ISSNServiceClientTest.class);

	/**
	 * wait for async notification.
	 */
	private static final int WAIT_FOR_ASYNC = 1000;

	/**
	 * lookup locator.
	 */
	@Resource(name = "lookUpLocator")
	private transient ServiceLocator<ISLookUpService> lookupLocator;

	/**
	 * registry locator.
	 */
	@Resource(name = "registryLocator")
	private transient ServiceLocator<ISRegistryService> registryLocator;

	/**
	 * sn locator.
	 */
	@Resource(name = "snLocator")
	private transient ServiceLocator<ISSNService> snLocator;

	/**
	 * notification delegator. fetched from child context.
	 */
	private transient TestNotificationDelegator notifier;

	/**
	 * reference to the notification delegator test service.
	 */
	private transient W3CEndpointReference consumerReference;

	/**
	 * other consumer reference pointing to a non existing host.
	 */
	private W3CEndpointReference otherConsumerReference;

	/**
	 * common initialization.
	 */
	@SuppressWarnings("unchecked")
	@Before
	public void setUp() {
		log.debug("setting up a child context");

		final ApplicationContext context = new ClassPathXmlApplicationContext(
				new String[] { "eu/dnetlib/enabling/is/sn/ISSNServiceClientTest-context.xml" }, SpringContextSharer.getGlobalContext());
		final Endpoint endpoint = (Endpoint) context.getBean("testNotificationDelegatorEndpoint");
		final EndpointReferenceBuilder<Endpoint> builder = (EndpointReferenceBuilder<Endpoint>) context.getBean("jaxwsEndpointReferenceBuilder");

		consumerReference = builder.getEndpointReference(endpoint);

		StringWriter eprWriter = new StringWriter();
		StreamResult eprResult = new StreamResult(eprWriter);
		consumerReference.writeTo(eprResult);

		String eprString = eprWriter.toString();
		eprString = eprString.replace("localhost", "somehost");
		log.warn("EPR: " + eprString);
		otherConsumerReference = (W3CEndpointReference) W3CEndpointReference.readFrom(new StreamSource(new StringReader(eprString)));

		notifier = (TestNotificationDelegator) context.getBean("testNotificationDelegator", TestNotificationDelegator.class);

	}

	/**
	 * register a simple subscription and check that the notification arrives.
	 *
	 * @throws ISSNException
	 *             shoulnd't happen
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             shouldn't happen
	 * @throws ISLookUpException
	 *             shouldn't happen
	 * @throws InterruptedException
	 *             shouldn't happen
	 */
	@Test
//	@Ignore
	public void testSimpleSubscription() throws ISSNException, ISRegistryException, ISLookUpException, ISStoreException, InterruptedException {

		final String subId = snLocator.getService().subscribe(consumerReference, "UPDATE/TestResourceType", 0);
		assertNotNull("check successful subscription", subId);

		final String subId2 = snLocator.getService().subscribe(consumerReference, "UPDATE/TestResourceType", 0);
		assertNotNull("check successful subscription", subId2);
		assertEquals("avoid duplicated subscription", subId, subId2);

		log.warn("other EPR: " + otherConsumerReference);
		final String subId3 = snLocator.getService().subscribe(otherConsumerReference, "UPDATE/TestResourceType", 0);
		assertNotNull("check successful subscription", subId3);
		log.warn("subId: " + subId);
		log.warn("subId3: " + subId3);
		assertFalse("avoid duplicated subscription", subId.equals(subId3));

		final String rsId = lookupLocator
				.getService()
				.getResourceProfileByQuery(
						"for $x in collection('/db/DRIVER/TestResources/TestResourceType') where $x//TEST = '123' return $x//RESOURCE_IDENTIFIER/@value/string()");

		final NotificationDelegatorListener listener = mock(NotificationDelegatorListener.class);
		notifier.setListener(listener);

		registryLocator.getService().executeXUpdate(
				"for $x in collection('/db/DRIVER/TestResources/TestResourceType') where $x//TEST = '123' "
						+ "return update value $x//TEST/text() with '321'");

		final String newProfile = lookupLocator.getService().getResourceProfile(rsId);

		registryLocator.getService().executeXUpdate(
				"for $x in collection('/db/DRIVER/TestResources/TestResourceType') where $x//TEST = '321' "
						+ "return update value $x//TEST/text() with '123'");

		final String newProfile2 = lookupLocator.getService().getResourceProfile(rsId);

		assertFalse("ensure that the xupdate works", newProfile.equals(newProfile2));
		Thread.sleep(WAIT_FOR_ASYNC);

		//verify(listener, times(2)).notify(anyString(), anyString(), anyString(), anyString());
		verify(listener, times(2)).notify(eq(subId), startsWith("UPDATE.TestResourceType"), eq(rsId), anyString());
	}

	/**
	 * test a subscription with a single.
	 *
	 * @throws ISLookUpException
	 *             shouldn't happen
	 * @throws ISSNException
	 *             shouldn't happen
	 */
	@Test
	public void testSpecificSubscription() throws ISLookUpException, ISSNException {
		final String rsId = lookupLocator
				.getService()
				.getResourceProfileByQuery(
						"for $x in collection('/db/DRIVER/TestResources/TestResourceType') where $x//TEST = '123' return $x//RESOURCE_IDENTIFIER/@value/string()");

		assertNotNull("test resource", rsId);

		final String subId = snLocator.getService().subscribe(consumerReference, "UPDATE/TestResourceType/" + rsId, 0);
		assertNotNull("check successful subscription", subId);
	}

}
