package ru.yandex.wmconsole.servantlet;

import java.util.Date;
import java.util.List;

import com.codahale.metrics.MetricRegistry;
import org.jetbrains.annotations.Nullable;
import org.joda.time.DateTime;
import org.joda.time.Hours;
import org.joda.time.ReadablePeriod;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.common.framework.core.ServRequest;
import ru.yandex.common.framework.core.ServResponse;
import ru.yandex.wmconsole.data.HostInfoStatusEnum;
import ru.yandex.wmconsole.data.UpdateStateEnum;
import ru.yandex.wmconsole.data.info.BriefHostInfo;
import ru.yandex.wmconsole.data.info.HostDbHostInfo;
import ru.yandex.wmconsole.data.info.HostInfo;
import ru.yandex.wmconsole.data.info.MainPageErrorInfo;
import ru.yandex.wmconsole.data.info.SearchUpdateIntervalInfo;
import ru.yandex.wmconsole.data.info.SearchUpdateNotificationInfo;
import ru.yandex.wmconsole.data.wrappers.HostInfoWrapper;
import ru.yandex.wmconsole.data.wrappers.MainPageErrorWrapper;
import ru.yandex.wmconsole.service.DispatcherHttpService;
import ru.yandex.wmconsole.service.ErrorInfoService;
import ru.yandex.wmconsole.service.HostDbHostInfoService;
import ru.yandex.wmconsole.service.dao.TblLogUpdaterDao;
import ru.yandex.wmconsole.service.dao.TblNotificationSearchupdate;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.error.UserException;
import ru.yandex.wmtools.common.util.XmlDataWrapper;

public class HostInfoServantlet extends WMCAuthorizedHostOperationServantlet {
    private static final Logger log = LoggerFactory.getLogger(HostInfoServantlet.class);
    private static final Logger monLog = LoggerFactory.getLogger(HostInfoServantlet.class.getName());

    public static final int LAST_UPDATES_NUMBER = 2;
    public static final long MAX_LAST_LOADER_TIME_TO_UPLOAD = 1000;

    private static int INDEXING_MONITORING_THREASHOLD = 0;
    private static String INDEXING_MONITORING_ID = "FAST_ROBOT_HAS_INDEXED_MORE_PAGES (urls:%d indexCount:%d)";

    private static final ReadablePeriod DEFAULT_UPDATE_WARNING_SHOW_PERIOD = Hours.hours(24);

    private ErrorInfoService errorInfoService;

    private TblLogUpdaterDao tblLogUpdaterDao;
    private TblNotificationSearchupdate tblNotificationSearchupdate;
    private DispatcherHttpService dispatcherHttpService;
    private HostDbHostInfoService hostDbHostInfoService;

    private MetricRegistry metricRegistry;

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

        final BriefHostInfo briefHostInfo = getHostInfoAndCheckHostAdded(req, userId);
        HostInfo hostInfo = getHostInfoService().getHostInfo(userId, briefHostInfo);

        if (hostInfo.getVerificationState().isVerified()) {
            List<SearchUpdateIntervalInfo> lastUpdateIntervals = tblLogUpdaterDao.getLastUpdateIntervals(
                    LAST_UPDATES_NUMBER);
            SearchUpdateIntervalInfo currentUpdateInterval = null;
            SearchUpdateIntervalInfo previousUpdateInterval = null;
            if (!lastUpdateIntervals.isEmpty()) {
                currentUpdateInterval = lastUpdateIntervals.get(0);
                if (lastUpdateIntervals.size() > 1) {
                    previousUpdateInterval = lastUpdateIntervals.get(1);
                }
            }
            SearchUpdateNotificationInfo lastSearchUpdateNotification =
                    tblNotificationSearchupdate.getLastSearchUpdateNotification();

            DateTime now = new DateTime();
            if (isUpdateWarningNeeded(currentUpdateInterval, hostInfo, lastSearchUpdateNotification, now)) {
                boolean updated = false;
                if (hostInfo.getLastLoaderTime() != null &&
                        hostInfo.getLastLoaderTime() < MAX_LAST_LOADER_TIME_TO_UPLOAD) {
                    try {
                        Date updateTime = currentUpdateInterval == null ? new Date() : currentUpdateInterval.getStartTime().toDate();
                        HostDbHostInfo hostDbHostInfo = hostDbHostInfoService.getHostDbHostInfo(briefHostInfo.getName());
                        dispatcherHttpService.uploadHostData(hostDbHostInfo.getName(), hostDbHostInfo.getHostDbHostId(), updateTime);
                        metricRegistry.meter("monitoring.hostInfo.upload").mark();
                        updated = true;
                    } catch (Exception e) {
                        log.warn("unable to upload host data for " + briefHostInfo.getName());
                    }
                }

                if (updated) {
                    // Обновляем hostInfo
                    hostInfo = getHostInfoService().getHostInfo(userId, briefHostInfo);
                } else {
                    UpdateInProgressWarning updateInProgressWarning = new UpdateInProgressWarning(previousUpdateInterval);
                    metricRegistry.meter("monitoring.hostInfo.updateInProgress").mark();
                    res.addData(new UpdateInProgressWarningWrapper(updateInProgressWarning));
                }
            }
        }
        res.addData(new HostInfoWrapper(hostInfo, false));

        try {
            monitoring(hostInfo);
        } catch (Exception e) {
            log.error("Exception during monitoring " + e.getMessage());
        }

