package ru.yandex.wmconsole.servantlet.errors;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.common.framework.core.ServRequest;
import ru.yandex.common.framework.core.ServResponse;
import ru.yandex.common.framework.pager.Pager;
import ru.yandex.common.util.db.OrderByClause;
import ru.yandex.wmconsole.data.ErrorUrlInfoCorrespondence;
import ru.yandex.wmconsole.data.LastDaysFilterEnum;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.data.info.ErrorCountInfo;
import ru.yandex.wmconsole.data.info.ErrorUrlInfo;
import ru.yandex.wmconsole.data.info.TreeInfo;
import ru.yandex.wmconsole.data.wrappers.ErrorCountInfoWrapper;
import ru.yandex.wmconsole.data.wrappers.ErrorUrlInfoWrapper;
import ru.yandex.wmconsole.servantlet.WMCAuthorizedHostOperationServantlet;
import ru.yandex.wmconsole.service.ErrorInfoService;
import ru.yandex.wmconsole.service.ErrorTreeService;
import ru.yandex.wmconsole.util.HostElementWrapper;
import ru.yandex.wmconsole.util.ServantletUtil;
import ru.yandex.wmconsole.util.UrlErrorOrGroup;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.error.UserProblem;
import ru.yandex.wmtools.common.util.TimeFilter;
import ru.yandex.wmtools.common.util.XmlConvertableCollectionWrapper;

/**
 * Created by IntelliJ IDEA.
 * User: senin
 * Date: 20.03.2007
 * Time: 16:58:13
 */
public class ErrorInfoUrlsServantlet extends WMCAuthorizedHostOperationServantlet {
    private static final String PARAM_CODE = "code";
    private static final String PARAM_NODE_ID = "id";
    private static final String PARAM_MUST_HAVE_INT_LINKS = "int_links_only";
    private static final String PARAM_MUST_HAVE_EXT_LINKS = "ext_links_only";
    private static final String PARAM_LAST = "last";
    private static final String PARAM_MODIFIED_AFTER = "modified_after";
    private static final String PARAM_MODIFIED_BEFORE = "modified_before";

    private ErrorInfoService errorInfoService;
    private ErrorTreeService errorTreeService;

    @Override
    public void doProcess(ServRequest req, ServResponse res, long userId) throws UserException, InternalException {

        int code = getRequiredIntParam(req, PARAM_CODE);
        Long nodeId = getLongParam(req, PARAM_NODE_ID);

        BriefHostInfo briefHostInfo = getHostInfo(req, false, userId, true, true, res, true, true);
        if (briefHostInfo == null) {
            return;
        }
        // Проверяем, что сайт проиндексирован обычным роботом или быстророботом
        checkIndexed(briefHostInfo);

        String mustHaveIntLinksStr = req.getParam(PARAM_MUST_HAVE_INT_LINKS);
        boolean mustHaveIntLinks = false;
        if (!StringUtils.isEmpty(mustHaveIntLinksStr)) {
            mustHaveIntLinks = true;
        }

        String mustHaveExtLinksStr = req.getParam(PARAM_MUST_HAVE_EXT_LINKS);
        boolean mustHaveExtLinks = false;
        if (!StringUtils.isEmpty(mustHaveExtLinksStr)) {
            mustHaveExtLinks = true;
        }

        UrlErrorOrGroup error = new UrlErrorOrGroup(code);

        Pager pager = createOutputStrategy(req).createPager();
        OrderByClause order = new OrderByClause(req, ErrorInfoService.FIELD_URL, true, new ErrorUrlInfoCorrespondence());

        Calendar now = Calendar.getInstance();

        String after = null;
        String before = null;
        TimeFilter timeFilter = null;
        boolean needFilter = false;
        if (req.getParam(PARAM_MODIFIED_AFTER, true) != null || req.getParam(PARAM_MODIFIED_BEFORE, true) != null) {
            after = req.getParam(PARAM_MODIFIED_AFTER, true);
            before = req.getParam(PARAM_MODIFIED_BEFORE, true);
        } else {
            String lastDays = getStringParam(req, PARAM_LAST);
            if (LastDaysFilterEnum.ONE_WEEK.getIntegerValue().toString().equals(lastDays)) {
                now.add(Calendar.WEEK_OF_YEAR, -1);
                needFilter = true;
            } else if (LastDaysFilterEnum.TWO_WEEKS.getIntegerValue().toString().equals(lastDays)) {
                now.add(Calendar.WEEK_OF_YEAR, -2);
                needFilter = true;
            } else if (LastDaysFilterEnum.THREE_WEEKS.getIntegerValue().toString().equals(lastDays)) {
                now.add(Calendar.WEEK_OF_YEAR, -3);
                needFilter = true;
            }
        }

        if ((after != null) || (before != null)) {
            try {
                timeFilter = TimeFilter.create(after, before);
            } catch (NumberFormatException e) {
                throw new UserException(UserProblem.ILLEGAL_VALUE_TYPE, "failed to parse params", e);
            }
        } else if (needFilter) {
            Integer year = now.get(Calendar.YEAR);
            Integer month = now.get(Calendar.MONTH) + 1;
            Integer day = now.get(Calendar.DATE);
            after = year.toString() + "/" + month.toString() + "/" + day.toString();
        }

        if (timeFilter == null) {
            try {
                timeFilter = TimeFilter.create(after, null, "yyyy/MM/dd");
            } catch (ParseException e) {
                throw new UserException(UserProblem.ILLEGAL_VALUE_TYPE, "failed to parse date", e);
            }
        }

        final List<ErrorUrlInfo> errorUrlInfos;
        if (nodeId == null) {
            errorUrlInfos = getSafeUrlsWithErrorByCode(userId, briefHostInfo, error, pager, order, timeFilter, mustHaveIntLinks, mustHaveExtLinks);
        } else {
            TreeInfo treeInfo = errorTreeService.getErrorTreeInfoByCode(userId, briefHostInfo, nodeId, code);
            List<Long> nodes = new ArrayList<Long>();
            visitNode(treeInfo.getSelectedNode(), nodes);
            if (nodes.isEmpty()) {
                errorUrlInfos = Collections.emptyList();
            } else {
                errorUrlInfos = getNodesSafeUrlsWithErrorByCode(userId, briefHostInfo, error, pager, order, timeFilter, mustHaveIntLinks, mustHaveExtLinks, nodes);
            }
        }

        XmlConvertableCollectionWrapper wrappedUrlErrors =
                XmlConvertableCollectionWrapper.wrap(
                        errorUrlInfos,
                        ErrorUrlInfoWrapper.class,
                        "url-errors-for-code",
                        "code",
                        Integer.toString(code)
                );
        res.addData(new HostElementWrapper(wrappedUrlErrors, briefHostInfo));

        final ErrorCountInfo errorCountInfo;
        if (nodeId != null) {
            errorCountInfo = errorInfoService.calcErrorCountInfoForCodeAndNode(briefHostInfo, error, nodeId, timeFilter, mustHaveIntLinks, mustHaveExtLinks, null);
        } else {
            errorCountInfo = errorInfoService.calcErrorCountInfo(briefHostInfo, error, timeFilter, mustHaveIntLinks, mustHaveExtLinks, null);
        }
        res.addData(new ErrorCountInfoWrapper(errorCountInfo));

        res.addData(pager);
    }

