package eu.dnetlib.data.sts.das;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import eu.dnetlib.data.sts.ds.DepotServiceHelper;
import eu.dnetlib.data.sts.ds.IDepotService;
import eu.dnetlib.data.sts.ds.StorageConstants;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;

import pl.edu.icm.driver.factories.UniBiBeansFactory;
import pl.edu.icm.driver.is.ISConstants;
import pl.edu.icm.driver.is.ISUtils;
import pl.edu.icm.driver.tester.TestDescription;
import pl.edu.icm.driver.tester.runner.Parameterized4TestCase;
import pl.edu.icm.driver.tester.runner.TestJobContext;
import pl.edu.icm.driver.utils.PropertiesKeys;

/**
 * {@link IDataAccessService} test class.
 * 
 * @author mhorst
 *
 */
@TestDescription(
		name="DataAccess Service test suite",
		description="Performs all required functionality tests of DataAccess service.\n" +
				"Expected properties:\n" +
				"unibi.das.service.location - localisation of DataAccess service to be tested\n" +
				"unibi.depot.service.location - localisation of Depot service for storing data\n" +
				"Optional properties:\n" +
				"text.txt.location - remote txt file location"
)
public class DataAccessServiceTest extends Parameterized4TestCase {

	protected static final Logger log = Logger.getLogger(DataAccessServiceTest.class);
	
	IDepotService depotService;
	IDataAccessService dataAccessService;
	
	String depotServiceLocation;
	String dataAccessServiceLocation;
	
	String textTxtLocation = null;
	
	long sleepTime = 1000;
	int triesCount = 5;
	
	/* (non-Javadoc)
	 * @see pl.edu.icm.driver.tester.ParameterizedTest#setParameters(java.util.Map)
	 */
	@Override
	public void setParameters(Map<Object, Object> parameters) {
		depotServiceLocation = (String) parameters.get(
				PropertiesKeys.SERVICE_LOCATION_DEPOT);
		log.info("TestCase depotServiceLocation: " + 
				depotServiceLocation);
		dataAccessServiceLocation = (String) parameters.get(
				PropertiesKeys.SERVICE_LOCATION_DAS);
		log.info("TestCase dataAccessServiceLocation: " + 
				dataAccessServiceLocation);
		
		textTxtLocation = (String) parameters.get(PropertiesKeys.TEXT_TXT_LOCATION);
	}

	@Before
	public void setUp() throws Exception {
		ResourceBundle bundle = ResourceBundle.getBundle("pl.edu.icm.driver.is.auxiliary");
		if (textTxtLocation==null)
			textTxtLocation = bundle.getString(PropertiesKeys.TEXT_TXT_LOCATION);
		
		UniBiBeansFactory ubBeansFactory = new UniBiBeansFactory();
		dataAccessService = ubBeansFactory.getDAS(dataAccessServiceLocation);
		depotService = ubBeansFactory.getDepot(depotServiceLocation);
	}
	
	@After
	public void tearDown() throws Exception {

	}

	@Test
	@TestDescription(
			planId="das.functionality",
			description="Retrieves version number from identity call in DataAccess service. " +
					"Stores it in the result with description"
			)
	public void testIdentify() throws Exception {
		String identify = dataAccessService.identify();
		assertNotNull(identify);
		assertTrue(identify.length()>0);
		System.out.println("service version: "+identify);
		TestJobContext.storeResult(identify, "service version");
	}

