/*
 * Copyright 2012 LinkedIn Corp.
 *
 * Licensed 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 azkaban.utils;

import azkaban.alert.Alerter;
import azkaban.executor.ExecutableFlow;
import azkaban.executor.ExecutableNode;
import azkaban.executor.ExecutionOptions;
import azkaban.executor.Status;
import azkaban.executor.mail.DefaultMailCreator;
import azkaban.executor.mail.MailCreator;
import azkaban.sla.SlaOption;
import org.apache.log4j.Logger;

import javax.mail.MessagingException;
import java.util.ArrayList;
import java.util.List;

public class Emailer extends AbstractMailer implements Alerter {
  private static Logger logger = Logger.getLogger(Emailer.class);

  private static final String HTTPS = "https";

  private static final String HTTP = "http";

  private boolean testMode = false;

  private String scheme;
  private String clientHostname;
  private String clientPortNumber;

  private String mailHost;
  private int mailPort;
  private String mailUser;
  private String mailPassword;
  private String mailSender;
  private String azkabanName;
  private String tls;

  public Emailer(Props props) {
    super(props);
    this.azkabanName = props.getString("azkaban.name", "azkaban");
    this.mailHost = props.getString("mail.host", "localhost");
    this.mailPort = props.getInt("mail.port", DEFAULT_SMTP_PORT);
    this.mailUser = props.getString("mail.user", "");
    this.mailPassword = props.getString("mail.password", "");
    this.mailSender = props.getString("mail.sender", "");
    this.tls = props.getString("mail.tls", "false");

    int mailTimeout = props.getInt("mail.timeout.millis", 10000);
    EmailMessage.setTimeout(mailTimeout);
    int connectionTimeout =
        props.getInt("mail.connection.timeout.millis", 10000);
    EmailMessage.setConnectionTimeout(connectionTimeout);

    EmailMessage.setTotalAttachmentMaxSize(getAttachmentMaxSize());

    this.clientHostname = props.getString("jetty.hostname", "localhost");

    if (props.getBoolean("jetty.use.ssl", true)) {
      this.scheme = HTTPS;
      this.clientPortNumber = props.getString("jetty.ssl.port");
    } else {
      this.scheme = HTTP;
      this.clientPortNumber = props.getString("jetty.port");
    }

    testMode = props.getBoolean("test.mode", false);
  }

  @SuppressWarnings("unchecked")
  private void sendSlaAlertEmail(SlaOption slaOption, String slaMessage) {
    String subject = "Sla Violation Alert on " + getAzkabanName();
    String body = slaMessage;
    List<String> emailList =
        (List<String>) slaOption.getInfo().get(SlaOption.INFO_EMAIL_LIST);
    if (emailList != null && !emailList.isEmpty()) {
      EmailMessage message =
          super.createEmailMessage(subject, "text/html", emailList);

      message.setBody(body);

      if (!testMode) {
        try {
          message.sendEmail();
        } catch (MessagingException e) {
          logger.error("Email message send failed", e);
        }
      }
    }
  }

  public void sendFirstErrorMessage(ExecutableFlow flow) {
    EmailMessage message = new EmailMessage(mailHost, mailPort, mailUser, mailPassword);
    message.setFromAddress(mailSender);
    message.setTLS(tls);
    message.setAuth(super.hasMailAuth());

    ExecutionOptions option = flow.getExecutionOptions();

    MailCreator mailCreator =
        DefaultMailCreator.getCreator(option.getMailCreator());

    logger.debug("ExecutorMailer using mail creator:"
        + mailCreator.getClass().getCanonicalName());

    boolean mailCreated =
        mailCreator.createFirstErrorMessage(flow, message, azkabanName, scheme,
            clientHostname, clientPortNumber);

    if (mailCreated && !testMode) {
      try {
        message.sendEmail();
      } catch (MessagingException e) {
        logger.error("Email message send failed", e);
      }
    }
  }

  public void sendErrorEmail(ExecutableFlow flow, String... extraReasons) {
    EmailMessage message = new EmailMessage(mailHost, mailPort, mailUser, mailPassword);
    message.setFromAddress(mailSender);
    message.setTLS(tls);
    message.setAuth(super.hasMailAuth());

    ExecutionOptions option = flow.getExecutionOptions();

    MailCreator mailCreator =
        DefaultMailCreator.getCreator(option.getMailCreator());
    logger.debug("ExecutorMailer using mail creator:"
        + mailCreator.getClass().getCanonicalName());

    boolean mailCreated =
        mailCreator.createErrorEmail(flow, message, azkabanName, scheme,
            clientHostname, clientPortNumber, extraReasons);

    if (mailCreated && !testMode) {
      try {
        message.sendEmail();
      } catch (MessagingException e) {
        logger.error("Email message send failed", e);
      }
    }
  }

  public void sendSuccessEmail(ExecutableFlow flow) {
    EmailMessage message = new EmailMessage(mailHost, mailPort, mailUser, mailPassword);
    message.setFromAddress(mailSender);
    message.setTLS(tls);
    message.setAuth(super.hasMailAuth());

    ExecutionOptions option = flow.getExecutionOptions();

    MailCreator mailCreator =
        DefaultMailCreator.getCreator(option.getMailCreator());
    logger.debug("ExecutorMailer using mail creator:"
        + mailCreator.getClass().getCanonicalName());

    boolean mailCreated =
        mailCreator.createSuccessEmail(flow, message, azkabanName, scheme,
            clientHostname, clientPortNumber);

    if (mailCreated && !testMode) {
      try {
        message.sendEmail();
      } catch (MessagingException e) {
        logger.error("Email message send failed", e);
      }
    }
  }

  public static List<String> findFailedJobs(ExecutableFlow flow) {
    ArrayList<String> failedJobs = new ArrayList<String>();
    for (ExecutableNode node : flow.getExecutableNodes()) {
      if (node.getStatus() == Status.FAILED) {
        failedJobs.add(node.getId());
      }
    }
    return failedJobs;
  }

  @Override
  public void alertOnSuccess(ExecutableFlow exflow) throws Exception {
    sendSuccessEmail(exflow);
  }

  @Override
  public void alertOnError(ExecutableFlow exflow, String... extraReasons)
      throws Exception {
    sendErrorEmail(exflow, extraReasons);
  }

  @Override
  public void alertOnFirstError(ExecutableFlow exflow) throws Exception {
    sendFirstErrorMessage(exflow);
  }

  @Override
  public void alertOnSla(SlaOption slaOption, String slaMessage)
      throws Exception {
    sendSlaAlertEmail(slaOption, slaMessage);
  }
}
