package eu.dnetlib.enabling.aas.retrievers;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.StringReader;
import java.net.URI;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import pl.edu.icm.yadda.aas.utils.FileUtils;
import an.xacml.ExtendedRequest;
import an.xacml.context.Attribute;
import an.xacml.context.Request;
import an.xacml.context.Resource;
import an.xacml.context.Subject;
import an.xacml.converter.AttributeValueDataConverter;
import an.xacml.engine.EvaluationContext;
import an.xacml.policy.AttributeValue;
import edu.emory.mathcs.backport.java.util.Arrays;
import eu.dnetlib.common.ws.nh.NotificationConstants;
import eu.dnetlib.enabling.aas.retrievers.cache.IProfilesCache;
import eu.dnetlib.enabling.aas.retrievers.resultset.IResultSetProvider;
import eu.dnetlib.enabling.aas.ws.nh.NotificationUtils;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.sn.rmi.ISSNService;
import eu.dnetlib.enabling.resultset.ResultSetConstants;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;
import eu.dnetlib.enabling.tools.StaticServiceLocator;

/**
 * {@link CachableISLookupAttributeRetriever} test class.
 * 
 * @author mhorst
 *
 */
public class CachableISLookupAttributeRetrieverTest {

	CachableISLookupAttributeRetriever retriever = null;
	
	StaticServiceLocator<ISLookUpService> lookupLocator = null;
	
	StaticServiceLocator<ISSNService> issnLocator = null;

	public static final URI STRING_DATA_TYPE = URI.create("http://www.w3.org/2001/XMLSchema#string");
	
	String consumerReferenceURL = "http://somehost.eu/url";
	
	String supportedIdPath = "RESOURCE_PROFILE/BODY/CONFIGURATION/resourceId/text()";
	
	@Before
	public void init() {
		lookupLocator = new StaticServiceLocator<ISLookUpService>();
		issnLocator = new StaticServiceLocator<ISSNService>();
		
		retriever = new CachableISLookupAttributeRetriever();
		retriever.setAvDataConverter(new AttributeValueDataConverter());
		retriever.setLookupLocator(lookupLocator);
		retriever.setIssnServiceLocator(issnLocator);
		retriever.setNamespaceContext(null);
//		not needed as soon as I don't use http://www.icm.edu.pl/yadda# prefixed datatype
		retriever.setSerializer(null);
		retriever.setConsumerReferenceURL(consumerReferenceURL);
		retriever.setSupportedIdPath(supportedIdPath);
		retriever.init();
	}
	
	@Test
	public void testDirectXPathExpressions() throws Exception {
		XPathFactory xpathFactory = XPathFactory.newInstance();
		XPath xpath = xpathFactory.newXPath();
		
		String profileContentPath = "classpath:eu/dnetlib/enabling/aas/retrievers/islookup-attribute-retriever-test-security-profile.xml";
		String profile = FileUtils.getFileContent(profileContentPath);
		
		String expression = "RESOURCE_PROFILE/BODY/CONFIGURATION/identities[type='password']/text/text()";
		
		NodeList nList = (NodeList) xpath.evaluate(expression, 
				new InputSource(new StringReader(profile)), XPathConstants.NODESET);
		assertNotNull(nList);
		assertEquals(1, nList.getLength());
		assertEquals("somepass", nList.item(0).getNodeValue());
	}
	
	@Test
	public void testGetSingleValueForXPath() throws Exception {
		String xPath = null;
		String profileContentPath = "classpath:eu/dnetlib/enabling/aas/retrievers/islookup-attribute-retriever-test-security-profile.xml";
		final String profile = FileUtils.getFileContent(profileContentPath);
		
		xPath = CachableISLookupAttributeRetriever.DEFAULT_SUPPORTED_ID_PATH;
		String result = retriever.getSingleValueForXPath(profile, xPath);
		assertEquals("someSecProfId", result);
		
		xPath = CachableISLookupAttributeRetriever.DEFAULT_SUPPORTED_KIND_PATH;
		result = retriever.getSingleValueForXPath(profile, xPath);
		assertEquals("SecurityProfileDSResources", result);
		
		xPath = CachableISLookupAttributeRetriever.DEFAULT_SUPPORTED_TYPE_PATH;
		result = retriever.getSingleValueForXPath(profile, xPath);
		assertEquals("SecurityProfileDSResourceType", result);
		
//		checking element
		xPath = "RESOURCE_PROFILE/BODY/CONFIGURATION/resourceId/text()";
		result = retriever.getSingleValueForXPath(profile, xPath);
		assertEquals("someUserProfId", result);
	}
	
