package eu.dnetlib.data.information.citation;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Map;
import java.util.ResourceBundle;

import javax.xml.ws.Endpoint;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
import org.xml.sax.InputSource;

import pl.edu.icm.driver.factories.ICMBeansFactory;
import pl.edu.icm.driver.factories.ISBeansFactory;
import pl.edu.icm.driver.index.DummyStaticResultSet;
import pl.edu.icm.driver.is.ISUtils;
import pl.edu.icm.driver.is.profile.blackboard.Blackboard;
import pl.edu.icm.driver.is.profile.blackboard.BlackboardLastAction;
import pl.edu.icm.driver.is.profile.blackboard.Message;
import pl.edu.icm.driver.is.profile.blackboard.Parameter;
import pl.edu.icm.driver.is.utils.ResourcesGenerator;
import pl.edu.icm.driver.tester.TestDescription;
import pl.edu.icm.driver.tester.runner.Parameterized4TestCase;
import pl.edu.icm.driver.utils.PropertiesKeys;
import sun.misc.BASE64Encoder;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;
import eu.dnetlib.miscutils.datetime.DateUtils;

/**
 * Citation service backend notification tests.
 * 
 * @author mhorst
 *
 */
@TestDescription(
		name="Citation service notifications test suite",
		description="Performs all tests for all notification types sent to citation service.\n" +
				"Expected properties:\n" +
				"icm.cite.service.location - localisation of citation service\n" +
				"is.pr.service.location - localisation of IS Profile Registry service for tested Index Service\n" +
				"Optional properties:\n" +
				"is.rs.dataprov.ip - data provider service ip\n" +
				"is.rs.dataprov.port - data provider service port\n" +
				"is.rs.dataprov.location - data provider service location (default: 'IDataProvider?wsdl')\n" +
				"cite.notify.service.id - predefined citation service profId"
)
public class CitationNotificationTest extends Parameterized4TestCase {

	protected static final Logger log = Logger.getLogger(CitationNotificationTest.class);
	
	/**
	 * Notification wait time.
	 */
	public static final int SLEEP_TIME_MILLIS = 5 * 1000;
	
	/**
	 * Tries count.
	 */
	public static final int TRIES_COUNT = 5;
	
	ICitationService citationService;
	ISRegistryService registryService;
	ISLookUpService lookupService;
	
	String citationServiceLocation;
	String registryServiceLocation;
	String lookupServiceLocation;
	
	String predefinedServiceId;
	
	String dataProviderIP = null;
	String dataProviderPort = null;
	String dataProviderLocation = null;
	String dataProviderURL = null;
	
	/**
	 * Records data provider.
	 */
	ResultSetService dataProvider;
	
	/**
	 * CXF bus containing HTTP server.
	 */
	Bus bus;
	
	/* (non-Javadoc)
	 * @see pl.edu.icm.driver.tester.ParameterizedTest#setParameters(java.util.Map)
	 */
	@Override
	public void setParameters(Map<Object, Object> parameters) {
		citationServiceLocation = (String) parameters.get(
				PropertiesKeys.SERVICE_LOCATION_CITATION);
		log.info("TestCase citationServiceLocation: " + citationServiceLocation);
		lookupServiceLocation =  (String) parameters.get(PropertiesKeys.SERVICE_LOCATION_IS_LU);
		log.info("TestCase lookupServiceLocation: " + lookupServiceLocation);
		registryServiceLocation =  (String) parameters.get(PropertiesKeys.SERVICE_LOCATION_IS_PR);
		log.info("TestCase registryServiceLocation: " + registryServiceLocation);
		
		dataProviderIP = (String) parameters.get(PropertiesKeys.RS_DATA_PROVIDER_IP);
		dataProviderPort = (String) parameters.get(PropertiesKeys.RS_DATA_PROVIDER_PORT);
		dataProviderLocation = (String) parameters.get(PropertiesKeys.RS_DATA_PROVIDER_LOCATION);
		predefinedServiceId = (String) parameters.get(PropertiesKeys.CITE_NOTIFY_SERVICE_ID);
	}

	@Before
	public void setUp() throws Exception {
		ResourceBundle citeBundle = ResourceBundle.getBundle(
				"eu.dnetlib.data.information.citation.auxiliary");
		if (predefinedServiceId==null)
			predefinedServiceId = citeBundle.getString(
					PropertiesKeys.CITE_NOTIFY_SERVICE_ID);
		
		ResourceBundle bundle = ResourceBundle.getBundle(
				"pl.edu.icm.driver.is.auxiliary");
		if (dataProviderIP==null)
			dataProviderIP = bundle.getString(PropertiesKeys.RS_DATA_PROVIDER_IP);
		if (dataProviderPort==null)
			dataProviderPort = bundle.getString(PropertiesKeys.RS_DATA_PROVIDER_PORT);
		if (dataProviderLocation==null)
			dataProviderLocation = bundle.getString(PropertiesKeys.RS_DATA_PROVIDER_LOCATION);
		StringBuffer strBuff = new StringBuffer();
		strBuff.append("http://");
		strBuff.append(dataProviderIP);
		strBuff.append(':');
		strBuff.append(dataProviderPort);
		strBuff.append('/');
		strBuff.append(dataProviderLocation);
		dataProviderURL = strBuff.toString();
		log.info("dataProviderURL: "+dataProviderURL);
		
		ICMBeansFactory factory = new ICMBeansFactory();
		citationService = factory.getCitationService(citationServiceLocation);
		
		ISBeansFactory isBeansFactory = new ISBeansFactory();
		lookupService = isBeansFactory.getISLookUp(lookupServiceLocation);
		registryService = isBeansFactory.getISRegistry(registryServiceLocation);
	}
	