	@Test
	@TestDescription(
			planId="das.functionality",
			description="Verifies if service is able to provide uris"
			)
	public void testStoreLookupRS() throws Exception {
		String stId = null;
		try {
			stId = depotService.createStore(null, StorageConstants.DEFAULT_MAX_ST_DS_SIZE);
			assertNotNull(stId);
			List<String> data = new ArrayList<String>();
			String uriObjectId1 = "test_id_"+System.currentTimeMillis();
			data.add(DepotServiceHelper.buildStoreRecord(uriObjectId1, 
					StorageConstants.STORE_DATATYPE_URI, textTxtLocation));
			String uriObjectId2 = "test_id_"+(System.currentTimeMillis() + 1);
			data.add(DepotServiceHelper.buildStoreRecord(uriObjectId2, 
					StorageConstants.STORE_DATATYPE_URI, textTxtLocation));
			String actionId = depotService.storeObjects(
					stId, data, StorageConstants.STORING_TYPE_REFRESH);
//			wait until storing is finished
			boolean actionFinished = false;
			for (int i=0; i<triesCount; i++) {
				Thread.sleep(sleepTime);
				String storingStatus = depotService.storingCallback(
						stId, actionId, false);
				if (isStoringStatusDone(storingStatus)) {
					actionFinished = true;
					break;
				}
			}
			assertTrue(actionFinished);
//			validate store lookup RS
			W3CEndpointReference epr = dataAccessService.storeLookUpRS(stId);
			assertNotNull(epr);
			ResultSetService resultSetService = epr.getPort(ResultSetService.class);
			assertNotNull(resultSetService);
			String rsId = ISUtils.extractResultSetId(epr);
			assertNotNull(rsId);

//			waiting until RS is closed
			int maxTriesCount = 5;
			int triesCount = 0;
			while(!ISConstants.RESULT_SET_STATUS_CLOSED.equals(
					resultSetService.getRSStatus(rsId))) {
				Thread.sleep(1000);
				triesCount++;
				if (triesCount>maxTriesCount) {
					fail("rs is not closed, maxTriesCount: " + maxTriesCount + 
							" was exceeded!");
				}
			}
			
			assertEquals(2, resultSetService.getNumberOfElements(rsId));
			List<String> results = resultSetService.getResult(rsId, 1, 2, 
					ISConstants.RESULT_SET_REQUEST_MODE_WAITING);
			assertNotNull(results);
			assertEquals(2, results.size());
			for (String currentResult : results) {
				assertNotNull(currentResult);
				assertTrue(currentResult.length()>0);
//				TODO may want to try to download this file for URI
			}
			
		} finally {
			if (stId!=null) {
				depotService.deleteStore(stId);
			}
		}
	}

	@Test
	@TestDescription(
			planId="das.functionality",
			description="Checks whether service fails smoothly when " +
				"looking up for URIs with invalid id"
			)
	public void testStoreLookupRSForInvalidId() throws Exception {
		try {
			dataAccessService.storeLookUpRS("invalidStId");
			fail("exception should be thrown when " +
					"looking up for URIs with invalid id");
		} catch (DataAccessServiceException e) {
//			ok
		}
	}
	
	@Test
	@TestDescription(
			planId="das.functionality",
			description="Verifies if service is able to provide data"
			)
	public void testStoreLookupDataRS() throws Exception {
		String stId = null;
		try {
			stId = depotService.createStore(null, StorageConstants.DEFAULT_MAX_ST_DS_SIZE);
			assertNotNull(stId);
			List<String> data = new ArrayList<String>();
			String dataObjectId1 = "test_id_"+System.currentTimeMillis();
			String dataObjectContent = "some data object content";
			data.add(DepotServiceHelper.buildStoreRecord(dataObjectId1, 
					StorageConstants.STORE_DATATYPE_DATA, dataObjectContent));
			String dataObjectId2 = "test_id_"+(System.currentTimeMillis() + 1);
			data.add(DepotServiceHelper.buildStoreRecord(dataObjectId2, 
					StorageConstants.STORE_DATATYPE_DATA, dataObjectContent));
			String actionId = depotService.storeObjects(
					stId, data, StorageConstants.STORING_TYPE_REFRESH);
//			wait until storing is finished
			boolean actionFinished = false;
			for (int i=0; i<triesCount; i++) {
				Thread.sleep(sleepTime);
				String storingStatus = depotService.storingCallback(
						stId, actionId, false);
				if (isStoringStatusDone(storingStatus)) {
					actionFinished = true;
					break;
				}
			}
			assertTrue(actionFinished);
//			validate store lookup RS
			W3CEndpointReference epr = dataAccessService.storeLookUpDataRS(stId);
			assertNotNull(epr);
			ResultSetService resultSetService = epr.getPort(ResultSetService.class);
			assertNotNull(resultSetService);
			String rsId = ISUtils.extractResultSetId(epr);
			assertNotNull(rsId);
			
//			waiting until RS is closed
			int maxTriesCount = 5;
			int triesCount = 0;
			while(!ISConstants.RESULT_SET_STATUS_CLOSED.equals(
					resultSetService.getRSStatus(rsId))) {
				Thread.sleep(1000);
				triesCount++;
				if (triesCount>maxTriesCount) {
					fail("rs is not closed, maxTriesCount: " + maxTriesCount + 
							" was exceeded!");
				}
			}
			
			assertEquals(2, resultSetService.getNumberOfElements(rsId));
			List<String> results = resultSetService.getResult(rsId, 1, 2, 
					ISConstants.RESULT_SET_REQUEST_MODE_WAITING);
			assertNotNull(results);
			assertEquals(2, results.size());
			for (String currentResult : results) {
				assertNotNull(currentResult);
				assertTrue(currentResult.length()>0);
				assertTrue(currentResult.equals(dataObjectContent));
			}
			
		} finally {
			if (stId!=null) {
				depotService.deleteStore(stId);
			}
		}
	}
	
