package ru.yandex.wmconsole.notifier.sender;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MailDateFormat;
import javax.mail.internet.MimeMessage;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.transform.JDOMSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.mail.MailException;

import ru.yandex.common.util.collections.CollectionFactory;
import ru.yandex.wmconsole.data.LanguageEnum;
import ru.yandex.wmconsole.data.NotificationChannelEnum;
import ru.yandex.wmconsole.data.NotificationTypeEnum;
import ru.yandex.wmconsole.data.NotificationsForUser;
import ru.yandex.wmconsole.data.info.UserOptionsInfo;
import ru.yandex.wmconsole.service.UserOptionsService;
import ru.yandex.wmtools.common.data.info.WMUserInfo;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.InternalProblem;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.service.MailService;
import ru.yandex.wmtools.common.service.SidsBlackboxService;
import ru.yandex.wmtools.common.service.ValidatorService;

/**
 * @author Andrey Mima (amima@yandex-team.ru)
 */
public class EmailNotificationSender extends AbstractSender {
    private static final Logger log = LoggerFactory.getLogger(EmailNotificationSender.class);

    private static final String EMPTY_STRING = "";
    private static final String TAG_TITLE = "title";

    private static final String HTML_PAGE_HEADER = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">" +
            "<html><head><meta content=\"text/html; charset=UTF-8\" http-equiv=\"Content-Type\"></head>" +
            "<body text=\"#000000\" bgcolor=\"#ffffff\">";
    private static final String HTML_PAGE_FOOTER = "</body></html>";
    public static final String YAWEBMASTER = "yawebmaster";

    public static final Set<Integer> SERVICE_NOTIFICATION_TYPES_IN_HTML = CollectionFactory.set(
            //недвижимость
            1030,
            //рецепты
            1024,
            //рефераты
            1025,
            //словари
            1026,
            //услуги
            1028,
            //авто
            1031,
            //работа
            1027,
            //отзывы
            1033);

    private MailService mailService;
    private UserOptionsService userOptionsService;
    private ValidatorService validatorService;
    private SidsBlackboxService sidsBlackboxService;
    private boolean productionMode = false;

    private String emailEncoding;
    private File emailXmlFile;
    private File ruEmailSubjectXslFile;
    private File ukEmailSubjectXslFile;
    private File enEmailSubjectXslFile;
    private File trEmailSubjectXslFile;

    private void logError(Long userId, String message, boolean isError, Throwable e) {
        String errorMessage = "Failed to send notification by email for user " + String.valueOf(userId) + ". Reason: " + message;
        if (isError) {
            if (e == null) {
                log.error(errorMessage);
            } else {
                log.error(errorMessage, e);
            }
        } else {
            if (e == null) {
                log.warn(errorMessage);
            } else {
                log.warn(errorMessage, e);
            }
        }
    }

    private void logError(Long userId, String message, boolean isError) {
        logError(userId, message, isError, null);
    }

