package ru.yandex.wmconsole.notifier.handler;

import java.io.IOException;
import java.io.StringReader;
import java.net.IDN;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

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

import ru.yandex.common.util.collections.MultiMap;
import ru.yandex.wmconsole.data.HostOwnersNotificationData;
import ru.yandex.wmconsole.data.LanguageEnum;
import ru.yandex.wmconsole.data.NotificationChannelEnum;
import ru.yandex.wmconsole.data.NotificationTypeEnum;
import ru.yandex.wmconsole.data.UserNotificationOptions;
import ru.yandex.wmconsole.data.info.UserOptionsInfo;
import ru.yandex.wmconsole.data.info.UsersHostsInfo;
import ru.yandex.wmconsole.service.HostInfoService;
import ru.yandex.wmconsole.service.NotificationOptionsService;
import ru.yandex.wmconsole.service.NotificationService;
import ru.yandex.wmconsole.service.PersonalMessageService;
import ru.yandex.wmconsole.service.UserOptionsService;
import ru.yandex.wmconsole.service.UsersHostsService;
import ru.yandex.wmconsole.service.WMCUserInfoService;
import ru.yandex.wmtools.common.data.info.WMUserInfo;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;

/**
 * Not for direct use - needed for external plugins
 *
 * @author Andrey Mima (amima@yandex-team.ru)
 */
public class HostOwnersHandler implements Handler {
    private static final Logger log = LoggerFactory.getLogger(HostOwnersHandler.class);

    private static final String PARAM_HEADER = "header";
    private static final String PARAM_MESSAGE = "message";
    private static final String PARAM_MESSAGE_LANG = "message-lang";
    private static final String PARAM_HOST_LIST = "host_id";
    private static final String PARAM_GROUPED = "grouped";

    private static final String TAG_MESSAGE = "message";
    private static final String TAG_HEADER = "header";
    private static final String TAG_HOST = "host";

    private static final String PATTERN_HOST_NAMES_LIST = "%%host-names-list%%";

    private NotificationService notificationService;
    private UsersHostsService usersHostsService;
    private HostInfoService hostInfoService;
    private PersonalMessageService personalMessageService;
    private NotificationOptionsService notificationOptionsService;
    private WMCUserInfoService userInfoService;
    private UserOptionsService userOptionsService;

    @Override
    public void handleNotification(String xmlData) {
        throw new UnsupportedOperationException("handleNotification not supported for HostOwners notification");
    }

    /**
     * Вызывается из NotifyServantlet для нотификаций с типом, которого нет в NotificationTypeEnum,
     * то есть для сообщений из АВТО, НЕДВИЖИМОСТЬ...
     *
     * @param xmlData       XML-документ, содержащий сообщение
     * @param serviceId     типа сообщения от другого сервиса
     * @throws InternalException
     */
    public void handleServiceNotification(String xmlData, Integer serviceId) throws InternalException {
        log.debug("Host owners service message received externally");
        HostOwnersNotificationData data = handleXML(xmlData);
        handleServiceNotification(data, serviceId, null);
    }