        HostInfoStatusEnum calculatedStatus = hostInfo.getCalculatedHostInfoStatus();
        if (calculatedStatus == null) {
            // В этом случае статус точно не показывается,
            // значит нужно проверить, что сайт проиндексирован (urls > 0 или indexCount > 0)
            checkIndexed(briefHostInfo);
        }

        if (calculatedStatus == null || !HostInfoStatusEnum.isPenaltyStatus(calculatedStatus)) {
            // Сообщение про главную страницу показывается только если нет статуса пенальти
            MainPageErrorInfo mainPageErrorInfo = errorInfoService.getMainPageError(userId, briefHostInfo);
            if (mainPageErrorInfo != null) {
                res.addData(new MainPageErrorWrapper(mainPageErrorInfo));
            }
        }
    }

    private boolean isUpdateWarningNeeded(SearchUpdateIntervalInfo latestSearchUpdateInterval, HostInfo hostInfo,
            SearchUpdateNotificationInfo lastSearchUpdateNotification, DateTime now)
    {
        if (latestSearchUpdateInterval == null) {
            return false;
        }
        if (lastSearchUpdateNotification == null) {
            return false;
        }
        if (lastSearchUpdateNotification.getNotificationTime().isBefore(latestSearchUpdateInterval.getStartTime())) {
            return false;
        }

        if (hostInfo.getUpdateState() == UpdateStateEnum.UPDATED) {
            return false;
        }

        Date updatedOn = hostInfo.getUpdatedOn();
        if (updatedOn == null) {
            return false;
        }

        DateTime updatedOnDateTime = new DateTime(updatedOn.getTime());
        if (updatedOnDateTime.isBefore(latestSearchUpdateInterval.getStartTime())) {
            // If update date too old and notification is already sent then this site will not be updated this time
            return false;
        }

        if (hostInfo.getUpdateState() == UpdateStateEnum.WAITING || hostInfo.getUpdateState() == UpdateStateEnum.IN_PROGRESS) {
            if (latestSearchUpdateInterval.getEndTime() == null) {
                return true;
            }

            if (now.isBefore(latestSearchUpdateInterval.getStartTime().plus(DEFAULT_UPDATE_WARNING_SHOW_PERIOD))) {
                // Updater finished, but host didn't updated due to an error in updater
                // If host wasn't fixed in one day we wouldn't show notification
                return true;
            } else {
                return false;
            }
        }

        return false;
    }

    /**
     * Мониторинг ситуации, когда от быстроробота больше данных, чем от обычного робота
     *
     * @param hostInfo
     */
    private void monitoring(final HostInfo hostInfo) {
        if (hostInfo == null) {
            return;
        }
        // загружено страниц (информация от обычного робота)
        final Long urlCount = hostInfo.getUrlCount();
        // страниц в поиске (информация от быстрого робота)
        final Long indexCount = hostInfo.getIndexCount();
        long uc = urlCount == null ? 0 : urlCount;
        long ic = indexCount == null ? 0 : indexCount;
        // Учет подстановки
        if (uc == 0 && ic > 0) {
            uc = ic;
        }
        // Мониторинг ситуации, когда
        if (INDEXING_MONITORING_THREASHOLD < uc && uc < ic) {
            monLog.warn(String.format(INDEXING_MONITORING_ID, uc, ic));
        }
    }

    private static class UpdateInProgressWarning {
        @Nullable
        private final SearchUpdateIntervalInfo searchUpdateIntervalInfo;

        private UpdateInProgressWarning(@Nullable SearchUpdateIntervalInfo searchUpdateIntervalInfo) {
            this.searchUpdateIntervalInfo = searchUpdateIntervalInfo;
        }

        @Nullable
        public SearchUpdateIntervalInfo getSearchUpdateIntervalInfo() {
            return searchUpdateIntervalInfo;
        }
    }

    private static class UpdateInProgressWarningWrapper extends XmlDataWrapper<UpdateInProgressWarning> {
        public UpdateInProgressWarningWrapper(UpdateInProgressWarning data) {
            super(data, "update-in-progress-warning");
        }

        @Override
        protected void doToXml(StringBuilder result) {
            SearchUpdateIntervalInfo searchUpdateIntervalInfo = data.getSearchUpdateIntervalInfo();
            if (searchUpdateIntervalInfo != null) {
                putTag(result, "site-updated-on",
                        searchUpdateIntervalInfo.getStartTime().toString(ISODateTimeFormat.dateTimeNoMillis()));
            }
        }
    }

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

    @Required
    public void setTblLogUpdaterDao(TblLogUpdaterDao tblLogUpdaterDao) {
        this.tblLogUpdaterDao = tblLogUpdaterDao;
    }

    @Required
    public void setTblNotificationSearchupdate(TblNotificationSearchupdate tblNotificationSearchupdate) {
        this.tblNotificationSearchupdate = tblNotificationSearchupdate;
    }

    @Required
    public void setDispatcherHttpService(DispatcherHttpService dispatcherHttpService) {
        this.dispatcherHttpService = dispatcherHttpService;
    }

    @Required
    public void setHostDbHostInfoService(HostDbHostInfoService hostDbHostInfoService) {
        this.hostDbHostInfoService = hostDbHostInfoService;
    }

    @Required
    public void setMetricRegistry(MetricRegistry metricRegistry) {
        this.metricRegistry = metricRegistry;
    }
}
