package eu.dnetlib.msro.notification;

import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;

import com.google.common.base.Splitter;
import com.google.common.collect.Maps;

public class EmailDispatcher {

	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;

	private static final Log log = LogFactory.getLog(EmailDispatcher.class);

	private final BlockingQueue<Message> queue = new LinkedBlockingQueue<Message>();

	public StringTemplate prepareMessage(final String template, final Map<String, Object> tmplParams) {
		final StringTemplate st = new StringTemplate(template);
		st.setAttributes(tmplParams);
		st.setAttribute("baseUrl", baseUrl);
		st.setAttribute("infrastructure", infrastructure);
		return st;
	}

	public void sendMail(final Set<String> to, final String subject, final String template, final Map<String, Object> tmplParams) {
		try {
			final StringTemplate st = prepareMessage(template, tmplParams);

			final Session session = Session.getInstance(obtainProperties(), obtainAuthenticator());

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

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

			queue.add(message);

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

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

	private void sendWfStatusMail(final boolean success,
			final Set<String> to,
			final String wfId,
			final String procId,
			final String wfName,
			final Map<String, String> pendingWfs,
			final Map<String, String> responses,
			final String error) {
		try {
			final Map<String, Object> map = Maps.newHashMap();
			map.put("wfId", wfId);
			map.put("wfName", wfName);
			map.put("procId", procId);
			if ((pendingWfs != null) && !pendingWfs.isEmpty()) {
				map.put("pendingWfs", pendingWfs);
			}
			if ((responses != null) && !responses.isEmpty()) {
				map.put("responses", responses);
			}
			if ((error != null) && !error.isEmpty()) {
				map.put("error", error);
			}

			String subject = success ? "[D-NET WORKFLOW] -- SUCCESS: " : "WF FAILURE: ";
			subject += wfName + " on " + infrastructure;
			final String tmplName = success ? "wf_success.mail.st" : "wf_failed.mail.st";
			final String template = IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/msro/mail/" + tmplName));

			sendMail(to, subject, template, map);
		} catch (Exception e) {
			log.error("Error generating success-mail", e);
		}
	}

	public void sendSuccessMail(final Set<String> to,
			final String wfId,
			final String procId,
			final String wfName,
			final Map<String, String> pendingWfs,
			final Map<String, String> responses) {
		sendWfStatusMail(true, to, wfId, procId, wfName, pendingWfs, responses, "");
	}

	public void sendFailedMail(final Set<String> to,
			final String wfId,
			final String procId,
			final String wfName,
			final Map<String, String> pendingWfs,
			final Map<String, String> responses,
			final String error) {
		sendWfStatusMail(false, to, wfId, procId, wfName, pendingWfs, responses, error);
	}

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

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

		return new Authenticator() {

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

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

		};
	}

	public String getFrom() {
		return from;
	}

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

	public String getFromName() {
		return fromName;
	}

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

	public String getCc() {
		return cc;
	}

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

	public String getSmtpHost() {
		return smtpHost;
	}

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

	public int getSmtpPort() {
		return smtpPort;
	}

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

	public String getSmtpUser() {
		return smtpUser;
	}

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

	public String getSmtpPassword() {
		return smtpPassword;
	}

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

	public String getBaseUrl() {
		return baseUrl;
	}

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

	public String getInfrastructure() {
		return infrastructure;
	}

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

}
