package eu.dnetlib.functionality.alert.app;

import java.net.URI;
import java.net.URL;
import java.util.SortedSet;

import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import eu.dnetlib.api.functionality.AlertServiceException;
import eu.dnetlib.domain.functionality.AlertSubscription;
import eu.dnetlib.domain.functionality.AlertTemplate;
import eu.dnetlib.domain.functionality.ObjectPage;
import eu.dnetlib.functionality.alert.dao.AlertDAO;
import eu.dnetlib.functionality.alert.dao.AlertDAOException;

public class AlertServiceTransactionHelper {
	private static final Logger logger = Logger.getLogger(AlertServiceTransactionHelper.class);
	
	private AlertDAO dao;
	
	/**
	 * Set the DAO.
	 * @param dao the DAO of this alert service transaction helper
	 */
	public void setDao(final AlertDAO dao) {
		this.dao = dao;
	}
	
	/**
	 * Retrieve a page of templates.
	 * @param pageNumber the number of the page to retrieve
	 * @param pageSize the size of the page to retrieve
	 * @return an object page containing templates
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, readOnly = true, rollbackFor = AlertServiceException.class)
	public ObjectPage<AlertTemplate> getTemplates(final int pageNumber, final int pageSize) throws AlertServiceException {
		try {
			final ObjectPage<AlertTemplate> templates = new ObjectPage<AlertTemplate>(pageNumber, pageSize, dao.countTemplates(), dao.getTemplates(pageSize, pageNumber * pageSize));
			logger.info("Retrieved templates " + templates);
			return templates;
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error retrieving templates (page: " + pageNumber + ", page size: " + pageSize + ")", e);
		}
	}
	
	/**
	 * Retrieve a template.
	 * @param templateId the unique identifier of the template to retrieve
	 * @return the template specified or null if no such template exists
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, readOnly = true, rollbackFor = AlertServiceException.class)
	public AlertTemplate getTemplate(final String templateId) throws AlertServiceException {
		try {
			final AlertTemplate template = dao.getTemplate(templateId);
			logger.info((template == null) ? ("Template " + templateId + " does not exist") : ("Retrieved template " + templateId));
			return template;
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error retrieving template " + templateId, e);
		}
	}
	
	/**
	 * Add a template.
	 * @param template the template to add
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlertServiceException.class)
	public void addTemplate(final AlertTemplate template) throws AlertServiceException {
		try {
			if (dao.getTemplate(template.getTemplateId()) != null)
				throw new AlertServiceException("error adding template " + template + ": template already exists");
			dao.saveTemplate(template);
			logger.info("Added template " + template);
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error adding template " + template, e);
			
		}
	}
	
	/**
	 * Remove a template.
	 * @param templateId the unique identifier of the template to remove
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlertServiceException.class)
	public void removeTemplate(final String templateId) throws AlertServiceException {
		try {
			if (dao.getTemplate(templateId) == null)
				throw new AlertServiceException("error removing template " + templateId + ": template does not exist");
			dao.deleteTemplate(templateId);
			logger.info("Removed template " + templateId);
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error removing template " + templateId, e);
		}
	}	

	/**
	 * Retrieve a page of subscriptions.
	 * @param pageNumber the number of the page to retrieve
	 * @param pageSize the size of the page to retrieve
	 * @return an object page containing subscriptions
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, readOnly = true, rollbackFor = AlertServiceException.class)
	public ObjectPage<AlertSubscription> getSubscriptions(final int pageNumber, final int pageSize) throws AlertServiceException {
		try {
			final ObjectPage<AlertSubscription> subscriptions = new ObjectPage<AlertSubscription>(pageNumber, pageSize, dao.countSubscriptions(), dao.getSubscriptions(pageSize, pageNumber * pageSize));
			logger.info("Retrieved subscriptions " + subscriptions);
			return subscriptions;
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error retrieving subscriptions (page: " + pageNumber + ", page size: " + pageSize + ")", e);
		}
	}
	
	/**
	 * Retrieve some subscriptions by alert mode and subscriber.
	 * @param alertMode the alert mode of the subscriptions to retrieve
	 * @param subscriber the subscriber URI of the subscriptions to retrieve (prefix match)
	 * @param limit the maximum number of subscriptions to retrieve
	 * @param offset the offset to start at
	 * @return a list of subscriptions
	 * @throws AlertServiceException if any errors occur
	 */ 
	@Transactional(isolation = Isolation.SERIALIZABLE, readOnly = true, rollbackFor = AlertServiceException.class)
	public SortedSet<AlertSubscription> getSubscriptions(final String alertMode, final String subscriber, final int limit, final int offset) throws AlertServiceException {
		try {
			final SortedSet<AlertSubscription> subscriptions = dao.getSubscriptions(alertMode, subscriber, limit, offset);
			logger.info("Retrieved " + subscriptions.size() + " subscriptions with alert mode " + alertMode + " and subscriber like '" + subscriber + "' (limit: " + limit + ", offset: " + offset + ")");
			return subscriptions;
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error retrieving subscriptions with alert mode " + alertMode + " and subscriber like '" + subscriber + "' (limit: " + limit + ", offset: " + offset + ")", e);
		}
	}
	