	@Test
	public void testFeedAndDeleteNotifications() throws Exception {
		String predefinedFeedId = "predefinedFeedId_" + System.currentTimeMillis();
		boolean citeFed = false;
		int beforeSize = -1;
		try {
			String initialCitDSContent = getCitationDSProfile(citationServiceLocation);
			assertNotNull(initialCitDSContent);
			beforeSize = Integer.valueOf(
					extractSingleValue("//RECORDS_STORED", initialCitDSContent)); 
			String initialIndexLastUpdate = extractSingleValue(
					"//INDEX_LAST_UPDATE", initialCitDSContent);
	//		preparing feeding mdi data provider
			String preparedData[] = prepareCitationRecordData();
			serverStartup(new Integer(dataProviderPort).intValue(), preparedData);
			
			String bdId = String.valueOf(System.currentTimeMillis());
			int initialPageSize = 10;
			int expiryTime = 50000;
	//		changed to dataProvider deployed locally!
			W3CEndpointReference resultSetEPR = dataProvider.createPullRS(dataProviderURL, bdId, 
					initialPageSize, expiryTime, null, null, null);
			String rsId = ISUtils.extractResultSetId(resultSetEPR);
			assertNotNull(rsId);
			
			Blackboard blackboard = new Blackboard();
			String actionId = String.valueOf(System.currentTimeMillis());
			blackboard.setLastRequest(new BlackboardLastAction("1190027429.91795",actionId));
			blackboard.setLastResponse(new BlackboardLastAction("1190026760.552806",actionId));
			blackboard.setMessages(new ArrayList<Message>());
			blackboard.getMessages().add(new Message());
	
			blackboard.getMessages().get(0).setId(actionId);
			DateUtils date = new DateUtils();
			blackboard.getMessages().get(0).setDate(date.getDateAsISO8601String());
			blackboard.getMessages().get(0).setAction(Message.Action.FEED);
			blackboard.getMessages().get(0).setActionStatus(Message.ActionStatus.ASSIGNED);
			blackboard.getMessages().get(0).addParam(new Parameter(Parameter.PARAM_NAME_ID, predefinedFeedId));
			blackboard.getMessages().get(0).addParam(new Parameter(Parameter.PARAM_NAME_RESULTSET_EPR,
					encode(resultSetEPR)));
			
			String messageStr = ResourcesGenerator.generateMessagePartOfBlackBoard(blackboard.getMessages());
			System.out.println(messageStr);
			registryService.addBlackBoardMessage(predefinedServiceId, 
					actionId, messageStr);
			for (int i=0; i<TRIES_COUNT; i++) {
				Thread.sleep(SLEEP_TIME_MILLIS);
	//			checking if INDEX_LAST_UPDATE has changed, what will mean the data was fed
				String currentCitDSContent = getCitationDSProfile(citationServiceLocation);
				assertNotNull(currentCitDSContent);
				String currentIndexLastUpdate = extractSingleValue(
						"//INDEX_LAST_UPDATE", currentCitDSContent);
				if (!initialIndexLastUpdate.equals(currentIndexLastUpdate)) {
//					verifying after size
					int finalSize = Integer.valueOf(
							extractSingleValue("//RECORDS_STORED", currentCitDSContent)); 
					assertEquals(beforeSize + preparedData.length, finalSize);
					citeFed = true;
					break;
				}
			}
			if (!citeFed) {
				fail("citation service seem to be not fed until wait time has passed!");
			}
	//		verifying if data[0] fed is accessible through frontend
			String recordId = extractSingleValue("//dri:objidentifier", preparedData[0]);
			assertNotNull(recordId);
			assertTrue(recordId.trim().length()>0);
			DNetReference[] references = citationService.fetchReferences(recordId);
			assertNotNull(references);
			assertTrue(references.length>0);
			
			String refToBeFound = extractSingleValue("//ref", preparedData[0]);
			assertNotNull(refToBeFound);
			boolean foundMatch = false;
			for (DNetReference currentRef : references) {
				if (refToBeFound.trim().equals(
						currentRef.getText().trim())) {
					foundMatch = true;
					break;
				}
			}
			assertTrue("reference: \"" + refToBeFound + "\" was not found in response!", 
					foundMatch);
		} finally {
//			deleting records fed
			if (citeFed) {
				String beforeDeletionCitDSContent = getCitationDSProfile(citationServiceLocation);
				String initialIndexLastUpdate = extractSingleValue(
						"//INDEX_LAST_UPDATE", beforeDeletionCitDSContent);
				
				Blackboard blackboard = new Blackboard();
				String actionId = String.valueOf(System.currentTimeMillis());
				blackboard.setLastRequest(new BlackboardLastAction("1190027429.91795",actionId));
				blackboard.setLastResponse(new BlackboardLastAction("1190026760.552806",actionId));
				blackboard.setMessages(new ArrayList<Message>());
				blackboard.getMessages().add(new Message());
				blackboard.getMessages().get(0).setId(actionId);
				DateUtils date = new DateUtils();
				blackboard.getMessages().get(0).setDate(date.getDateAsISO8601String());
				blackboard.getMessages().get(0).setAction(Message.Action.DELETE);
				blackboard.getMessages().get(0).setActionStatus(Message.ActionStatus.ASSIGNED);
				blackboard.getMessages().get(0).addParam(
						new Parameter(Parameter.PARAM_NAME_ID, predefinedFeedId));
				String messageStr = ResourcesGenerator.generateMessagePartOfBlackBoard(
						blackboard.getMessages());
				System.out.println(messageStr);
				registryService.addBlackBoardMessage(predefinedServiceId, 
						actionId, messageStr);
				boolean citeDeleted = false;
				for (int i=0; i<TRIES_COUNT; i++) {
					Thread.sleep(SLEEP_TIME_MILLIS);
		//			checking if INDEX_LAST_UPDATE has changed, what will mean the data was fed
					String currentCitDSContent = getCitationDSProfile(citationServiceLocation);
					assertNotNull(currentCitDSContent);
					String currentIndexLastUpdate = extractSingleValue(
							"//INDEX_LAST_UPDATE", currentCitDSContent);
					if (!initialIndexLastUpdate.equals(currentIndexLastUpdate)) {
//						verifying after deletion size
						int finalSize = Integer.valueOf(
								extractSingleValue("//RECORDS_STORED", currentCitDSContent)); 
						assertEquals(beforeSize, finalSize);
						citeDeleted = true;
						break;
					}
				}
				assertTrue(citeDeleted);
			}
		}
	}

