package eu.dnetlib.enabling.is.registry; // NOPMD

import static org.junit.Assert.*; // NOPMD
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*; // NOPMD

import java.io.IOException;
import java.io.StringWriter;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;

import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnit44Runner;
import org.xml.sax.SAXException;

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.registry.schema.OpaqueResourceValidator;
import eu.dnetlib.enabling.is.registry.schema.ValidationException;
import eu.dnetlib.enabling.is.store.rmi.ISStoreException;
import eu.dnetlib.enabling.is.store.rmi.ISStoreService;
import eu.dnetlib.enabling.tools.CompatResourceIdentifierResolverImpl;
import eu.dnetlib.enabling.tools.OpaqueResource;
import eu.dnetlib.enabling.tools.ResourceType;
import eu.dnetlib.enabling.tools.StaticServiceLocator;
import eu.dnetlib.enabling.tools.StringOpaqueResource;
import eu.dnetlib.enabling.tools.XQueryUtils;
import eu.dnetlib.enabling.tools.XQueryUtilsImpl;

/**
 * test registry service.
 *
 * @author marko
 *
 */
@RunWith(MockitoJUnit44Runner.class)
public class ISRegistryServiceImplTest { // NOPMD

	/**
	 * a test resource type.
	 */
	private static final String REPOSITORY_TYPE = "RepositoryServiceResourceType";

	/**
	 * TODO: use a shared constant for this.
	 */
	private static final String DB_DRIVER = "/db/DRIVER/";

	/**
	 * class under test.
	 */
	private transient ISRegistryServiceImpl registry;

	/**
	 * store service mock.
	 */
	@Mock
	private transient ISStoreService storeService;

	/**
	 * lookup service mock.
	 */
	@Mock
	private transient ISLookUpService lookUpService;

	/**
	 * xml source of a test profile, loaded from test-profile.xml.
	 */
	private transient String profileSource;

	/**
	 * resource identifier resolver, converts IDs to filename/collection pairs.
	 */
	private transient CompatResourceIdentifierResolverImpl resIdResolver;

	/**
	 * schema validator.
	 */
	@Mock
	private transient OpaqueResourceValidator validator;

	/**
	 * xquery utils, used for xmldb root path.
	 */
	private final transient XQueryUtils xqueryUtils = new XQueryUtilsImpl();

	/**
	 * common initialization.
	 *
	 * @throws IOException
	 *             shouldn't happen
	 */
	@Before
	public void setUp() throws IOException {
		registry = new ISRegistryServiceImpl();
		registry.setXqueryUtils(new XQueryUtilsImpl());
		resIdResolver = new CompatResourceIdentifierResolverImpl();
		registry.setResIdResolver(resIdResolver);

		final CompatPendingResourceManagerImpl pendingManager = new CompatPendingResourceManagerImpl();
		registry.setPendingManager(pendingManager);
		pendingManager.setRegistryLocator(new StaticServiceLocator<ISRegistryService>(registry));

		registry.setStoreLocator(new StaticServiceLocator<ISStoreService>(storeService));
		registry.setLookupLocator(new StaticServiceLocator<ISLookUpService>(lookUpService));

		registry.setResourceValidator(validator);

		final StringWriter writer = new StringWriter();
		IOUtils.copy(getClass().getResourceAsStream("test-profile.xml"), writer);
		profileSource = writer.getBuffer().toString();

	}

	/**
	 * test the deletion of a profile.
	 *
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws XPathExpressionException
	 *             shouldn't happen
	 * @throws SAXException
	 *             shouldn't happen
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ParserConfigurationException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             shouldn't happen
	 */
	@Test
	public void testDeleteProfile() throws ISRegistryException, XPathExpressionException, SAXException, IOException, ParserConfigurationException,
			ISStoreException {
		final OpaqueResource resource = new StringOpaqueResource(profileSource);
		final String resId = resource.getResourceId();

		boolean catched = false; // NOPMD
		try {
			registry.deleteProfile(resId);
		} catch (ISRegistryDocumentNotFoundException e) {
			catched = true;
		}

		assertTrue("exception raised", catched);
		verify(storeService).deleteXML(registry.getResIdResolver().getFileName(resId), DB_DRIVER + registry.getResIdResolver().getCollectionName(resId));

		assertNotNull("dummy", storeService);
	}

