package ru.yandex.wmtools.common.servantlet.top;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.wmtools.common.data.info.HostRegionsInfo;
import ru.yandex.wmtools.common.data.info.RegionInfo;
import ru.yandex.wmtools.common.data.info.TopInfo;
import ru.yandex.wmtools.common.data.info.TopInfoItem;
import ru.yandex.wmtools.common.data.info.TotalsInfo;
import ru.yandex.wmtools.common.data.wrappers.HostRegionsInfoWrapper;
import ru.yandex.wmtools.common.data.wrappers.RegionInfoTreeNodeWrapper;
import ru.yandex.wmtools.common.error.ExtraTagInfo;
import ru.yandex.wmtools.common.error.ExtraTagNameEnum;
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.servantlet.AbstractServantlet;
import ru.yandex.wmtools.common.servantlet.AuthenticationServantlet;
import ru.yandex.wmtools.common.service.CustomRegionService;
import ru.yandex.wmtools.common.service.RegionsTreeCacheService;
import ru.yandex.wmtools.common.service.TopInfoService;
import ru.yandex.wmtools.common.service.TopType;
import ru.yandex.wmtools.common.service.TopTypeEnum;
import ru.yandex.wmtools.common.util.XmlConvertableCollectionWrapper;
import ru.yandex.wmtools.common.util.XmlDataWrapper;

/**
 * @author avhaliullin
 */