    @Override
    public boolean sendPortionToUser(NotificationsForUser notificationsForUser, Integer type) {
        Long userId = notificationsForUser.getUserId();
        // Получаем настройки
        final UserOptionsInfo userOptionsInfo;
        try {
            userOptionsInfo = userOptionsService.getUserOptions(userId);
        } catch (InternalException e) {
            logError(userId, "UserException in " + getClass().getName() + " while getting userOptions", true, e);
            return false;
        }

        String message;
        try {
            message = extractXmlString(
                    notificationsForUser,
                    NotificationChannelEnum.EMAIL,
                    userOptionsInfo.getEmailLanguage(),
                    true);
        } catch (Exception e) {
            logError(userId, "Exception while sending email", true, e);
            return false;
        }

        String subject = EMPTY_STRING;
        try {
            if (type.equals(NotificationTypeEnum.GLOBAL_MESSAGE.getValue()) ||
                    type.equals(NotificationTypeEnum.PERSONAL_MESSAGE.getValue()) ||
                    type >= 1024) {
                NotificationTypeEnum t = (type >= 1024) ? NotificationTypeEnum.PERSONAL_MESSAGE
                        : NotificationTypeEnum.R.fromValueOrNull(type);
                List<Element> notificationElements =
                        providers.get(t).getNotificationElements(
                                notificationsForUser.getAllNotifications().get(0),
                                NotificationChannelEnum.EMAIL
                        );
                if (notificationElements == null) {
                    return true;
                }
                for (Element element : notificationElements) {
                    if (TAG_TITLE.equals(element.getName())) {
                        subject = element.getText();
                    }
                }
            }
        } catch (InternalException e) {
            logError(userId, "Cannot get email header for personal or global message", true, e);
        } catch (Exception e) {
            logError(userId, "Error extracting notification data from xml", true, e);
            return false;
        }

        MimeMessage mail = mailService.getMailSender().createMimeMessage();

        String toEmail;
        String userFIO;
        try {
            toEmail = userOptionsService.getUserOptions(userId).getEmail();
            WMUserInfo userInfo = userInfoService.getUserInfo(userId);
            userFIO = userInfo.getFIO();

            if ((toEmail == null) || (toEmail.length() == 0)) {
                logError(userId, userId + " email is null or empty, using default", false);

                if (productionMode) {
                    try {
                        String serviceLogin = sidsBlackboxService.getServiceLogins(userId, 2).get(2);
                        if (serviceLogin == null || serviceLogin.isEmpty()) {
                            logError(userId, "Mail service is not active for user, cannot send to default email", false);
                            return false;
                        }
                        String defaultEmail = userInfo.getUserInfo() == null ?
                                null :
                                validatorService.getDefaultEmail(userId, userInfo.getLogin());

                        if ((defaultEmail == null) || (defaultEmail.length() == 0)) {
                            logError(userId, userId + " default email is null or empty, terminating send", false);
                            return false;
                        }

                        toEmail = defaultEmail;
                    } catch (Exception e) {
                        logError(userId, "Exception while trying to learn if mail service is active", true, e);
                        return false;
                    }
                } else {
                    log.warn("Force emailing not allowed in developer mode, terminating");
                    return false;
                }
            }

            if ((userFIO == null) || (userFIO.length() == 0)) {
                logError(userId, userId + " fio is null or empty", true);
                return true;
            }
        } catch (UserException e) {
            logError(userId, "UserException in " + getClass().getName() + " while getting email", true, e);
            return false;
        } catch (InternalException e) {
            logError(userId, "InternalException in " + getClass().getName() + " while getting email", true, e);
            return false;
        }

        String fromEmail = EMPTY_STRING;
        String fromName = EMPTY_STRING;
        try {
            Reader xmlReader = new InputStreamReader(new FileInputStream(emailXmlFile), "UTF-8");
            SAXBuilder builder = new SAXBuilder();
            Document emailDocument = builder.build(xmlReader);
            Element rootElement = emailDocument.getRootElement();

            if (EMPTY_STRING.equals(subject)) {
                // Если заголовок не указан, берем заголовок из танкера
                try {
                    subject = getSubject(notificationsForUser, userOptionsInfo.getEmailLanguage());
                } catch (Exception e) {
                    logError(userId, "Exception while sending email", true, e);
                    return false;
                }
            }

            Element fromEmailElement = rootElement.getChild("from");
            fromEmail = fromEmailElement.getText();

            Element fromNameElement = rootElement.getChild("from-name");
            fromName = fromNameElement.getText();
        } catch (JDOMException e) {
            logError(userId, "JDOMException in " + getClass().getName(), true, e);
        } catch (IOException e) {
            logError(userId, "IOException in " + getClass().getName(), true, e);
        }

        if (!isSettingsLoaded(fromEmail, fromName, subject, emailEncoding)) {
            return false;
        }

        try {
            final InternetAddress from = new InternetAddress(fromEmail, fromName, emailEncoding);
            mail.setFrom(from);
            mail.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmail, userFIO, emailEncoding));
            mail.setSubject(subject, emailEncoding);
            if (type.equals(NotificationTypeEnum.PERSONAL_MESSAGE.getValue()) ||
                    type.equals(NotificationTypeEnum.GLOBAL_MESSAGE.getValue()) ||
                    SERVICE_NOTIFICATION_TYPES_IN_HTML.contains(type)) {
                mail.setContent(new StringBuilder(HTML_PAGE_HEADER).append(message).append(HTML_PAGE_FOOTER).toString(),
                        "text/html; charset=UTF-8");
            } else {
                mail.setText(message, emailEncoding);
            }
            final Date date = new Date();
            mail.setSentDate(date);
            final MailDateFormat mdf = new MailDateFormat();
            final String sentDate = mdf.format(date);
            final String xYandexService = mailService.getYandexServiceHeaderValue(sentDate, from.toUnicodeString(), subject, YAWEBMASTER);
            log.debug(sentDate + "_" + from.toUnicodeString() + "_" + subject + "_" + YAWEBMASTER + "_76336c7a96b13f0258054d239e70fec8");
            log.debug("X-Yandex-Service: " + xYandexService);
            mail.addHeader("X-Yandex-Service", xYandexService);
        } catch (UnsupportedEncodingException e) {
            logError(userId, "UnsupportedEncodingException in " + getClass().getName(), true, e);
            return false;
        } catch (MessagingException e) {
            logError(userId, "MessagingException in " + getClass().getName(), true, e);
            return false;
        }

        try {
            log.debug("SENDING EMAIL to " + notificationsForUser.getUserId());
            mailService.getMailSender().send(mail);
        } catch (MailException e) {
            logError(userId, "MailException in " + getClass().getName(), true, e);
            return false;
        }

        return true;
    }

    private boolean isSettingsLoaded(String... settings) {
        for (String setting : settings) {
            if ((setting == null) || (setting.length() == 0)) {
                log.error("Settings load failed!");
                return false;
            }
        }

        return true;
    }

    private String getSubject(NotificationsForUser notificationsForUser, LanguageEnum lang) throws UserException, InternalException, FileNotFoundException, TransformerException {
        if (lang == null) {
            log.debug("setting email notification language to default " + LanguageEnum.DEFAULT_EMAIL_LANGUAGE);
            lang = LanguageEnum.DEFAULT_EMAIL_LANGUAGE;
        }
        File xslFile;
        switch(lang) {
            case RU: xslFile = ruEmailSubjectXslFile; break;
            case UK: xslFile = ukEmailSubjectXslFile; break;
            case EN: xslFile = enEmailSubjectXslFile; break;
            case TR: xslFile = trEmailSubjectXslFile; break;
            default:
                throw new InternalException(InternalProblem.ILLEGAL_ARGUMENT,
                        "Unable to find email subject template for lang: " + lang);
        }

        Document document = getNotificationDocument(notificationsForUser, NotificationChannelEnum.EMAIL);
        document.getRootElement().setAttribute(CHANNEL_ATTRIBUTE, Integer.toString(NotificationChannelEnum.EMAIL.getValue()));
        Source srcXml = new JDOMSource(document);
        TransformerFactory xslTransformerFactory = TransformerFactory.newInstance();
        Source xslSource = new StreamSource(xslFile);
        Transformer transformer = xslTransformerFactory.newTransformer(xslSource);
        Writer writer = new StringWriter();
        transformer.transform(srcXml, new StreamResult(writer));
        return writer.toString();
    }

    @Required
    public void setSidsBlackboxService(SidsBlackboxService sidsBlackboxService) {
        this.sidsBlackboxService = sidsBlackboxService;
    }

    @Required
    public void setMailService(MailService mailService) {
        this.mailService = mailService;
    }

    @Required
    public void setUserOptionsService(UserOptionsService userOptionsService) {
        this.userOptionsService = userOptionsService;
    }

    @Required
    public void setValidatorService(ValidatorService validatorService) {
        this.validatorService = validatorService;
    }

    @Required
    public void setProductionMode(boolean productionMode) {
        this.productionMode = productionMode;
    }

    @Required
    public void setEmailEncoding(String emailEncoding) {
        this.emailEncoding = emailEncoding;
    }

    @Required
    public void setEmailXmlFile(File emailXmlFile) {
        this.emailXmlFile = emailXmlFile;
    }

    @Required
    public void setRuEmailSubjectXslFile(File ruEmailSubjectXslFile) {
        this.ruEmailSubjectXslFile = ruEmailSubjectXslFile;
    }

    @Required
    public void setUkEmailSubjectXslFile(File ukEmailSubjectXslFile) {
        this.ukEmailSubjectXslFile = ukEmailSubjectXslFile;
    }

    @Required
    public void setEnEmailSubjectXslFile(File enEmailSubjectXslFile) {
        this.enEmailSubjectXslFile = enEmailSubjectXslFile;
    }

    @Required
    public void setTrEmailSubjectXslFile(File trEmailSubjectXslFile) {
        this.trEmailSubjectXslFile = trEmailSubjectXslFile;
    }
}