	@Test
	@TestDescription(
			planId="das.functionality",
			description="Checks whether service fails smoothly when " +
				"looking up for data with invalid Id"
			)
	public void testStoreLookupDataRSForInvalidId() throws Exception {
		try {
			dataAccessService.storeLookUpDataRS("invalidStId");
			fail("exception should be thrown when " +
					"looking up for data with invalid id");
		} catch (DataAccessServiceException e) {
//			ok
		}
	}
	
	@Test
	@TestDescription(
			planId="das.functionality",
			description="Verifies if service is able to provide SimpleDataObjects"
			)
	public void testStoreLookupSDORS() throws Exception {
		String stId = null;
		try {
			stId = depotService.createStore(null, StorageConstants.DEFAULT_MAX_ST_DS_SIZE);
			assertNotNull(stId);
			List<String> data = new ArrayList<String>();
			String uriObjectId1 = "test_id_"+System.currentTimeMillis();
			data.add(DepotServiceHelper.buildStoreRecord(uriObjectId1, 
					StorageConstants.STORE_DATATYPE_URI, textTxtLocation));
			String uriObjectId2 = "test_id_"+(System.currentTimeMillis() + 1);
			data.add(DepotServiceHelper.buildStoreRecord(uriObjectId2, 
					StorageConstants.STORE_DATATYPE_URI, textTxtLocation));
			String actionId = depotService.storeObjects(
					stId, data, StorageConstants.STORING_TYPE_REFRESH);
//			wait until storing is finished
			boolean actionFinished = false;
			for (int i=0; i<triesCount; i++) {
				Thread.sleep(sleepTime);
				String storingStatus = depotService.storingCallback(
						stId, actionId, false);
				if (isStoringStatusDone(storingStatus)) {
					actionFinished = true;
					break;
				}
			}
			assertTrue(actionFinished);
//			validate store lookup RS
			W3CEndpointReference epr = dataAccessService.storeLookUpSDORS(stId);
			assertNotNull(epr);
			ResultSetService resultSetService = epr.getPort(ResultSetService.class);
			assertNotNull(resultSetService);
			String rsId = ISUtils.extractResultSetId(epr);
			assertNotNull(rsId);

//			waiting until RS is closed
			int maxTriesCount = 5;
			int triesCount = 0;
			while(!ISConstants.RESULT_SET_STATUS_CLOSED.equals(
					resultSetService.getRSStatus(rsId))) {
				Thread.sleep(1000);
				triesCount++;
				if (triesCount>maxTriesCount) {
					fail("rs is not closed, maxTriesCount: " + maxTriesCount + 
							" was exceeded!");
				}
			}
			
			assertEquals(2, resultSetService.getNumberOfElements(rsId));
			List<String> results = resultSetService.getResult(rsId, 1, 2, 
					ISConstants.RESULT_SET_REQUEST_MODE_WAITING);
			assertNotNull(results);
			assertEquals(2, results.size());
			for (String currentResult : results) {
				assertNotNull(currentResult);
				assertTrue(currentResult.length()>0);
				assertTrue(currentResult.contains("RESOURCE_PROFILE"));
				assertTrue(currentResult.contains("StoreDSResources"));
//				don't know the order
				assertTrue(currentResult.contains(uriObjectId1) ||
						currentResult.contains(uriObjectId2));
//				TODO add more detailed verification
			}
			
		} finally {
			if (stId!=null) {
				depotService.deleteStore(stId);
			}
		}
	}
	
	@Test
	@TestDescription(
			planId="das.functionality",
			description="Checks whether service fails smoothly when " +
				"looking up for SDOs with invalid Id"
			)
	public void testStoreLookupSDORSForInvalidId() throws Exception {
		try {
			dataAccessService.storeLookUpSDORS("invalidStId");
			fail("exception should be thrown when " +
					"looking up for SDOs with invalid id");
		} catch (DataAccessServiceException e) {
//			ok
		}
	}

	@Test
	@TestDescription(
			planId="das.functionality",
			description="Verifies if service is able to return single object URL"
			)
	public void testGetObjectURL() throws Exception {
		String stId = null;
		try {
			stId = depotService.createStore(null, StorageConstants.DEFAULT_MAX_ST_DS_SIZE);
			assertNotNull(stId);
			List<String> data = new ArrayList<String>();
			String uriObjectId = "test_id_"+System.currentTimeMillis();
			data.add(DepotServiceHelper.buildStoreRecord(uriObjectId, 
					StorageConstants.STORE_DATATYPE_URI, textTxtLocation));
			String actionId = depotService.storeObjects(
					stId, data, StorageConstants.STORING_TYPE_REFRESH);
//			wait until storing is finished
			boolean actionFinished = false;
			for (int i=0; i<triesCount; i++) {
				Thread.sleep(sleepTime);
				String storingStatus = depotService.storingCallback(
						stId, actionId, true);
				if (isStoringStatusDone(storingStatus)) {
					actionFinished = true;
					break;
				}
			}
			assertTrue(actionFinished);
//			validate simply via data access
			String objectURL = dataAccessService.getObjectUrl(uriObjectId, stId);
			assertNotNull(objectURL);
			assertTrue(objectURL.length()>0);
//			TODO try to download object content
			
		} finally {
			if (stId!=null) {
				depotService.deleteStore(stId);
			}
		}
	}

