/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.shardingsphere.elasticjob.error.handler.email;

import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.elasticjob.spi.executor.error.handler.JobErrorHandler;

import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.Properties;

/**
 * Job error handler for send error message via email.
 */
@Slf4j
public final class EmailJobErrorHandler implements JobErrorHandler {
    
    private Session session;
    
    private String subject;
    
    private String from;
    
    private String to;
    
    private String cc;
    
    private String bcc;
    
    @Override
    public void init(final Properties props) {
        String host = props.getProperty(EmailPropertiesConstants.HOST);
        int port = Integer.parseInt(props.getProperty(EmailPropertiesConstants.PORT));
        boolean isUseSSL = Boolean.parseBoolean(props.getProperty(EmailPropertiesConstants.IS_USE_SSL, EmailPropertiesConstants.DEFAULT_IS_USE_SSL));
        boolean isDebug = Boolean.parseBoolean(props.getProperty(EmailPropertiesConstants.IS_DEBUG, EmailPropertiesConstants.DEFAULT_IS_DEBUG));
        String username = props.getProperty(EmailPropertiesConstants.USERNAME);
        String password = props.getProperty(EmailPropertiesConstants.PASSWORD);
        String sslTrust = props.getProperty(EmailPropertiesConstants.SSL_TRUST);
        session = Session.getDefaultInstance(createSessionProperties(host, port, isUseSSL, isDebug, sslTrust), getSessionAuthenticator(username, password));
        subject = props.getProperty(EmailPropertiesConstants.SUBJECT, EmailPropertiesConstants.DEFAULT_SUBJECT);
        from = props.getProperty(EmailPropertiesConstants.FROM);
        to = props.getProperty(EmailPropertiesConstants.TO);
        cc = props.getProperty(EmailPropertiesConstants.CC);
        bcc = props.getProperty(EmailPropertiesConstants.BCC);
    }
    
    private Properties createSessionProperties(final String host, final int port, final boolean isUseSSL, final boolean isDebug, final String sslTrust) {
        Properties result = new Properties();
        result.put("mail.smtp.host", host);
        result.put("mail.smtp.port", port);
        result.put("mail.smtp.auth", Boolean.TRUE.toString());
        result.put("mail.transport.protocol", "smtp");
        result.setProperty("mail.debug", Boolean.toString(isDebug));
        if (isUseSSL) {
            result.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            result.setProperty("mail.smtp.socketFactory.fallback", Boolean.FALSE.toString());
            if (!Strings.isNullOrEmpty(sslTrust)) {
                result.setProperty("mail.smtp.ssl.trust", sslTrust);
            }
        }
        return result;
    }
    
    private Authenticator getSessionAuthenticator(final String username, final String password) {
        return new Authenticator() {
            
            @Override
            public PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(username, password);
            }
        };
    }
    
    @Override
    public void handleException(final String jobName, final Throwable cause) {
        String errorMessage = getErrorMessage(jobName, cause);
        try {
            sendMessage(createMessage(errorMessage));
            log.info("An exception has occurred in Job '{}', an email has been sent successfully.", jobName, cause);
        } catch (final MessagingException ex) {
            cause.addSuppressed(ex);
            log.error("An exception has occurred in Job '{}' but failed to send email because of", jobName, cause);
        }
    }
    
    private String getErrorMessage(final String jobName, final Throwable cause) {
        StringWriter writer = new StringWriter();
        cause.printStackTrace(new PrintWriter(writer, true));
        return String.format("Job '%s' exception occur in job processing, caused by %s", jobName, writer);
    }
    
    private Message createMessage(final String content) throws MessagingException {
        MimeMessage result = new MimeMessage(session);
        result.setFrom(new InternetAddress(from));
        result.setSubject(subject);
        result.setSentDate(new Date());
        Multipart multipart = new MimeMultipart();
        BodyPart mailBody = new MimeBodyPart();
        mailBody.setContent(content, "text/html; charset=utf-8");
        multipart.addBodyPart(mailBody);
        result.setContent(multipart);
        if (StringUtils.isNotBlank(to)) {
            String[] tos = to.split(",");
            for (String each : tos) {
                result.addRecipient(Message.RecipientType.TO, new InternetAddress(each));
            }
        }
        if (!Strings.isNullOrEmpty(cc)) {
            result.addRecipient(Message.RecipientType.CC, new InternetAddress(cc));
        }
        if (!Strings.isNullOrEmpty(bcc)) {
            result.addRecipient(Message.RecipientType.BCC, new InternetAddress(bcc));
        }
        result.saveChanges();
        return result;
    }
    
    private void sendMessage(final Message message) throws MessagingException {
        try (Transport transport = session.getTransport()) {
            transport.connect();
            transport.sendMessage(message, message.getAllRecipients());
        }
    }
    
    @Override
    public String getType() {
        return "EMAIL";
    }
}