	@Test
	public void testCachingResults() throws Exception {
		final String expectedQuery = "for $el in fn:collection(" +
			"\"DRIVER/SecurityProfileDSResources/SecurityProfileDSResourceType\")" +
			"/RESOURCE_PROFILE[BODY/CONFIGURATION/resourceId/text() = 'someUserProfId'] " +
			"return $el";
		final String userProfId = "someUserProfId";
		final String predefinedRSId = "predefinedRSId";
		String profileContentPath = "classpath:eu/dnetlib/enabling/aas/retrievers/islookup-attribute-retriever-test-security-profile.xml";
		final String profileContent = FileUtils.getFileContent(profileContentPath);
		assertNotNull(profileContent);
		
//		setting lookupService mock into lookupLocator
		Mockery lookupMockery = new Mockery();
		final ISLookUpService lookupMockService = lookupMockery.mock(ISLookUpService.class);
		lookupLocator.setService(lookupMockService);
		lookupMockery.checking(new Expectations(){{
			one(lookupMockService).searchProfile(expectedQuery);
			will(returnValue(new W3CEndpointReferenceBuilder().address("someAddr").build()));
		}});
		
		//defining resultset service mock
		Mockery rsMockery = new Mockery();
		final ResultSetService resultSetMockService = rsMockery.mock(ResultSetService.class);
		rsMockery.checking(new Expectations(){{
			one(resultSetMockService).getNumberOfElements(predefinedRSId);
			will(returnValue(1));
			one(resultSetMockService).getResult(predefinedRSId,
					ResultSetConstants.RESULT_SET_FIRST_ELEMENT, 1, 
					ResultSetConstants.RESULT_SET_REQUEST_MODE_WAITING);
			will(returnValue(Arrays.asList(new String[] {profileContent})));
		}});
		
		//setting dummy ResultSetProvider into retriever
		retriever.setResultSetProvider(new IResultSetProvider() {
			public ResultSetService discover(W3CEndpointReference epr) {
				return resultSetMockService;
			}
			public String extractId(W3CEndpointReference epr) {
				return predefinedRSId;
			}
		});
		
//		setting issn mockery
		Mockery issnMockery = new Mockery();
		final ISSNService issnMockService = issnMockery.mock(ISSNService.class);
		issnLocator.setService(issnMockService);
		issnMockery.checking(new Expectations(){{
//			unfortunatelly you cannot mix matchers with exprlicit constraints
//			thats why with(equal(xxx)) clause need to be used
			one(issnMockService).subscribe(
					with(any(W3CEndpointReference.class)), 
					with(equal(NotificationUtils.buildTopicForProfId(
							NotificationConstants.TOPIC_PREFIX_DELETE, 
							"SecurityProfileDSResourceType", "someSecProfId"))), 
							with(equal(NotificationConstants.TERMINATION_TIME_INFINITE)));
			will(returnValue("someSubscrId"));
			
			one(issnMockService).subscribe(
					with(any(W3CEndpointReference.class)), 
					with(equal(NotificationUtils.buildTopicForProfId(
							NotificationConstants.TOPIC_PREFIX_UPDATE, 
							"SecurityProfileDSResourceType", "someSecProfId"))), 
							with(equal(NotificationConstants.TERMINATION_TIME_INFINITE)));
			will(returnValue("someSubscrId"));
		}});
		
//		setting cache mockery
		Mockery cacheMockery = new Mockery();
		final IProfilesCache profileCache = cacheMockery.mock(IProfilesCache.class);
		retriever.setCache(profileCache);
		cacheMockery.checking(new Expectations(){{
			one(profileCache).getProfile(userProfId);
			will(returnValue(null));
			
			one(profileCache).setProfile(userProfId, profileContent);
			
//			the third test call expected, retrieving data from cache
			one(profileCache).getProfile(userProfId);
			will(returnValue(profileContent));
		}});
		
		Subject subj = new Subject(
				URI.create(ISLookupAttributeRetrieverConstants.SUBJECT_CATEGORY),
				new Attribute[] {new Attribute(
						URI.create(ISLookupAttributeRetrieverConstants.SUBJECT_PARAM_ID),
						STRING_DATA_TYPE, 
						null, new String[] {userProfId})});
		//TODO need to setup dummy element to overcome stupid problem, xacml module is a C_R_A_P!!!
		Element element = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().createElement("dummy");
		Request baseRequest = new Request(element, new Subject[] {subj}, new Resource[0], null, null);
		EvaluationContext context = new EvaluationContext(new ExtendedRequest(baseRequest,null, null));
		//notice, query used in this test is not checked against xquery spec
		//anyway RESOURCE_PROFILE prefix in idPath should be removed by retriever
		String requestCtxPath = "SecurityProfileDSResources$SecurityProfileDSResourceType$" +
				"RESOURCE_PROFILE/BODY/CONFIGURATION/resourceId/text()" + 
				"#RESOURCE_PROFILE/BODY/CONFIGURATION/identities[type='password']/text/text()";
		AttributeValue[] result = retriever.retrieveAttributeValues(
				context, requestCtxPath, STRING_DATA_TYPE, null, null);
		assertNotNull(result);
		assertEquals(1, result.length);
		assertNotNull(result[0]);
		assertEquals(STRING_DATA_TYPE, result[0].getDataType());
		assertEquals("somepass", result[0].getValue());
		
		//checking request cache
		assertEquals(profileContent, ((ExtendedRequest)context.getRequest()).getRequestCache().
				getCachedData("SecurityProfileDSResources|SecurityProfileDSResourceType|" +
						"RESOURCE_PROFILE/BODY/CONFIGURATION/resourceId/text()", 
						userProfId).getSingleDataObject());
		
//		checking second time in order to verify if the cache was used
//		if request cache wasn't used, the mock objects should complain - one(T);
//		request cache is called first place, application cache is not being used
		result = retriever.retrieveAttributeValues(
				context, requestCtxPath, STRING_DATA_TYPE, null, null);
		assertNotNull(result);
		assertEquals(1, result.length);
		assertNotNull(result[0]);
		assertEquals(STRING_DATA_TYPE, result[0].getDataType());
		assertEquals("somepass", result[0].getValue());
		
//		cleaning up request cache to reach application cache
		((ExtendedRequest)context.getRequest()).getRequestCache().setCachedData(
				"SecurityProfileDSResources|SecurityProfileDSResourceType|" +
				"RESOURCE_PROFILE/BODY/CONFIGURATION/resourceId/text()", 
				userProfId, null);
		
//		re-testing this time result should be retrieved from application cache
		result = retriever.retrieveAttributeValues(
				context, requestCtxPath, STRING_DATA_TYPE, null, null);
		assertNotNull(result);
		assertEquals(1, result.length);
		assertNotNull(result[0]);
		assertEquals(STRING_DATA_TYPE, result[0].getDataType());
		assertEquals("somepass", result[0].getValue());
	}
	
