package ru.yandex.wmconsole.notifier.handler;

import java.io.IOException;
import java.io.StringReader;
import java.util.Date;
import java.util.Map;

import javax.xml.transform.TransformerException;

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

import ru.yandex.wmconsole.data.LanguageEnum;
import ru.yandex.wmconsole.data.NotificationTypeEnum;
import ru.yandex.wmconsole.data.info.UserOptionsInfo;
import ru.yandex.wmconsole.service.IWMCUserInfoService;
import ru.yandex.wmconsole.service.NotificationService;
import ru.yandex.wmconsole.service.PersonalMessageService;
import ru.yandex.wmconsole.service.UserOptionsService;
import ru.yandex.wmconsole.util.XmlUtil;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;

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

    private static final String INTERNAL_HANDLE_HEADER = "header";
    private static final String INTERNAL_HANDLE_MESSAGE = "message";
    private static final String INTERNAL_USER_ID = "user-id";
    private static final String INTERNAL_MESSAGE_LANG = "message-lang";

    private static final String TAG_MESSAGE = "message";
    private static final String TAG_HEADER = "header";
    private static final String TAG_USER_ID = "user-id";

    private NotificationService notificationService;
    private PersonalMessageService personalMessageService;
    private IWMCUserInfoService userInfoService;
    private UserOptionsService userOptionsService;

    @Override
    public void handleNotification(String xmlData) {
        log.debug("Personal message received externally");
        PersonalMessageData message = handleXML(xmlData);
        try {
            internalHandleNotification(message.getHeader(), message.getMessage(), message.getUserId());
        } catch (ClassCastException e) {
            log.error("ClassCastException in " + getClass().getName() + " " +
                    "while extracting notification data (possible corrupt xml)", e);
        }
    }

    @Override
    public void internalHandle(String... params) {
        String[] userIds = params[2].split(",");
        for (String userId : userIds) {
            internalHandleNotification(params[0], params[1], new Long(userId));
        }
    }

    @Override
    public void handleInternalNotification(final Map<String, String> params) {
        log.debug("Personal message received internally");
        String userIdList = params.get(INTERNAL_USER_ID);
        if (userIdList == null) {
            log.error(INTERNAL_USER_ID + " parameter is null");
            return;
        }
        String[] userIds = params.get(INTERNAL_USER_ID).split(",");
        String header = params.get(INTERNAL_HANDLE_HEADER);
        String message = params.get(INTERNAL_HANDLE_MESSAGE);
        String messageLanguageParam = params.get(INTERNAL_MESSAGE_LANG);
        LanguageEnum messageLanguage = LanguageEnum.fromString(messageLanguageParam);
        if (messageLanguage == null) {
            // сообщение для всех пользователей из списка
            for (String userIdStr : userIds) {
                Long userId = Long.valueOf(userIdStr);
                internalHandleNotification(header, message, userId);
            }
        } else {
            // сообщение для пользователей из списка, у которых выбран соответствующий язык пересылки на email
            for (String userIdStr : userIds) {
                Long userId = Long.valueOf(userIdStr);
                try {
                    final UserOptionsInfo userOptionsInfo = userOptionsService.getUserOptions(userId);
                    LanguageEnum emailLanguage = userOptionsInfo.getEmailLanguage();
                    // Если в настройках не задан язык пересылки на email, то считаем, что язык русский
                    emailLanguage = emailLanguage != null ? emailLanguage : LanguageEnum.DEFAULT_EMAIL_LANGUAGE;
                    if (messageLanguage.equals(emailLanguage)) {
                        internalHandleNotification(header, message, userId);
                    }
                } catch (InternalException e) {
                    log.error("UserException in " + getClass().getName() + " while getting userOptions for " + userId, e);
                    continue;
                }
            }
        }
    }

    public void internalHandleNotification(String header, String message, Long userId) {
        log.debug("Personal message received internally");
        try {
            if ((message == null) || (message.length() == 0)) {
                log.error(TAG_MESSAGE + " is null or empty (possible corrupt xml)");
                return;
            }

            if ((header == null) || (header.length() == 0)) {
                log.error(TAG_HEADER + " is null or empty (possible corrupt xml)");
                return;
            }

            if ((userId == null) || (userId < 1)) {
                log.error(TAG_USER_ID + " is null or less than 1 (possible corrupt xml)");
                return;
            }

            userInfoService.getUserInfo(userId); //throws exceptions if there is no such user

            Date date = new Date();
            Long issueId = fillTables(userId, message, header, date);
            notificationService.insertNotificationForUser(NotificationTypeEnum.PERSONAL_MESSAGE, issueId, userId, date);
        } catch (UserException e) {
            log.error("UserException in " + getClass().getName() + " while inserting notification(s)", e);
        } catch (InternalException e) {
            log.error("InternalException in " + getClass().getName() + " while inserting notification(s)", e);
        }
    }

    private Long fillTables(Long userId, String message, String header, Date date) throws InternalException {
        return personalMessageService.addPersonalMessage(userId, message, header, date);
    }

    private PersonalMessageData handleXML(String xmlData) {
        PersonalMessageData personalMessageData = new PersonalMessageData();
        StringReader stringReader = new StringReader(xmlData);
        SAXBuilder builder = new SAXBuilder();
        try {
            Document eventDocument = builder.build(stringReader);
            Element rootElement = eventDocument.getRootElement();
            Element messageElement = rootElement.getChild(TAG_MESSAGE);
            String message = null;
            try {
                message = XmlUtil.xmlToString(messageElement);
            } catch (TransformerException e) {
                log.error("TransformerException in " + getClass().getName() + " while extracting xml data", e);
                throw new IllegalArgumentException("Invalid xml notification data format");
            }
            personalMessageData.setMessage(message);

            Element headerElement = rootElement.getChild(TAG_HEADER);
            personalMessageData.setHeader(headerElement.getText());

            Element userIdElement = rootElement.getChild(TAG_USER_ID);
            String userIdString = userIdElement.getText();
            personalMessageData.setUserId(new Long(userIdString));
        } catch (JDOMException e) {
            log.error("JDOMException in " + getClass().getName() + " while extracting xml data", e);
            throw new IllegalArgumentException("Invalid xml notification data format");
        } catch (IOException e) {
            log.error("IOException in " + getClass().getName() + " while reading xml data", e);
            throw new IllegalArgumentException("Invalid xml notification data format");
        } catch (NumberFormatException e) {
            log.error("NumberFormatException in" + getClass().getName() + " while parsing userId");
            throw new IllegalArgumentException("Invalid xml notification data format");
        } catch (NullPointerException e) {
            log.warn("Invalid xml notification data format", e);
            throw new IllegalArgumentException("Invalid xml notification data format");
        }

        return personalMessageData;
    }

    private static final class PersonalMessageData {
        private String message;
        private String header;
        private Long userId;

        public void setMessage(String message) {
            this.message = message;
        }

        public void setHeader(String header) {
            this.header = header;
        }

        public void setUserId(Long userId) {
            this.userId = userId;
        }

        public String getMessage() {
            return message;
        }

        public String getHeader() {
            return header;
        }

        public Long getUserId() {
            return userId;
        }
    }

    @Required
    public void setNotificationService(NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    @Required
    public void setPersonalMessageService(PersonalMessageService personalMessageService) {
        this.personalMessageService = personalMessageService;
    }

    @Required
    public void setUserInfoService(IWMCUserInfoService userInfoService) {
        this.userInfoService = userInfoService;
    }

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