package eu.dnetlib.broker.events.output;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Properties;

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.AddressException;
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.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;

import com.google.common.base.Splitter;

import eu.dnetlib.broker.common.elasticsearch.Event;
import eu.dnetlib.broker.common.subscriptions.NotificationMode;
import eu.dnetlib.broker.common.subscriptions.Subscription;
import eu.dnetlib.broker.properties.EmailProperties;

@Component
public class EmailDispatcher extends AbstractNotificationDispatcher<Message> {

	@Autowired
	private EmailProperties props;

	@Value("${lbs.mail.message.template}")
	private Resource emailTemplate;

	@Value("${lbs.mail.message.template.dashboard.baseUrl}")
	private String dashboardBaseUrl;

	@Value("${lbs.mail.message.template.dashboard.helpdesk}")
	private String helpdesk;

	@Value("${lbs.mail.message.template.publicApi}")
	private String publicApi;

	public static final int MAX_NUMBER_OF_EVENTS = 20;

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

	@Override
	protected Message prepareAction(final Subscription subscription, final Event... events) throws Exception {

		if (subscription == null || StringUtils.isBlank(subscription.getSubscriber())) {
			log.warn("Invalid subscription");
			throw new IllegalArgumentException("Invalid subscription");
		}
		if (events.length == 0) {
			log.warn("Event list is empty");
		}

		final String content = generateMailContent(subscription, null, events);

		return prepareMimeMessage(subscription, content);
	}

	@Override
	protected Message prepareAction(final Subscription subscription, final Map<String, Object> params) throws Exception {
		if (subscription == null || StringUtils.isBlank(subscription.getSubscriber())) {
			log.warn("Invalid subscription");
			throw new IllegalArgumentException("Invalid subscription");
		}
		final String content = generateMailContent(subscription, params);

		return prepareMimeMessage(subscription, content);
	}

	protected String generateMailContent(final Subscription subscription, final Map<String, Object> params, final Event... events) throws IOException {
		final StringTemplate st = new StringTemplate(IOUtils.toString(emailTemplate.getInputStream(), StandardCharsets.UTF_8));
		st.setAttribute("sub", subscription);

		st.setAttribute("total", events.length);
		st.setAttribute("max", MAX_NUMBER_OF_EVENTS);
		st.setAttribute("dashboardBaseUrl", dashboardBaseUrl.trim());
		st.setAttribute("publicApi", publicApi.trim());
		st.setAttribute("helpdesk", helpdesk.trim());

		if (events.length > MAX_NUMBER_OF_EVENTS) {
			st.setAttribute("events", Arrays.copyOfRange(events, 0, MAX_NUMBER_OF_EVENTS));
		} else {
			st.setAttribute("events", events);
		}
		if (params != null) {
			params.entrySet().forEach(e -> st.setAttribute(e.getKey(), e.getValue()));
		}

		return st.toString();
	}

	private Message prepareMimeMessage(final Subscription subscription, final String content)
		throws MessagingException, UnsupportedEncodingException, AddressException {
		final Session session = Session.getInstance(obtainProperties(), obtainAuthenticator());

		final MimeMessage mimeMessage = new MimeMessage(session);
		mimeMessage.setFrom(new InternetAddress(props.getFrom(), props.getFromName()));
		mimeMessage.setSubject("Notification for topic: " + subscription.getTopic());
		mimeMessage.setContent(content, "text/html; charset=utf-8");
		mimeMessage.setSentDate(new Date());

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

		if (StringUtils.isNotBlank(props.getCc())) {
			for (final String aCC : Splitter.on(",").omitEmptyStrings().trimResults().split(props.getCc())) {
				mimeMessage.addRecipient(Message.RecipientType.CC, new InternetAddress(aCC));
			}
		}

		log.info("Created mail, to: " + subscription.getSubscriber());

		return mimeMessage;
	}

	@Override
	protected void performAction(final Message message) throws Exception {
		log.info("Sending mail to " + Arrays.toString(message.getAllRecipients()) + "...");
		Transport.send(message);
		log.info("...sent");
	}

	private Properties obtainProperties() {
		final Properties p = new Properties();
		p.put("mail.transport.protocol", "smtp");
		p.put("mail.smtp.host", props.getSmtpHost());
		p.put("mail.smtp.port", props.getSmtpPort());
		p.put("mail.smtp.auth", Boolean.toString(StringUtils.isNotBlank(props.getSmtpUser())));
		return p;
	}

	private Authenticator obtainAuthenticator() {
		if (StringUtils.isBlank(props.getSmtpUser())) { return null; }

		return new Authenticator() {

			private final PasswordAuthentication authentication =
				new PasswordAuthentication(props.getSmtpUser(), props.getSmtpPassword());

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

		};
	}

	@Override
	public NotificationMode getMode() {
		return NotificationMode.EMAIL;
	}

	protected Resource getEmailTemplate() {
		return emailTemplate;
	}

	protected void setEmailTemplate(final Resource emailTemplate) {
		this.emailTemplate = emailTemplate;
	}

	protected String getDashboardBaseUrl() {
		return dashboardBaseUrl;
	}

	protected void setDashboardBaseUrl(final String dashboardBaseUrl) {
		this.dashboardBaseUrl = dashboardBaseUrl;
	}

	protected String getHelpdesk() {
		return helpdesk;
	}

	protected void setHelpdesk(final String helpdesk) {
		this.helpdesk = helpdesk;
	}

	protected String getPublicApi() {
		return publicApi;
	}

	protected void setPublicApi(final String publicApi) {
		this.publicApi = publicApi;
	}

}