public abstract class WMToolsQueriesInfoServantlet
        <HostInfoType, TI extends TopInfo, TII extends TopInfoItem, TTL extends TotalsInfo>
        extends AuthenticationServantlet {
    public static class TopInfoCorrespondence extends HashMap<String, String> {}

    private static final Logger log = LoggerFactory.getLogger(WMToolsQueriesInfoServantlet.class);

    public static final boolean INCLUDE_SUBREGIONS_BY_DEFAULT = true;
    public static final int HISTORY_LENGTH = 0;

    private static final String PARAM_DATE = "date";
    private static final String PARAM_REGION = "region";
    private static final String PARAM_TYPE = "type";
    private static final String PARAM_INCLUDE_SUBREGIONS = "include_subregions";

    private static final String TAG_EMPTY_TOP_DATA = "empty-top-data";
    private static final String TAG_INVALID_REGION = "invalid-user-region";
    private static final String TAG_INVALID_DATE = "invalid-user-date";

    abstract protected RegionsTreeCacheService getRegionsTreeCacheService();
    abstract protected TopInfoService<HostInfoType, TI, TII, TTL> getTopInfoService();
    abstract protected CustomRegionService<HostInfoType> getCustomRegionService();

    @Override
    public void doProcess(ServRequest req, ServResponse res, long userId) throws UserException, InternalException {
        HostInfoType hostInfo = getHostInfoType(req, res, userId);
        if (hostInfo == null) {
            return;
        }

        TopType topType = isTopInfoTypeRequired() ? initTopType(AbstractServantlet.getStringParam(req, PARAM_TYPE)) : null;

        Boolean includeSubregions = AbstractServantlet.getBooleanParam(req, PARAM_INCLUDE_SUBREGIONS);
        includeSubregions = includeSubregions == null ? INCLUDE_SUBREGIONS_BY_DEFAULT : includeSubregions;
        Integer userRegion = AbstractServantlet.getIntParam(req, PARAM_REGION);
        Integer region = userRegion;
        if (region != null && region < 0) {
            region = 0;
        }
        Date userDate = getDateParam(req, PARAM_DATE);
        Date date = getLatestInfoDateIfDateIsNull(null, hostInfo, userDate, includeSubregions);
        log.debug("Selected date (1) is " + date);
        if (date == null) {
            if (userDate != null) {
                date = getLatestInfoDateIfDateIsNull(null, hostInfo, null, includeSubregions);
                if (date != null) {
                    log.debug("invalid user date " + userDate + " replaced with " + date);
                    res.addData(new NoTopDataWrapper(null, userDate, TAG_INVALID_DATE));
                }
            }

            if (date == null) {
                // отсутствуют даты для данного хоста
                throw new UserException(
                        UserProblem.INVALID_TOP_DATE,
                        "top date is not allowed");
            }
        }
        log.debug("Selected date (2) is " + date);

        List<RegionInfo> keyRegions = getCustomRegionService().getKeyRegionsForHostAndDate(hostInfo, date);
        if (!getTopInfoService().existsInKeyRegions(region, keyRegions)) {
            // Выбранный регион отсутствует в списке регионов.
            // сбрасываем регион, чтобы выбрать регион по-умолчанию или Общую статистику
            region = null;
        }
        outputKeyRegionsTree(res, keyRegions);

        // invariant for variable "region":
        //  - "other regions" => 0
        //  - "summmary" => -1
        //  - home region, otherwise "summary" => null
        //  - specified region >= more than 0

        if (region == null) {
            if (userRegion != null) {
                log.debug("Invalid region");
                res.addData(new NoTopDataWrapper(userRegion, null, TAG_INVALID_REGION));
            }

            region = tryToUseAndOutputHomeRegion(res, hostInfo, keyRegions);
        }
        if (region == null) {
            region = 0;
        }

        // invariant for variable "region":
        //  - "other regions" => 0
        //  - no home regions, or home region is not a key region, or "summary" required => null
        //  - home region => more than 0
        //  - specified region >= more than 0

        Map<Date, Boolean> dates = getInfoDatesForRegionByHost(region, hostInfo, includeSubregions);

        OrderByClause orderBy = new OrderByClause(req, topType == null ? getFieldShows() : topType.getOrderByDbField(), false, getTopInfoCorrespondence());

        final Pager pager = isPagerNeeded() ? getOutputStrategy().createPager() : null;
        TI topInfo = getTopInfo(region, keyRegions, hostInfo, date, orderBy, topType == null ? null : topType.getTopTypeEnum(), includeSubregions, pager);

        outputData(res, hostInfo, dates, date, region, pager, topInfo);
    }

    abstract protected String getFieldShows();
    abstract protected String getFieldClicks();

    abstract protected TopInfoCorrespondence getTopInfoCorrespondence();

    abstract protected void outputData(ServResponse res, final HostInfoType hostInfo, final Map<Date, Boolean> dates, final Date selectedDate, final Integer selectedRegion, final Pager pager, final TI topInfo);

    abstract protected HostInfoType getHostInfoType(ServRequest req, ServResponse res, long userId) throws UserException, InternalException;

    private TopType initTopType(String paramType) throws UserException {
        TopType topType;
        if ("shows".equals(paramType)) {
            topType = new TopType(TopTypeEnum.SHOWS, getFieldShows());
        } else if ("clicks".equals(paramType)) {
            topType = new TopType(TopTypeEnum.CLICKS, getFieldClicks());
        } else {
            throw new UserException(UserProblem.ILLEGAL_PARAM_VALUE, "Parameter " + PARAM_TYPE + " has illegal value: " + paramType,
                    new ExtraTagInfo(ExtraTagNameEnum.PARAM, PARAM_TYPE), new ExtraTagInfo(ExtraTagNameEnum.VALUE, paramType));
        }
        return topType;
    }

    private void outputKeyRegionsTree(ServResponse res, List<RegionInfo> keyRegions) throws InternalException {
        //TODO: КОСТЫЛЬ!!!
        List<RegionInfo> list = new ArrayList<RegionInfo>(keyRegions);
        for (Iterator<RegionInfo> i = list.iterator(); i.hasNext();) {
            int region = i.next().getId();
            if (region == 0 || region == 10000) {
                i.remove();
            }
        }
        res.addData(XmlConvertableCollectionWrapper.wrap(getRegionsTreeCacheService().getTreeWithCountries(list),
                RegionInfoTreeNodeWrapper.class, "key-regions"));
    }

    private Integer tryToUseAndOutputHomeRegion(ServResponse res, HostInfoType hostInfo, List<RegionInfo> keyRegions) throws InternalException {
        HostRegionsInfo homeRegion = getTopInfoService().tryToUseHomeRegion(hostInfo, keyRegions);

        if (homeRegion == null) {
            return null;
        }

        Integer result = homeRegion.getRegions().iterator().next().getRegionInfo().getId();
        res.addData(new HostRegionsInfoWrapper(homeRegion));
        return result;
    }

    protected abstract TI getTopInfo(Integer region, List<RegionInfo> keyRegions, HostInfoType hostInfo,
                                             Date date, OrderByClause orderBy, TopTypeEnum type, boolean includeSubregions, Pager pager)
            throws InternalException;

    protected abstract boolean isPagerNeeded();

    protected abstract boolean isTopInfoTypeRequired();

    protected abstract Date getLatestInfoDateIfDateIsNull(Integer region, HostInfoType hostInfoType, Date date, boolean includeSubregions) throws InternalException;

    protected abstract List<Date> getInfoDatesByHost(final HostInfoType hostInfoType) throws InternalException;

    protected abstract Map<Date, Boolean> getInfoDatesForRegionByHost(Integer region, HostInfoType hostInfoType, boolean includeSubRegion) throws InternalException;

    public static class NoTopDataWrapper extends XmlDataWrapper<Integer> {
        private static final String TAG_REGION = "region";
        private static final String TAG_DATE = "date";

        private Date selectedDate;

        public NoTopDataWrapper(Integer regionId, Date date, String name) {
            super(regionId, name);
            this.selectedDate = date;
        }

        @Override
        protected void doToXml(StringBuilder result) {
            putIntegerTag(result, TAG_REGION, data);
            putDateTag(result, TAG_DATE, selectedDate);
        }
    }
}
