package eu.dnetlib.msro.notification;

import java.io.StringReader;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.google.common.base.Splitter;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.msro.workflows.procs.WorkflowProcess;
import eu.dnetlib.msro.workflows.procs.WorkflowProcess.Status;
import eu.dnetlib.rmi.enabling.ISLookUpService;
import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;

public class EmailDispatcher {

	private static final Log log = LogFactory.getLog(EmailDispatcher.class);
	private final BlockingQueue<Message> queue = new LinkedBlockingQueue<Message>();
	private String from;
	private String fromName;
	private String cc;
	private String smtpHost;
	private int smtpPort = 587;
	private String smtpUser;
	private String smtpPassword;
	private String baseUrl;
	private String infrastructure;
	@Autowired
	private UniqueServiceLocator serviceLocator;

	public void sendMail(final String to, final String subject, final String message) {
		try {
			final Session session = Session.getInstance(obtainProperties(), obtainAuthenticator());

			final MimeMessage mimeMessage = new MimeMessage(session);
			mimeMessage.setFrom(new InternetAddress(this.from, this.fromName));
			mimeMessage.setSubject(subject);
			mimeMessage.setContent(message, "text/html; charset=utf-8");
			mimeMessage.setSentDate(new Date());

			mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(to));

			if ((this.cc != null) && !this.cc.isEmpty()) {
				for (final String aCC : Splitter.on(",").omitEmptyStrings().trimResults().split(getCc())) {
					mimeMessage.addRecipient(Message.RecipientType.CC, new InternetAddress(aCC));
				}
			}

			this.queue.add(mimeMessage);

			log.info("Mail to " + to + " in queue");
		} catch (final Exception e) {
			log.error("Error sending mail", e);
		}
	}

	private String applyTemplate(final String template, final WorkflowProcess proc) {
		final StringTemplate st = new StringTemplate(template);
		st.setAttribute("proc", proc);
		st.setAttribute("baseUrl", this.baseUrl);
		st.setAttribute("infrastructure", this.infrastructure);
		return st.toString();
	}

	public void processMailQueue() {
		while (true) {
			final Message message = this.queue.poll();
			if (message == null) {
				return;
			} else {
				try {
					log.info("Sending mail...");
					Transport.send(message);
					log.info("...sent");
				} catch (final MessagingException e) {
					log.error("Error sending email", e);
					this.queue.add(message);
					return;
				}
			}
		}
	}

	public void sendMails(final WorkflowProcess proc) {

		final SAXReader reader = new SAXReader();
		final Map<String, Document> emailProfiles = new HashMap<>();

		try {

			this.serviceLocator
					.getService(ISLookUpService.class)
					.quickSearchProfile("for $x in collection('/db/DRIVER/WorkflowDSResources/WorkflowDSResourceType') " +
							"where $x//RESOURCE_IDENTIFIER/@value='" + proc.getProfileId() + "' " +
							"or $x//RESOURCE_IDENTIFIER/@value='" + proc.getParentProfileId() + "' " +
							"return $x//NOTIFICATIONS/EMAIL/concat(@condition , ' @@@ ',  @messageProfileId, ' @@@ ', @address)")
					.forEach(s -> {
						final String[] arr = s.split("@@@");
						if (StringUtils.isNoneBlank(arr[0].trim())) {
							final NotificationCondition condition = NotificationCondition.valueOf(arr[0].trim());
							final String emailProfileId = arr[1].trim();
							final String to = arr[2].trim();

							if (condition == NotificationCondition.ALWAYS ||
									(condition == NotificationCondition.ONLY_FAILED && proc.getStatus() == Status.FAILURE) ||
									(condition == NotificationCondition.ONLY_SUCCESS && proc.getStatus() == Status.SUCCESS)) {
								try {
									if (!emailProfiles.containsKey(emailProfileId)) {
										final String profile = this.serviceLocator.getService(ISLookUpService.class).getResourceProfile(emailProfileId);
										final Document doc = reader.read(new StringReader(profile));
										emailProfiles.put(emailProfileId, doc);
									}
									final String subject = applyTemplate(emailProfiles.get(emailProfileId).valueOf("//SUBJECT_TEMPLATE").trim(), proc);
									final String message = applyTemplate(emailProfiles.get(emailProfileId).valueOf("//EMAIL_TEMPLATE").trim(), proc);

									sendMail(to, subject, message);

								} catch (final Exception e) {
									log.error("Error sending mail to " + to, e);
								}
							}
						}
					});
		} catch (final Throwable e) {
			log.error("Error generating sending mails for process: " + proc.getId(), e);
		}

	}

	private Properties obtainProperties() {
		final Properties props = new Properties();
		props.put("mail.transport.protocol", "smtp");
		props.put("mail.smtp.host", this.smtpHost);
		props.put("mail.smtp.port", this.smtpPort);
		props.put("mail.smtp.auth", Boolean.toString((this.smtpUser != null) && !this.smtpUser.isEmpty()));
		return props;
	}

	private Authenticator obtainAuthenticator() {
		if ((this.smtpUser == null) || this.smtpUser.isEmpty()) { return null; }

		return new Authenticator() {

			private final PasswordAuthentication authentication = new PasswordAuthentication(EmailDispatcher.this.smtpUser, EmailDispatcher.this.smtpPassword);

			@Override
			protected PasswordAuthentication getPasswordAuthentication() {
				return this.authentication;
			}

		};
	}

	public String getFrom() {
		return this.from;
	}

	@Required
	public void setFrom(final String from) {
		this.from = from;
	}

	public String getFromName() {
		return this.fromName;
	}

	@Required
	public void setFromName(final String fromName) {
		this.fromName = fromName;
	}

	public String getCc() {
		return this.cc;
	}

	@Required
	public void setCc(final String cc) {
		this.cc = cc;
	}

	public String getSmtpHost() {
		return this.smtpHost;
	}

	@Required
	public void setSmtpHost(final String smtpHost) {
		this.smtpHost = smtpHost;
	}

	public int getSmtpPort() {
		return this.smtpPort;
	}

	public void setSmtpPort(final int smtpPort) {
		this.smtpPort = smtpPort;
	}

	public String getSmtpUser() {
		return this.smtpUser;
	}

	public void setSmtpUser(final String smtpUser) {
		this.smtpUser = smtpUser;
	}

	public String getSmtpPassword() {
		return this.smtpPassword;
	}

	public void setSmtpPassword(final String smtpPassword) {
		this.smtpPassword = smtpPassword;
	}

	public String getBaseUrl() {
		return this.baseUrl;
	}

	@Required
	public void setBaseUrl(final String baseUrl) {
		this.baseUrl = baseUrl;
	}

	public String getInfrastructure() {
		return this.infrastructure;
	}

	@Required
	public void setInfrastructure(final String infrastructure) {
		this.infrastructure = infrastructure;
	}

}