	/**
	 * test the deletion of an inexitent profile. It should throwa specific exception
	 *
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws XPathExpressionException
	 *             shouldn't happen
	 * @throws SAXException
	 *             shouldn't happen
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ParserConfigurationException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             shouldn't happen
	 */
	@Test(expected = ISRegistryDocumentNotFoundException.class)
	public void testDeleteInexistentProfile() throws ISRegistryException, XPathExpressionException, SAXException, IOException,
			ParserConfigurationException, ISStoreException {
		final OpaqueResource resource = new StringOpaqueResource(profileSource);
		final String resId = resource.getResourceId();
		final String wrongId = "xx" + resId;

		when(storeService.deleteXML(registry.getResIdResolver().getFileName(wrongId), DB_DRIVER + registry.getResIdResolver().getCollectionName(wrongId)))
				.thenReturn(false);

		registry.deleteProfile(wrongId);
	}

	/**
	 * test register profile.
	 *
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             shouldn't happen
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ParserConfigurationException
	 *             shouldn't happen
	 * @throws SAXException
	 *             shouldn't happen
	 * @throws XPathExpressionException
	 *             shouldn't happen
	 */
	@Test
	public void testRegisterProfile() throws ISRegistryException, ISStoreException, IOException, XPathExpressionException, SAXException,
			ParserConfigurationException {
		final String newId = registry.registerProfile(profileSource);

		verify(storeService).insertXML(eq(resIdResolver.getFileName(newId)), eq(DB_DRIVER + resIdResolver.getCollectionName(newId)), anyString());

		final OpaqueResource resource = new StringOpaqueResource(profileSource);
		assertFalse("new identifier is generated", newId.equals(resource.getResourceId()));

	}

	/**
	 * test update profile.
	 *
	 * @throws XPathExpressionException
	 *             should'nt happen
	 * @throws SAXException
	 *             should'nt happen
	 * @throws IOException
	 *             should'nt happen
	 * @throws ParserConfigurationException
	 *             should'nt happen
	 * @throws ISRegistryException
	 *             should'nt happen
	 * @throws ISStoreException
	 *             should'nt happen
	 */
	@Test
	public void testUpdateProfile() throws XPathExpressionException, SAXException, IOException, ParserConfigurationException, ISRegistryException,
			ISStoreException {
		final OpaqueResource resource = new StringOpaqueResource(profileSource);

		final String fileName = resIdResolver.getFileName(resource.getResourceId());
		final String fileColl = resIdResolver.getCollectionName(resource.getResourceId());
		when(storeService.updateXML(eq(fileName), eq(DB_DRIVER + fileColl), anyString())).thenReturn(true);

		when(storeService.getXML(fileName, DB_DRIVER + fileColl)).thenReturn(resource.asString());

		final boolean res = registry.updateProfile(resource.getResourceId(), resource.asString(), resource.getResourceType());
		assertTrue("success", res);
	}

	/**
	 * Check that the xml schema is correctly validated even on updateProfile.
	 *
	 * @throws XPathExpressionException cannot happen
	 * @throws SAXException cannot happen
	 * @throws IOException cannot happen
	 * @throws ParserConfigurationException cannot happen
	 * @throws ISRegistryException mock
	 * @throws ValidationException mock
	 */
	@Test(expected = ISRegistryException.class)
	public void testUpdateProfileSchemaValidate() throws XPathExpressionException, SAXException, IOException, ParserConfigurationException,
			ISRegistryException, ValidationException {
		final OpaqueResource resource = new StringOpaqueResource(profileSource);

		doThrow(new ValidationException("invalid xml")).when(validator).validate((OpaqueResource) anyObject());

		registry.updateProfile(resource.getResourceId(), "<invalid/>", resource.getResourceType());
	}

