package eu.dnetlib.enabling.aas.retrievers;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

import java.net.URI;
import java.security.InvalidParameterException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Element;

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.enabling.aas.retrievers.resultset.IResultSetProvider;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.resultset.ResultSetConstants;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;
import eu.dnetlib.enabling.tools.StaticServiceLocator;

/**
 * {@link ISLookupAttributeRetriever} test class.
 * 
 * @author mhorst
 *
 */
public class ISLookupAttributeRetrieverTest {

	ISLookupAttributeRetriever retriever = null;
	
	StaticServiceLocator<ISLookUpService> lookupLocator = null;
	
	public static final URI STRING_DATA_TYPE = URI.create("http://www.w3.org/2001/XMLSchema#string");
	
	@Before
	public void init() {
		ISLookUpService lookupService = null;
		lookupLocator = new StaticServiceLocator<ISLookUpService>();
		lookupLocator.setService(lookupService);
		
		retriever = new ISLookupAttributeRetriever();
		retriever.setAvDataConverter(new AttributeValueDataConverter());
		retriever.setLookupLocator(lookupLocator);
		retriever.setNamespaceContext(null);
//		not needed as soon as I don't use http://www.icm.edu.pl/yadda# prefixed datatype
		retriever.setSerializer(null);
		
		retriever.init();
	}
	
	@Test
	public void testParseRequestCtxPath() {
		String requestCtxPath = null;
		ContextPathDTO dto = retriever.parseRequestCtxPath(requestCtxPath);
		assertNotNull(dto);
		assertNull(dto.getId());
		assertNull(dto.getIdPath());
		assertNull(dto.getValuePath());
		
		requestCtxPath = " ";
		dto = retriever.parseRequestCtxPath(requestCtxPath);
		assertNotNull(dto);
		assertNull(dto.getId());
		assertNull(dto.getIdPath());
		assertNull(dto.getValuePath());
		
//		direct profile retrieval mode
		requestCtxPath = "someValuePath";
		dto = retriever.parseRequestCtxPath(requestCtxPath);
		assertNotNull(dto);
		assertNull(dto.getId());
		assertEquals(requestCtxPath, dto.getValuePath());
		assertNull(dto.getIdPath());
		
		requestCtxPath = "someIdPath#someValuePath";
		dto = retriever.parseRequestCtxPath(requestCtxPath);
		assertNotNull(dto);
		assertNull(dto.getId());
		assertEquals("someIdPath", dto.getIdPath());
		assertEquals("someValuePath", dto.getValuePath());
		assertNull(dto.getProfileKind());
		assertNull(dto.getProfileType());
		
		requestCtxPath = "resourceKind$resourceType$someIdPath#someValuePath";
		dto = retriever.parseRequestCtxPath(requestCtxPath);
		assertNotNull(dto);
		assertNull(dto.getId());
		assertEquals("someIdPath", dto.getIdPath());
		assertEquals("someValuePath", dto.getValuePath());
		assertEquals("resourceKind", dto.getProfileKind());
		assertEquals("resourceType", dto.getProfileType());
		
		requestCtxPath = "resourceKind$resourceType$invalidToken$someIdPath#someValuePath";
		try {
			retriever.parseRequestCtxPath(requestCtxPath);
			fail("exception should be thrown!");
		} catch (InvalidParameterException e) {
//			ok
		}
	}
	
	@Test
	public void testGenerateXQuery() {
		boolean getGetFullProfileFlag = true;
		
		String requestCtxPath = "resourceKind$resourceType$someIdPath#someValuePath";
		ContextPathDTO contextPathDTO = retriever.parseRequestCtxPath(requestCtxPath);
//		no id mode
		String xquery = ISLookupAttributeRetriever.generateXQuery(contextPathDTO, getGetFullProfileFlag);
		assertEquals("for $el in fn:collection(\"DRIVER/resourceKind/resourceType\") return $el/someValuePath", xquery);
		
//		with id mode
		contextPathDTO.setId("someId");
		xquery = ISLookupAttributeRetriever.generateXQuery(contextPathDTO, getGetFullProfileFlag);
		assertEquals("for $el in fn:collection(\"DRIVER/resourceKind/resourceType\")/RESOURCE_PROFILE[someIdPath = 'someId'] " +
				"return $el/someValuePath", xquery);
		
//		no idPath mode
		requestCtxPath = "someValuePath";
		contextPathDTO = retriever.parseRequestCtxPath(requestCtxPath);
		xquery = ISLookupAttributeRetriever.generateXQuery(contextPathDTO, getGetFullProfileFlag);
		assertEquals("for $el in fn:collection(\"DRIVER\") return $el/someValuePath", xquery);
				
//		no resourceKind and resourceType mode
		requestCtxPath = "someIdPath#someValuePath";
		contextPathDTO = retriever.parseRequestCtxPath(requestCtxPath);
		contextPathDTO.setId("someId");
		xquery = ISLookupAttributeRetriever.generateXQuery(contextPathDTO, getGetFullProfileFlag);
		assertEquals("for $el in fn:collection(\"DRIVER\")/RESOURCE_PROFILE[someIdPath = 'someId'] " +
				"return $el/someValuePath", xquery);
		
//		no attach value path mode
		requestCtxPath = "someIdPath#someValuePath";
		contextPathDTO = retriever.parseRequestCtxPath(requestCtxPath);
		contextPathDTO.setId("someId");
		xquery = ISLookupAttributeRetriever.generateXQuery(contextPathDTO, false);
		assertEquals("for $el in fn:collection(\"DRIVER\")/RESOURCE_PROFILE[someIdPath = 'someId'] " +
				"return $el", xquery);
	}
	