    public void handleServiceNotification(HostOwnersNotificationData data, Integer type, @Nullable LanguageEnum language) {
        log.debug("Host owners service message received externally for language " + language);
        try {
            if (data.isGrouped()) {
                // отправляется сообщение для группы хостов, которыми владеет пользователь

                MultiMap<Long, Long> userHostsMap = getUsersHosts(data.getHostIdList());
                Date date = new Date();
                for (Map.Entry<Long, List<Long>> e : userHostsMap.entrySet()) {
                    Long userId = e.getKey();
                    List<Long> hostIds = e.getValue();
                    if (!needSendToUser(userId, language)) {
                        continue;
                    }

                    UserNotificationOptions options = notificationOptionsService.getUserNotificationOptions(userId);
                    if (options.containsOption(type, NotificationChannelEnum.MESSAGE) ||
                            options.containsOption(type, NotificationChannelEnum.EMAIL)) {
                        try {
                            final String hostNamesList = getUnicodeHostNames(data, hostIds, ",<br/>");
                            Long issueId = fillTables(userId,
                                    applyTemplates(data.getMessage(), userId, data, null, hostNamesList),
                                    applyTemplates(data.getHeader(), userId, data, null, hostNamesList), date);
                            notificationService.insertNotificationForUser(type, issueId, userId, date);
                        } catch (UserException ex) {
                            log.error("UserException in " + getClass().getName() + " while message template processing", ex);
                        }
                    }
                }
            } else {
                // отправляется сообщение для отдельного хоста, которым владеет пользователь
                Long hostId = data.getHost();
                String dbHostName = hostInfoService.getHostNameByHostId(hostId);
                if (dbHostName == null) {
                    log.error("Host " + hostId + " not found in database");
                    return;
                }
                String hostName = IDN.toUnicode(dbHostName);
                Date date = new Date();
                for (Long userId : getHostUsers(hostId)) {
                    if (!needSendToUser(userId, language)) {
                        // Если язык сообщений не совпадает с выбранным пользователем, то не отправляем сообщение
                        continue;
                    }

                    UserNotificationOptions options = notificationOptionsService.getUserNotificationOptions(userId);
                    if (options.containsOption(type, NotificationChannelEnum.MESSAGE) ||
                            options.containsOption(type, NotificationChannelEnum.EMAIL)) {
                        try {
                            Long issueId = fillTables(userId,
                                    applyTemplates(data.getMessage(), userId, data, hostName, null),
                                    applyTemplates(data.getHeader(), userId, data, hostName, null), date);
                            notificationService.insertNotificationForUser(type, issueId, userId, date);
                        } catch (UserException e) {
                            log.error("UserException in " + getClass().getName() + " while message template processing", e);
                        }
                    }
                }
            }
        } catch (ClassCastException e) {
            log.error("ClassCastException in " + getClass().getName() + " while extracting notification data (possible corrupt xml)", e);
        } catch (InternalException e) {
            log.error("InternalException in " + getClass().getName() + " while inserting notification(s)", e);
        }
    }

    private String applyTemplates(
            String message,
            Long userId,
            HostOwnersNotificationData data,
            String hostName,
            @Nullable String hostNameList) throws UserException, InternalException {
        String result = message;
        if (data.isGrouped()) {
            if (result.contains(PATTERN_HOST_NAMES_LIST)) {
                result = result.replaceAll(PATTERN_HOST_NAMES_LIST, hostNameList);
            }
        } else {
            if (result.contains("%%host-name%%")) {
                result = result.replaceAll("%%host-name%%", hostName);
            }
            if (result.contains("%%host-id%%")) {
                result = result.replaceAll("%%host-id%%", data.getHost().toString());
            }
        }
        if (result.contains("%%user-uid%%")) {
            result = result.replaceAll("%%user-uid%%", userId.toString());
        }
        WMUserInfo userInfo = userInfoService.getUserInfo(userId);
        if (userInfo.getUserInfo() != null) {
            if (result.contains("%%user-login%%")) {
                result = result.replaceAll("%%user-login%%", userInfo.getLogin());
            }
            if (result.contains("%%user-fio%%")) {
                result = result.replaceAll("%%user-fio%%", userInfo.getFIO());
            }
        } else {
            log.warn("User " + userId + " not found in passport. Can't apply templates for message");
        }
        return result;
    }

    private List<Long> getHostUsers(final Long hostId) throws InternalException {
        return usersHostsService.getHostVerifiedUserIds(hostId);
    }

    private MultiMap<Long, Long> getUsersHosts(final List<Long> hostIds) throws InternalException {
        List<UsersHostsInfo> usersHosts = usersHostsService.getUsersHostsInfoList(hostIds);
        MultiMap<Long, Long> map = new MultiMap<Long, Long>();
        for (UsersHostsInfo userHost : usersHosts) {
            map.append(userHost.getUserId(), userHost.getHostId());
        }
        return map;
    }

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

    @Override
    public void internalHandle(String... params) {
        if (params.length < 3) {
            log.debug("Host owners internalHandle must take 3 or 4 params but get " + params.length);
        }
        String message = params[0];
        String header = params[1];
        Long host = Long.parseLong(params[2]);
        LanguageEnum language = params.length == 4 ? LanguageEnum.fromString(params[3]) : null;
        int type = NotificationTypeEnum.PERSONAL_MESSAGE.getValue();

        handleServiceNotification(new HostOwnersNotificationData(message, header, host), type, language);
    }