	/**
	 * test profile validation.
	 *
	 * @throws XPathExpressionException
	 *             shouldn't happen
	 * @throws SAXException
	 *             shouldn't happen
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ParserConfigurationException
	 *             shouldn't happen
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             shouldn't happen
	 * @throws ISLookUpException
	 *             shouldn't happen
	 */
	@Test
	public void testValidateProfile() throws XPathExpressionException, SAXException, IOException, ParserConfigurationException, ISRegistryException,
			ISStoreException, ISLookUpException {
		final OpaqueResource resource = new StringOpaqueResource(profileSource);

		final String oldId = registry.insertProfileForValidation(resource.getResourceType(), resource.asString());
		resource.setResourceKind("PendingRepositoryResources"); // simulate the validation

		resource.setResourceId(oldId);
		when(lookUpService.getResourceProfile(oldId)).thenReturn(resource.asString());
		when(storeService.deleteXML(resIdResolver.getFileName(oldId), DB_DRIVER + resIdResolver.getCollectionName(oldId))).thenReturn(true);

		final String newId = registry.validateProfile(oldId);

		verify(storeService).deleteXML(resIdResolver.getFileName(oldId), DB_DRIVER + resIdResolver.getCollectionName(oldId));
		verify(storeService).insertXML(eq(resIdResolver.getFileName(newId)), eq(DB_DRIVER + resIdResolver.getCollectionName(newId)), anyString());

		assertFalse("the id should be be unstable", resIdResolver.getFileName(oldId).equals(resIdResolver.getFileName(newId)));
	}

	/**
	 * test invalidate profile.
	 *
	 * @throws XPathExpressionException
	 *             shouldn't happen
	 * @throws SAXException
	 *             shouldn't happen
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ParserConfigurationException
	 *             shouldn't happen
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             shouldn't happen
	 * @throws ISLookUpException
	 *             shouldn't happen
	 */
	@Test
	public void testInvalidateProfile() throws XPathExpressionException, SAXException, IOException, ParserConfigurationException, ISRegistryException,
			ISStoreException, ISLookUpException {

		final OpaqueResource resource = new StringOpaqueResource(profileSource);
		resource.setResourceKind("PendingResources");

		final String oldId = registry.registerProfile(resource.asString());

		resource.setResourceId(oldId);
		when(lookUpService.getResourceProfile(oldId)).thenReturn(resource.asString());
		when(storeService.deleteXML(resIdResolver.getFileName(oldId), DB_DRIVER + resIdResolver.getCollectionName(oldId))).thenReturn(true);

		final String newId = registry.invalidateProfile(oldId);

		verify(storeService).deleteXML(resIdResolver.getFileName(oldId), DB_DRIVER + resIdResolver.getCollectionName(oldId));
		verify(storeService).insertXML(eq(resIdResolver.getFileName(newId)), eq(DB_DRIVER + resIdResolver.getCollectionName(newId)), anyString());

		assertFalse("the id should be be unstable", resIdResolver.getFileName(oldId).equals(resIdResolver.getFileName(newId)));

	}

	/**
	 * test that the resource kind is correctly changed when inserting for validation.
	 *
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             shouldn't happen
	 * @throws XPathExpressionException
	 *             shouldn't happen
	 * @throws SAXException
	 *             shouldn't happen
	 * @throws ParserConfigurationException
	 *             shouldn't happen
	 */
	@Test
	public void testInsertProfileForValidation() throws IOException, ISRegistryException, ISStoreException, XPathExpressionException, SAXException,
			ParserConfigurationException {

		final OpaqueResource resource = new StringOpaqueResource(profileSource);
		final String newId = registry.insertProfileForValidation(resource.getResourceType(), resource.asString());

		verify(storeService).insertXML(eq(resIdResolver.getFileName(newId)), eq(DB_DRIVER + "PendingRepositoryResources/RepositoryServiceResourceType"),
				anyString());

		verify(storeService, never()).deleteXML(anyString(), anyString());

		assertEquals("check pending id", "PendingRepositoryResources/RepositoryServiceResourceType", resIdResolver.getCollectionName(newId));
	}

