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.CheckInfectedService;
import ru.yandex.wmconsole.service.HostInfoService;
import ru.yandex.wmconsole.service.NotificationService;
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 CheckInfectedNotificationHandler implements Handler {
    private static final Logger log = LoggerFactory.getLogger(CheckInfectedNotificationHandler.class);

    private static final String TAG_HOST = "host";
    private static final String ATTRIBUTE_NAME = "name";

    private NotificationService notificationService;
    private CheckInfectedService checkInfectedService;
    private UsersHostsService usersHostsService;
    private HostInfoService hostInfoService;

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

            for (String hostString : urlStringList) {
                URL host = SupportedProtocols.getURL(hostString);
                String hostName = host.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(hostId, date);

                List<Long> userIds = getHostUsers(hostId);
                notificationService.insertNotificationForUsers(NotificationTypeEnum.CHECK_INFECTED, issueId, userIds, date);
            }
        } 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);
        } 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);
        }
    }

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

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

    private Long fillTables(Long hostId, Date date) throws InternalException {
        return checkInfectedService.addCheckInfectedNotification(hostId, date);
    }

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

    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_HOST);
            for (Object url : urlElementList) {
                urlStringList.add(((Element) url).getAttributeValue(ATTRIBUTE_NAME));
            }
            log.debug("CheckInfected 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 setCheckInfectedService(CheckInfectedService checkInfectedService) {
        this.checkInfectedService = checkInfectedService;
    }

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

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