    private void visitNode(ru.yandex.wmconsole.data.Node currentNode, List<Long> nodes) {
        if (currentNode.getInfo().getUrlsInSubtree() > 0) {
            nodes.add(currentNode.getInfo().getId());
            if (currentNode.getChildren() != null && currentNode.getChildren().size() > 0) {
                for (ru.yandex.wmconsole.data.Node child : currentNode.getChildren()) {
                    visitNode(child, nodes);
                }
            }
        }
    }

    private List<ErrorUrlInfo> getSafeUrlsWithErrorByCode(long userId, BriefHostInfo hostInfo, UrlErrorOrGroup error, Pager pager, OrderByClause order, final TimeFilter timeFilter, Boolean mustHaveIntLinks, Boolean mustHaveExtLinks) throws InternalException {
        List<ErrorUrlInfo> errorUrlInfos;

        if (ServantletUtil.isErrorCodeSafeToShow(error)) {
            errorUrlInfos = errorInfoService.getUrlsWithErrorsByCode(pager, order, userId, hostInfo, error, timeFilter, mustHaveIntLinks, mustHaveExtLinks, null);
        } else {
            errorUrlInfos = new ArrayList<ErrorUrlInfo>();
        }

        return errorUrlInfos;
    }

    private List<ErrorUrlInfo> getNodesSafeUrlsWithErrorByCode(long userId, BriefHostInfo hostInfo, UrlErrorOrGroup error, Pager pager, OrderByClause order, final TimeFilter timeFilter, Boolean mustHaveIntLinks, Boolean mustHaveExtLinks, List<Long> nodeIds) throws InternalException {
        List<ErrorUrlInfo> errorUrlInfos;

        if (ServantletUtil.isErrorCodeSafeToShow(error)) {
            errorUrlInfos = errorTreeService.getNodesErrorUrlsByCode(userId, hostInfo, nodeIds, error, pager, order, timeFilter, mustHaveIntLinks, mustHaveExtLinks);
        } else {
            errorUrlInfos = new ArrayList<ErrorUrlInfo>();
        }

        return errorUrlInfos;
    }

    @Required
    public void setErrorInfoService(ErrorInfoService errorInfoService) {
        this.errorInfoService = errorInfoService;
    }

    @Required
    public void setErrorTreeService(ErrorTreeService errorTreeService) {
        this.errorTreeService = errorTreeService;
    }
}