    @Override
    public void handleInternalNotification(Map<String, String> params) {
        String header = params.get(PARAM_HEADER);
        String message = params.get(PARAM_MESSAGE);
        LanguageEnum language = LanguageEnum.fromString(params.get(PARAM_MESSAGE_LANG));
        int type = NotificationTypeEnum.PERSONAL_MESSAGE.getValue();

        // Проверяем опциональный параметр, который задает группировку сообщений для нескольких хостов
        // в одно сообщение/письмо
        Boolean grouped;
        try {
            grouped = params.get(PARAM_GROUPED) != null ? Boolean.valueOf(params.get(PARAM_GROUPED)) : false;
        } catch (NumberFormatException e) {
            grouped = false;
        }

        log.debug("params[grouped]=" + params.get(PARAM_GROUPED) + "; grouped=" + grouped);

        // CSV LIST -> List<Long>
        String csvHostList = params.get(PARAM_HOST_LIST);
        List<Long> hostList = csvToList(csvHostList);

        if (grouped) {
            handleServiceNotification(new HostOwnersNotificationData(message, header, hostList), type, language);
        } else {
            for (Long hostId : hostList) {
                handleServiceNotification(new HostOwnersNotificationData(message, header, hostId), type, language);
            }
        }
    }

    private HostOwnersNotificationData handleXML(String xmlData) throws InternalException {
        SAXBuilder builder = new SAXBuilder();
        try {
            Document eventDocument = builder.build(new StringReader(xmlData));
            Element rootElement = eventDocument.getRootElement();
            String message = rootElement.getChild(TAG_MESSAGE).getText();
            String header = rootElement.getChild(TAG_HEADER).getText();
            String host = rootElement.getChild(TAG_HOST).getText();
            return new HostOwnersNotificationData(message, header, hostInfoService.getHostIdByHostName(host, false));
        } 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");
        }
    }

    private boolean needSendToUser(long userId, LanguageEnum messageLanguage) throws InternalException {
        final UserOptionsInfo userOptionsInfo = userOptionsService.getUserOptions(userId);
        LanguageEnum emailLanguage = userOptionsInfo.getEmailLanguage();
        // Если в настройках не задан язык пересылки на email, то считаем, что язык русский
        emailLanguage = emailLanguage != null ? emailLanguage : LanguageEnum.DEFAULT_EMAIL_LANGUAGE;
        if (messageLanguage != null && !messageLanguage.equals(emailLanguage)) {
            // Если язык сообщения выбран и не совпадает с выбранным пользователем языком, то сообщение
            // не отправляется
            return false;
        }
        return true;
    }

    private @Nullable String getUnicodeHostNames(
            final HostOwnersNotificationData data,
            final List<Long> hostIds,
            final String delimiter) throws InternalException {

        // Избегаем лишнего вычисления списка имен хостов, если это не требуется шаблонами
        if (!data.getMessage().contains(PATTERN_HOST_NAMES_LIST) &&
                !data.getHeader().contains(PATTERN_HOST_NAMES_LIST)) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        String d = "";
        for (Long hostId : hostIds) {
            String hostName = IDN.toUnicode(hostInfoService.getHostNameByHostId(hostId));
            sb.append(d);
            sb.append(hostName);
            d = delimiter;
        }
        return sb.toString();
    }

    private static List<Long> csvToList(String csvList) {
        String [] values = csvList.split(",");
        List<Long> list = new ArrayList<Long>(values.length);
        for (String s : values) {
            list.add(Long.valueOf(s));
        }
        return list;
    }

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

    @Required
    public void setHostInfoService(HostInfoService hostInfoService) {
        this.hostInfoService = hostInfoService;
    }

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

    @Required
    public void setUsersHostsService(UsersHostsService usersHostsService) {
        this.usersHostsService = usersHostsService;
    }

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

    @Required
    public void setNotificationOptionsService(NotificationOptionsService notificationOptionsService) {
        this.notificationOptionsService = notificationOptionsService;
    }

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