	@Test
	public void testHandlingDeleteNotifications() {
		final String userProfId = "someUserProfId";
		String profileContentPath = "classpath:eu/dnetlib/enabling/aas/retrievers/islookup-attribute-retriever-test-security-profile.xml";
		final String profile = FileUtils.getFileContent(profileContentPath);
		retriever.setSupportedIdPath(supportedIdPath);
//		setting cache mockery
		Mockery cacheMockery = new Mockery();
		final IProfilesCache profileCache = cacheMockery.mock(IProfilesCache.class);
		retriever.setCache(profileCache);
		cacheMockery.checking(new Expectations(){{
			one(profileCache).removeProfile(userProfId);
			will(returnValue(profile));
		}});
		assertTrue(retriever.notify("irrelevant", 
				NotificationConstants.TOPIC_PREFIX_DELETE + "irrelevant", 
				"irrelevant", profile));
		
//		no call to the cache should be made, unsupported type
		retriever.setSupportedProfileType("someDifferentProfileType");
		assertTrue(retriever.notify("irrelevant", 
				NotificationConstants.TOPIC_PREFIX_DELETE + "irrelevant", 
				"irrelevant", profile));
	}
	
	@Test
	public void testHandlingUpdateNotifications() {
		final String userProfId = "someUserProfId";
		String profileContentPath = "classpath:eu/dnetlib/enabling/aas/retrievers/islookup-attribute-retriever-test-security-profile.xml";
		final String profile = FileUtils.getFileContent(profileContentPath);
		retriever.setSupportedIdPath(supportedIdPath);
//		setting cache mockery
		Mockery cacheMockery = new Mockery();
		final IProfilesCache profileCache = cacheMockery.mock(IProfilesCache.class);
		retriever.setCache(profileCache);
		cacheMockery.checking(new Expectations(){{
			one(profileCache).getProfile(userProfId);
			will(returnValue("xxx"));
			
			one(profileCache).setProfile(userProfId, profile);
		}});
		assertTrue(retriever.notify("irrelevant", 
				NotificationConstants.TOPIC_PREFIX_UPDATE + "irrelevant", 
				"irrelevant", profile));
		
//		no call to the cache should be made, unsupported type
		retriever.setSupportedProfileType("someDifferentProfileType");
		assertTrue(retriever.notify("irrelevant", 
				NotificationConstants.TOPIC_PREFIX_UPDATE + "irrelevant", 
				"irrelevant", profile));
	}
}