	@Test
	public void testRetrieveInDirectAccessMode() throws Exception {
//		use mockery, only one call to the IS-LU is expected
		final String profileId = "dummyProfileId";
		String profileContentPath = "classpath:eu/dnetlib/enabling/aas/retrievers/islookup-attribute-retriever-test-user-profile.xml";
		final String profileContent = FileUtils.getFileContent(profileContentPath);
		assertNotNull(profileContent);
		
		Mockery lookupMockery = new Mockery();
		final ISLookUpService lookupMockService = lookupMockery.mock(ISLookUpService.class);
		lookupLocator.setService(lookupMockService);
		lookupMockery.checking(new Expectations(){{
			one(lookupMockService).getResourceProfile(profileId);
			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[] {profileId})});
//		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));
		String requestCtxPath = "RESOURCE_PROFILE/HEADER/RESOURCE_IDENTIFIER/@value";
		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("someUserProfId", result[0].getValue());
		
//		checking cache
		assertEquals(profileContent, ((ExtendedRequest)context.getRequest()).getRequestCache().
				getCachedData(ISLookupAttributeRetriever.DIRECT_ID_CACHED_DATA_TYPE, profileId).getSingleDataObject());
		
//		checking second time in order to verify if the cache was used
//		if cache wasn't used, the mock objects should complain - one(T);
		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("someUserProfId", result[0].getValue());
	}
	
	@Test
	public void testRetrieveInXPathMode() throws Exception {
		final String expectedQuery = "for $el in fn:collection(" +
				"\"DRIVER/UserDSResources/UserDSResourceType\")" +
				"/RESOURCE_PROFILE[BODY/CONFIGURATION/PERSONAL_INFO/EMAIL/@value = 'someEmailAddress'] " +
				"return $el";
		final String emailAddress = "someEmailAddress";
		final String predefinedRSId = "predefinedRSId";
		String profileContentPath = "classpath:eu/dnetlib/enabling/aas/retrievers/islookup-attribute-retriever-test-user-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;
			}
		});
		
		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[] {emailAddress})});
//		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 = "UserDSResources$UserDSResourceType$" +
				"RESOURCE_PROFILE/BODY/CONFIGURATION/PERSONAL_INFO/EMAIL/@value" + 
				"#RESOURCE_PROFILE/HEADER/RESOURCE_IDENTIFIER/@value";
		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("someUserProfId", result[0].getValue());
		
//		checking cache
		assertEquals(profileContent, ((ExtendedRequest)context.getRequest()).getRequestCache().
				getCachedData("UserDSResources|UserDSResourceType|RESOURCE_PROFILE/BODY/CONFIGURATION/PERSONAL_INFO/EMAIL/@value", 
						emailAddress).getSingleDataObject());
		
//		checking second time in order to verify if the cache was used
//		if cache wasn't used, the mock objects should complain - one(T);
		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("someUserProfId", result[0].getValue());
	}
	
	@Test
	public void testRetrieveInXPathModeUsingValuePathMode() throws Exception {
//		retrieving exact value instead of the full profile
		retriever.setGetFullProfileFlag(false);
		
		final String expectedQuery = "for $el in fn:collection(" +
		"\"DRIVER/UserDSResources/UserDSResourceType\")" +
		"/RESOURCE_PROFILE[BODY/CONFIGURATION/PERSONAL_INFO/EMAIL/@value = 'someEmailAddress'] " +
		"return $el/HEADER/RESOURCE_IDENTIFIER/@value";
		final String emailAddress = "someEmailAddress";
		final String predefinedRSId = "predefinedRSId";
		String profileContentPath = "classpath:eu/dnetlib/enabling/aas/retrievers/islookup-attribute-retriever-test-user-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[] {"someUserProfId"})));
		}});
		
		//setting dummy ResultSetProvider into retriever
		retriever.setResultSetProvider(new IResultSetProvider() {
			public ResultSetService discover(W3CEndpointReference epr) {
				return resultSetMockService;
			}
			public String extractId(W3CEndpointReference epr) {
				return predefinedRSId;
			}
		});
		
		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[] {emailAddress})});
//		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 = "UserDSResources$UserDSResourceType$" +
				"RESOURCE_PROFILE/BODY/CONFIGURATION/PERSONAL_INFO/EMAIL/@value" + 
				"#RESOURCE_PROFILE/HEADER/RESOURCE_IDENTIFIER/@value";
		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("someUserProfId", result[0].getValue());
		
		//checking cache
		assertEquals("someUserProfId", ((ExtendedRequest)context.getRequest()).getRequestCache().
				getCachedData("UserDSResources|UserDSResourceType|" +
						"RESOURCE_PROFILE/BODY/CONFIGURATION/PERSONAL_INFO/EMAIL/@value|" +
						"RESOURCE_PROFILE/HEADER/RESOURCE_IDENTIFIER/@value", 
						emailAddress).getSingleDataObject());
		
//		checking second time in order to verify if the cache was used
//		if cache wasn't used, the mock objects should complain - one(T);
		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("someUserProfId", result[0].getValue());
	}

}