	@Test
	@TestDescription(
			planId="das.functionality",
			description="Checks whether service fails smoothly when " +
				"getting object URL for invalid struct id"
			)
	public void testGetObjectURLForInvalidStructId() throws Exception {
		try {
			dataAccessService.getObjectUrl(
					"someObjId", "invalidStructId");
			fail("exception should be thrown when " +
					"getting object URL for invalid struct id");
		} catch (DataAccessServiceException e) {
//			ok
		}
	}
	
	@Test
	@TestDescription(
			planId="das.functionality",
			description="Checks whether service fails smoothly when " +
				"getting object URL for invalid object id"
			)
	public void testGetObjectURLForInvalidObjectId() throws Exception {
		String stId = null;
		try {
			stId = depotService.createStore(null, StorageConstants.DEFAULT_MAX_ST_DS_SIZE);
			assertNotNull(stId);
			dataAccessService.getObjectUrl(
					"invalidObjId", stId);
			fail("exception should be thrown when " +
					"getting object URL for invalid object id");
		} catch (DataAccessServiceException e) {
//			ok
		} finally {
			if (stId!=null) {
				depotService.deleteStore(stId);
			}
		}
	}
	
	@Test
	@TestDescription(
			planId="das.functionality",
			description="Verifies if service is able to return single object SimpleDataObject"
			)
	public void testGetObjectSDO() throws Exception {
		String stId = null;
		try {
			stId = depotService.createStore(null, StorageConstants.DEFAULT_MAX_ST_DS_SIZE);
			assertNotNull(stId);
			List<String> data = new ArrayList<String>();
			String uriObjectId = "test_id_"+System.currentTimeMillis();
			data.add(DepotServiceHelper.buildStoreRecord(uriObjectId, 
					StorageConstants.STORE_DATATYPE_URI, textTxtLocation));
			String actionId = depotService.storeObjects(
					stId, data, StorageConstants.STORING_TYPE_REFRESH);
//			wait until storing is finished
			boolean actionFinished = false;
			for (int i=0; i<triesCount; i++) {
				Thread.sleep(sleepTime);
				String storingStatus = depotService.storingCallback(
						stId, actionId, false);
				if (isStoringStatusDone(storingStatus)) {
					actionFinished = true;
					break;
				}
			}
			assertTrue(actionFinished);
//			validate simply via data access
			String objectSDO = dataAccessService.getObjectSDO(uriObjectId, stId);
			assertNotNull(objectSDO);
			assertTrue(objectSDO.length()>0);
			assertTrue(objectSDO.contains("RESOURCE_PROFILE"));
			assertTrue(objectSDO.contains("StoreDSResources"));
			assertTrue(objectSDO.contains(uriObjectId));
//			TODO add more detailed verification
			
		} finally {
			if (stId!=null) {
				depotService.deleteStore(stId);
			}
		}
	}
	
	@Test
	@TestDescription(
			planId="das.functionality",
			description="Checks whether service fails smoothly when " +
				"getting object SDO for invalid struct id"
			)
	public void testGetObjectSDOForInvalidStructId() throws Exception {
		try {
			dataAccessService.getObjectSDO(
					"someObjId", "invalidStructId");
			fail("exception should be thrown when " +
					"getting object SDO for invalid struct id");
		} catch (DataAccessServiceException e) {
//			ok
		}
	}
	
	@Test
	@TestDescription(
			planId="das.functionality",
			description="Checks whether service fails smoothly when " +
				"getting object SDO for invalid object id"
			)
	public void testGetObjectSDOForInvalidObjectId() throws Exception {
		String stId = null;
		try {
			stId = depotService.createStore(null, StorageConstants.DEFAULT_MAX_ST_DS_SIZE);
			assertNotNull(stId);
			dataAccessService.getObjectSDO(
					"invalidObjId", stId);
			fail("exception should be thrown when " +
					"getting object SDO for invalid object id");
		} catch (DataAccessServiceException e) {
//			ok
		} finally {
			if (stId!=null) {
				depotService.deleteStore(stId);
			}
		}
	}

	protected boolean isStoringStatusDone(String storingStatus) {
		return storingStatus.contains("<STORING_STATUS value=\"DONE\"/>");
	}
}