	/**
	 * Retrieve enabled subscriptions.
	 * @param notificationService the URL of the notification service of the subscriptions to retrieve
	 * @param queryId the unique identifier of the notification query of the subscriptions to retrieve
	 * @param limit the maximum number of subscriptions to retrieve
	 * @param offset the offset to start at
	 * @return a sorted set containing subscriptions
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlertServiceException.class)
	public SortedSet<AlertSubscription> getEnabledSubscriptions(final int limit, final int offset) throws AlertServiceException {
		try {
			final SortedSet<AlertSubscription> subscriptions = dao.getEnabledSubscriptions(limit, offset);
			logger.info("Retrieved " + subscriptions.size() + " enabled subscriptions (limit: " + limit + ", offset: " + offset + ")");
			return subscriptions;
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error retrieving enabled subscriptions (limit: " + limit + ", offset: " + offset + ")", e);
		}
	}
	
	/**
	 * Retrieve enabled subscriptions by notification service and query identifier.
	 * @param notificationService the URL of the notification service of the subscriptions to retrieve
	 * @param queryId the unique identifier of the notification query of the subscriptions to retrieve
	 * @param limit the maximum number of subscriptions to retrieve
	 * @param offset the offset to start at
	 * @return a sorted set containing subscriptions
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, readOnly = true, rollbackFor = AlertServiceException.class)
	public SortedSet<AlertSubscription> getEnabledSubscriptions(final URL notificationService, final String queryId, final int limit, final int offset) throws AlertServiceException {
		try {
			final SortedSet<AlertSubscription> subscriptions = dao.getEnabledSubscriptions(notificationService, queryId, limit, offset);
			logger.info("Retrieved " + subscriptions.size() + " enabled subscriptions with notification service " + notificationService + " and query " + queryId + "(limit: " + limit + ", offset: " + offset + ")");
			return subscriptions;
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error retrieving enabled subscriptions with notification service " + notificationService + " and query " + queryId + "(limit: " + limit + ", offset: " + offset + ")", e);
		}
	}
		
	/**
	 * Add a subscription.
	 * @param subscription the subscription to add
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlertServiceException.class)
	public void addSubscription(final AlertSubscription subscription) throws AlertServiceException {
		try {
			if (dao.getSubscription(subscription.getTemplateId(), subscription.getNotificationService(), subscription.getQueryId(), subscription.getResultId(), subscription.getAlertMode(), subscription.getSubscriber()) != null)
				throw new AlertServiceException("error adding subscription " + subscription + ": subscription already exists");
			dao.saveSubscription(subscription);
			logger.info("Added subscription " + subscription);
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error adding subscription " + subscription, e);
		}
	}

	/**
	 * Enable a subscription.
	 * @param templateId the unique identifier of the template of the subscription to enable
	 * @param notificationService the URL of the notification service of the subscription to enable
	 * @param queryId the unique identifier of the notification query of the subscription to enable
	 * @param resultId the unique identifier of the notification result of the subscription to enable
	 * @param alertMode the alert mode of the subscription to enable
	 * @param subscriber the URI of the subscriber of the subscription to enable
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlertServiceException.class)
	public void enableSubscription(final String templateId, final URL notificationService, final String queryId, final String resultId, final String alertMode, final URI subscriber) throws AlertServiceException {
		try {
			final AlertSubscription subscription = dao.getSubscription(templateId, notificationService, queryId, resultId, alertMode, subscriber);
			if (subscription == null)
				throw new AlertServiceException("error enabling subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " +
						alertMode + ", subscriber: " + subscriber + "): subscription does not exist");
			if (subscription.isEnabled())
				throw new AlertServiceException("error enabling subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " +
						alertMode + ", subscriber: " + subscriber + "): subscription is already enabled");
			subscription.setEnabled(true);
			dao.saveSubscription(subscription);
			logger.info("Enabled subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " + alertMode + ", subscriber: " +
					subscriber + ")");
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error enabling subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " +
					alertMode + ", subscriber: " + subscriber + ")", e);
		}
	}

	/**
	 * Disable a subscription.
	 * @param templateId the unique identifier of the template of the subscription to disable
	 * @param notificationService the URL of the notification service of the subscription to disable
	 * @param queryId the unique identifier of the notification query of the subscription to disable
	 * @param resultId the unique identifier of the notification result of the subscription to disable
	 * @param alertMode the alert mode of the subscription to disable
	 * @param subscriber the URI of the subscriber of the subscription to disable
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlertServiceException.class)
	public void disableSubscription(final String templateId, final URL notificationService, final String queryId, final String resultId, final String alertMode, final URI subscriber) throws AlertServiceException {
		try {
			final AlertSubscription subscription = dao.getSubscription(templateId, notificationService, queryId, resultId, alertMode, subscriber);
			if (subscription == null)
				throw new AlertServiceException("error disabling subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " +
						alertMode + ", subscriber: " + subscriber + "): subscription does not exist");
			if (!subscription.isEnabled())
				throw new AlertServiceException("error disabling subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " +
						alertMode + ", subscriber: " + subscriber + "): subscription is already disabled");
			subscription.setEnabled(false);
			dao.saveSubscription(subscription);
			logger.info("Disabled subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " + alertMode + ", subscriber: " +
					subscriber + ")");
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error disabling subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " +
					alertMode + ", subscriber: " + subscriber + ")", e);
		}
	}

	/**
	 * Remove a subscription.
	 * @param templateId the unique identifier of the template of the subscription to remove
	 * @param notificationService the URL of the notification service of the subscription to remove
	 * @param queryId the unique identifier of the notification query of the subscription to remove
	 * @param resultId the unique identifier of the notification result of the subscription to remove
	 * @param alertMode the alert mode of the subscription to remove
	 * @param subscriber the URI of the subscriber of the subscription to remove
	 * @throws AlertServiceException if any errors occur
	 */
	@Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = AlertServiceException.class)
	public void removeSubscription(final String templateId, final URL notificationService, final String queryId, final String resultId, final String alertMode, final URI subscriber) throws AlertServiceException {
		try {
			if (dao.getSubscription(templateId, notificationService, queryId, resultId, alertMode, subscriber) == null)
				throw new AlertServiceException("error removing subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " +
						alertMode + ", subscriber: " + subscriber + "): subscription does not exist");
			dao.deleteSubscription(templateId, notificationService, queryId, resultId, alertMode, subscriber);
			logger.info("Removed subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " + alertMode + ", subscriber: " +
					subscriber + ")");
		} catch (final AlertDAOException e) {
			throw new AlertServiceException("error removing subscription (template: " + templateId + ", notification service: " + notificationService + ", query: " + queryId + ", result: " + resultId + ", alert mode: " +
					alertMode + ", subscriber: " + subscriber + ")", e);
		}
	}
}