	private String extractSingleValue(String xPathExpr, String source) throws Exception {
		XPath xpath = XPathFactory.newInstance().newXPath();
		return xpath.evaluate(xPathExpr, 
				new InputSource(new StringReader(source)));
	}
	
	private String getCitationDSProfile(String associatedServiceLocation) throws Exception {
		String serviceLocation = (associatedServiceLocation!=null)?associatedServiceLocation:
			ICMBeansFactory.citationLocation;
		String xQuery = "collection('/db/DRIVER/CitationDSResources')" 
			+ "//RESOURCE_PROFILE[HEADER/RESOURCE_URI[@value='"
			+ serviceLocation + "?wsdl']]";
		return lookupService.getResourceProfileByQuery(xQuery);
	}
	
	/**
	 * Prepares citation data for populating resultSet.
	 * @return citation String[] data
	 */
	private String[] prepareCitationRecordData() {
		String[] result = new String[2];
		result[0] = ResourcesGenerator.getFileContent("resources/citationRecord/01.xml");
		result[1] = ResourcesGenerator.getFileContent("resources/citationRecord/02.xml");
		return result;
	}
	
	/**
	 * Encodes resultSetEPR. 
	 * Returns base64 format of resultSetEPR.
	 * @param resultSetEPR
	 * @return base64 format of resultSetEPR
	 */
	private String encode(W3CEndpointReference resultSetEPR) {
		BASE64Encoder encoder = new BASE64Encoder();
		return encoder.encode(resultSetEPR.toString().getBytes());
	}
	
	/**
	 * Starts up xfire jetty server.
	 * @param dataProviderPort
	 * @throws Exception
	 */
	void serverStartup(int dataProviderPort, String[] data) throws Exception {
//		 Create CXF Service
		bus = BusFactory.getDefaultBus();
		dataProvider = new DummyStaticResultSet(data);
		StringBuffer strBuff = new StringBuffer();
		strBuff.append("http://");
		strBuff.append(dataProviderIP);
		strBuff.append(':');
		strBuff.append(dataProviderPort);
		strBuff.append('/');
		strBuff.append(dataProviderLocation);
		Endpoint.publish(strBuff.toString(), dataProvider);
		log.info("cxf service started...");
	}
	
	/**
	 * Shuts down xfire jetty server.
	 * @throws Exception
	 */
	void serverShutdown() throws Exception {
		if (bus!=null) {
			log.info("shutting down cxf service...");
			bus.shutdown(true);
		}
	}
	
}