	/**
	 * The ISRegistry's insertProfileForValidation contract specifies that an exception should be raised if the argument
	 * doesn't match the type.
	 *
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ISRegistryException
	 *             expected
	 */
	@Test(expected = ISRegistryException.class)
	public void testInsertProfileForValidationInvalid() throws IOException, ISRegistryException {
		registry.insertProfileForValidation("WrongRepositoryServiceResourceType", profileSource);
	}

	/**
	 * test resource type addition.
	 *
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             cannot happen, mock
	 */
	@Test
	public void testAddResourceType() throws ISRegistryException, IOException, ISStoreException {
		final StringWriter resourceSchema = new StringWriter();

		IOUtils.copy(getClass().getResourceAsStream("RepositoryServiceResourceType.xsd"), resourceSchema);
		assertNotNull("check schema", resourceSchema.toString());

		registry.addResourceType(REPOSITORY_TYPE, resourceSchema.toString());

		verify(storeService).insertXML(REPOSITORY_TYPE, xqueryUtils.getRootCollection() + ResourceType.RESOURCE_TYPES, resourceSchema.toString());
	}

	/**
	 * test resource type deletion.
	 *
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             cannot happen, mock
	 */
	@Test
	public void testDeleteResourceType() throws ISRegistryException, ISStoreException {
		assertNotNull("dummy", registry);

		registry.deleteResourceType(REPOSITORY_TYPE, false);

		verify(storeService).deleteXML(REPOSITORY_TYPE, xqueryUtils.getRootCollection() + ResourceType.RESOURCE_TYPES);
	}

	/**
	 * register secure profile.
	 *
	 * @throws ISRegistryException
	 *             shouldn't happen
	 * @throws ISLookUpException
	 *             mocked
	 * @throws IOException
	 *             shouldn't happen
	 * @throws ISStoreException
	 *             mock
	 */
	@Test
	public void testRegisterSecureProfile() throws ISRegistryException, ISLookUpException, IOException, ISStoreException { // NOPMD
		final String resourceProfId = "12-70b2a9e0-7d53-4ffb-be43-f3c571ae21a6_VXNlckRTUmVzb3VyY2VzL1VzZXJEU1Jlc291cmNlVHlwZQ==";
		final String secureProfId = "sec123_U2VjdXJpdHlQcm9maWxlRFNSZXNvdXJjZXMvU2VjdXJpdHlQcm9maWxlRFNSZXNvdXJjZVR5cGU=";

		final StringWriter profile = new StringWriter();
		IOUtils.copy(getClass().getResourceAsStream("userProfile.xml"), profile);
		when(lookUpService.getResourceProfile(resourceProfId)).thenReturn(profile.toString());

		final StringWriter secure = new StringWriter();
		IOUtils.copy(getClass().getResourceAsStream("securityProfile.xml"), secure);
		when(lookUpService.getResourceProfile(secureProfId)).thenReturn(secure.toString());
		when(storeService.deleteXML(resIdResolver.getFileName(resourceProfId), DB_DRIVER + resIdResolver.getCollectionName(resourceProfId))).thenReturn(
				true);
		when(storeService.updateXML(eq("sec123"), eq(DB_DRIVER + "SecurityProfileDSResources/SecurityProfileDSResourceType"), anyString())).thenReturn(
				true);
		when(storeService.getXML("sec123", DB_DRIVER + "SecurityProfileDSResources/SecurityProfileDSResourceType")).thenReturn("<RESOURCE_PROFILE/>");

		registry.registerSecureProfile(resourceProfId, secureProfId);
	}
}
