package fr.xephi.authme.mail;

import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.output.LogLevel;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.EmailSettings;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.StringUtils;
import org.apache.commons.mail.EmailConstants;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;

import javax.activation.CommandMap;
import javax.activation.MailcapCommandMap;
import javax.inject.Inject;
import javax.mail.Session;
import java.security.Security;
import java.util.Properties;

import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_ACCOUNT;
import static fr.xephi.authme.settings.properties.EmailSettings.MAIL_PASSWORD;


/**
 * Sends emails to players on behalf of the server.
 */
public class SendMailSsl {

    @Inject
    private Settings settings;

    /**
     * Returns whether all necessary settings are set for sending mails.
     *
     * @return true if the necessary email settings are set, false otherwise
     */
    public boolean hasAllInformation() {
        return !settings.getProperty(MAIL_ACCOUNT).isEmpty()
            && !settings.getProperty(MAIL_PASSWORD).isEmpty();
    }

    /**
     * Creates a {@link HtmlEmail} object configured as per the AuthMe config
     * with the given email address as recipient.
     *
     * @param emailAddress the email address the email is destined for
     * @return the created HtmlEmail object
     * @throws EmailException if the mail is invalid
     */
    public HtmlEmail initializeMail(String emailAddress) throws EmailException {
        String senderMail = StringUtils.isEmpty(settings.getProperty(EmailSettings.MAIL_ADDRESS))
            ? settings.getProperty(EmailSettings.MAIL_ACCOUNT)
            : settings.getProperty(EmailSettings.MAIL_ADDRESS);

        String senderName = StringUtils.isEmpty(settings.getProperty(EmailSettings.MAIL_SENDER_NAME))
            ? senderMail
            : settings.getProperty(EmailSettings.MAIL_SENDER_NAME);
        String mailPassword = settings.getProperty(EmailSettings.MAIL_PASSWORD);
        int port = settings.getProperty(EmailSettings.SMTP_PORT);

        HtmlEmail email = new HtmlEmail();
        email.setCharset(EmailConstants.UTF_8);
        email.setSmtpPort(port);
        email.setHostName(settings.getProperty(EmailSettings.SMTP_HOST));
        email.addTo(emailAddress);
        email.setFrom(senderMail, senderName);
        email.setSubject(settings.getProperty(EmailSettings.RECOVERY_MAIL_SUBJECT));
        email.setAuthentication(settings.getProperty(EmailSettings.MAIL_ACCOUNT), mailPassword);
        if (settings.getProperty(PluginSettings.LOG_LEVEL).includes(LogLevel.DEBUG)) {
            email.setDebug(true);
        }

        setPropertiesForPort(email, port);
        return email;
    }

    /**
     * Sets the given content to the HtmlEmail object and sends it.
     *
     * @param content the content to set
     * @param email the email object to send
     * @return true upon success, false otherwise
     */
    public boolean sendEmail(String content, HtmlEmail email) {
        Thread.currentThread().setContextClassLoader(SendMailSsl.class.getClassLoader());
        // Issue #999: Prevent UnsupportedDataTypeException: no object DCH for MIME type multipart/alternative
        // cf. http://stackoverflow.com/questions/21856211/unsupporteddatatypeexception-no-object-dch-for-mime-type
        MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
        mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
        mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
        mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
        mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed");
        mc.addMailcap("message/rfc822;; x-java-content- handler=com.sun.mail.handlers.message_rfc822");

        try {
            email.setHtmlMsg(content);
            email.setTextMsg(content);
        } catch (EmailException e) {
            ConsoleLogger.logException("Your email.html config contains an error and cannot be sent:", e);
            return false;
        }
        try {
            email.send();
            return true;
        } catch (EmailException e) {
            ConsoleLogger.logException("Failed to send a mail to " + email.getToAddresses() + ":", e);
            return false;
        }
    }

    /**
     * Sets properties to the given HtmlEmail object based on the port from which it will be sent.
     *
     * @param email the email object to configure
     * @param port the configured outgoing port
     */
    private void setPropertiesForPort(HtmlEmail email, int port) throws EmailException {
        switch (port) {
            case 587:
                String oAuth2Token = settings.getProperty(EmailSettings.OAUTH2_TOKEN);
                if (!oAuth2Token.isEmpty()) {
                    if (Security.getProvider("Google OAuth2 Provider") == null) {
                        Security.addProvider(new OAuth2Provider());
                    }
                    Properties mailProperties = email.getMailSession().getProperties();
                    mailProperties.setProperty("mail.smtp.ssl.enable", "true");
                    mailProperties.setProperty("mail.smtp.auth.mechanisms", "XOAUTH2");
                    mailProperties.setProperty("mail.smtp.sasl.enable", "true");
                    mailProperties.setProperty("mail.smtp.sasl.mechanisms", "XOAUTH2");
                    mailProperties.setProperty("mail.smtp.auth.login.disable", "true");
                    mailProperties.setProperty("mail.smtp.auth.plain.disable", "true");
                    mailProperties.setProperty(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, oAuth2Token);
                    email.setMailSession(Session.getInstance(mailProperties));
                } else {
                    email.setStartTLSEnabled(true);
                    email.setStartTLSRequired(true);
                }
                break;
            case 25:
                if (settings.getProperty(EmailSettings.PORT25_USE_TLS)) {
                    email.setStartTLSEnabled(true);
                    email.setSSLCheckServerIdentity(true);
                }
                break;
            case 465:
                email.setSslSmtpPort(Integer.toString(port));
                email.setSSLOnConnect(true);
                break;
            default:
                email.setStartTLSEnabled(true);
                email.setSSLOnConnect(true);
                email.setSSLCheckServerIdentity(true);
        }
    }
}
