package ru.yandex.wmconsole.notifier.handler;

import java.io.IOException;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.wmconsole.data.NotificationTypeEnum;
import ru.yandex.wmconsole.service.HostInfoService;
import ru.yandex.wmconsole.service.NotificationService;
import ru.yandex.wmconsole.service.SpamNotificationService;
import ru.yandex.wmconsole.service.UsersHostsService;
import ru.yandex.wmtools.common.SupportedProtocols;
import ru.yandex.wmtools.common.error.InternalException;

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

    private static final String TAG_URL = "url";

    private NotificationService notificationService;
    private SpamNotificationService spamNotificationService;
    private UsersHostsService usersHostsService;
    private HostInfoService hostInfoService;

    @Override
    @SuppressWarnings("unchecked")
    public void handleNotification(String xmlData) {
        log.debug("Spam notifications received externally");
        List<String> urlStringList = handleXML(xmlData);
        try {
            if ((urlStringList == null) || (urlStringList.size() == 0)) {
                log.error(TAG_URL + " is null or empty (possible corrupt xml)");
                return;
            }

            for (String urlString : urlStringList) {
                try {
                    URL url = SupportedProtocols.getURL(urlString);
                    String hostName = url.getHost();
                    Long hostId = getHostId(hostName);

                    if (hostId == null) {
                        log.warn("Host with name " + hostName + " was not found in database");
                        continue;
                    }

                    Date date = new Date();
                    Long issueId = fillTables(url.toString(), hostId, date);

                    List<Long> userIds = getHostUsers(hostId);
                    notificationService.insertNotificationForUsers(NotificationTypeEnum.SPAM, issueId, userIds, date);
                } catch (InternalException e) {
                    log.error("InternalException in " + getClass().getName() + " " +
                            "while inserting notification(s)", e);
                } catch (MalformedURLException e) {
                    log.error("MalformedURLException in " + getClass().getName() + " " +
                            "while inserting notification(s)", e);
                } catch (URISyntaxException e) {
                    log.error("URISyntaxException in " + getClass().getName() + " " +
                            "while inserting notification(s)", e);
                } catch (SupportedProtocols.UnsupportedProtocolException e) {
                    log.error("SupportedProtocols.UnsupportedProtocolException in " + getClass().getName() + " " +
                            "while inserting notification(s)", e);
                }
            }
        } catch (ClassCastException e) {
            log.error("ClassCastException in " + getClass().getName() + " " +
                    "while extracting notification data (possible corrupt xml)", e);
        }
    }

    @Override
    public void internalHandle(String... params) {
        throw new UnsupportedOperationException("Internal handle not supported for Spam notification");
    }

    @Override
    public void handleInternalNotification(Map<String, String> params) {
        throw new UnsupportedOperationException("Internal handle not supported for Spam notification");
    }

    private Long fillTables(String url, Long hostId, Date date) throws InternalException {
        return spamNotificationService.addSpamNotification(url, hostId, date);
    }

    private Long getHostId(String hostName) throws InternalException {
        return hostInfoService.getHostIdByHostName(hostName, false);
    }

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

    private List<String> handleXML(String xmlData) {
        List<String> urlStringList = new ArrayList<String>();
        StringReader stringReader = new StringReader(xmlData);
        SAXBuilder builder = new SAXBuilder();
        try {
            Document eventDocument = builder.build(stringReader);
            Element rootElement = eventDocument.getRootElement();
            List urlElementList = rootElement.getChildren(TAG_URL);
            for (Object url : urlElementList) {
                urlStringList.add(((Element) url).getText());
            }
            log.debug("Spam url list size is " + urlStringList.size());
        } 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 (NullPointerException e) {
            log.warn("Invalid xml notification data format", e);
            throw new IllegalArgumentException("Invalid xml notification data format");
        }
        return urlStringList;
    }

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

    @Required
    public void setSpamNotificationService(SpamNotificationService spamNotificationService) {
        this.spamNotificationService = spamNotificationService;
    }

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

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