package ru.yandex.webmaster3.viewer.http.searchquery.statistic;

import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.http.ReadAction;
import ru.yandex.webmaster3.core.searchquery.OrderDirection;
import ru.yandex.webmaster3.core.searchquery.QueryId;
import ru.yandex.webmaster3.core.searchquery.QueryIndicator;
import ru.yandex.webmaster3.core.searchquery.SpecialGroup;
import ru.yandex.webmaster3.core.searchquery.viewer.ViewerQuery;
import ru.yandex.webmaster3.core.util.PageUtils;
import ru.yandex.webmaster3.storage.http.searchquery.statistic.StatisticsHelper;
import ru.yandex.webmaster3.storage.searchquery.AccumulatorMap;
import ru.yandex.webmaster3.storage.searchquery.DeviceType;
import ru.yandex.webmaster3.storage.searchquery.MapWithDiff;
import ru.yandex.webmaster3.storage.searchquery.QueryStat;
import ru.yandex.webmaster3.storage.searchquery.QueryStatisticsService2;
import ru.yandex.webmaster3.storage.searchquery.RangeFactory;
import ru.yandex.webmaster3.storage.searchquery.RegionInclusion;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostAction;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
 * @author aherman
 */
@ReadAction
@Category("searchquery")
public class GetQueryListStatisticsAction extends
        AbstractUserVerifiedHostAction<GetQueryListStatisticsRequest, GetQueryListStatisticsResponse> {
    private QueryStatisticsService2 queryStatisticsService2;

    @Override
    public GetQueryListStatisticsResponse process(GetQueryListStatisticsRequest request) throws WebmasterException {
        final SpecialGroup specialGroup = request.getSpecialGroup();
        final LocalDate userRangeStart = request.getLocalDateFrom();
        final LocalDate userRangeEnd = request.getLocalDateTo();
        final boolean withDiff = true;
        final List<QueryId> userSelectedQueryIds = StatisticsHelper.asQueryIds(7, request.getQueryId());
        final List<QueryIndicator> indicators = StatisticsHelper.asList(7, request.getIndicator());
        final Set<Integer> regions = StatisticsHelper.asSet(10, request.getRegionId());
        final RegionInclusion regionInclusion = request.getRegionInclusion();
        final QueryIndicator orderBy = request.getOrderBy();
        final OrderDirection orderDirection = request.getOrderDirection();

        int queriesCount =
                queryStatisticsService2.countQueries(request.getHostId(), specialGroup,
                        regions, regionInclusion,
                        userRangeStart, userRangeEnd, request.getDeviceType());
        PageUtils.Pager pager = PageUtils.getPage(request.getPage(), request.getPageSize(), queriesCount);
        if (pager.isEmpty()) {
            return new GetQueryListStatisticsResponse.NormalResponse(queriesCount, Collections.emptyList());
        }

        int limit;
        int startFrom;
        if (request.getPage() == 0) {
            startFrom = 0;
            limit = request.getPageSize() - userSelectedQueryIds.size();
        } else {
            startFrom = pager.toRangeStart() - userSelectedQueryIds.size();
            limit = request.getPageSize();
        }

        List<GetQueryListStatisticsResponse.QueryStat> result =
                getStatistics(request.getHostId(), specialGroup, userRangeStart, userRangeEnd, withDiff,
                        userSelectedQueryIds, indicators, regions,
                        regionInclusion, request.getDeviceType(),
                        orderBy, orderDirection, limit, startFrom);

        return new GetQueryListStatisticsResponse.NormalResponse(queriesCount, result);
    }

    @NotNull
    protected List<GetQueryListStatisticsResponse.QueryStat> getStatistics(WebmasterHostId hostId,
                                                                           SpecialGroup specialGroup,
                                                                           LocalDate userRangeStart, LocalDate userRangeEnd, boolean withDiff,
                                                                           List<QueryId> userSelectedQueryIds,
                                                                           List<QueryIndicator> indicators,
                                                                           Set<Integer> regions, RegionInclusion regionInclusion,
                                                                           DeviceType deviceType,
                                                                           QueryIndicator orderBy, OrderDirection orderDirection,
                                                                           int limit, int startFrom) {
        List<QueryId> queryIds =
                queryStatisticsService2.getQueryIds(hostId, specialGroup,
                        regions, regionInclusion,
                        userRangeStart, userRangeEnd,
                        userSelectedQueryIds, deviceType,
                        orderBy, orderDirection, startFrom, limit
                );
        // First page
        if (startFrom == 0 && userSelectedQueryIds.size() > 0) {
            List<QueryId> tmpQueries = new ArrayList<>(queryIds.size() + userSelectedQueryIds.size());
            tmpQueries.addAll(userSelectedQueryIds);
            tmpQueries.addAll(queryIds);
            queryIds = tmpQueries;
        }

        final RangeSet<LocalDate> rangeSet;
        if (withDiff) {
            rangeSet = RangeFactory.doubleRange(userRangeStart, userRangeEnd);
        } else {
            rangeSet = RangeFactory.singleRange(userRangeStart, userRangeEnd);
        }

        Pair<Map<QueryId, String>, List<QueryStat>> statistics = queryStatisticsService2.getQueryStatistics(
                hostId, specialGroup, indicators,
                regionInclusion, regions,
                queryIds,
                rangeSet.span().lowerEndpoint(), rangeSet.span().upperEndpoint(), deviceType);

        Map<QueryId, AccumulatorMap> accumulators = new HashMap<>();
        Function<QueryId, AccumulatorMap> accumulatorFactory = q -> AccumulatorMap.create(indicators, rangeSet);
        for (QueryStat queryStat : statistics.getValue()) {
            AccumulatorMap ac = accumulators.computeIfAbsent(queryStat.getQueryId(), accumulatorFactory);
            ac.apply(queryStat);
        }

        List<GetQueryListStatisticsResponse.QueryStat> result = new ArrayList<>();
        for (QueryId id : queryIds) {
            String queryText = statistics.getKey().get(id);
            if (!StringUtils.isEmpty(queryText)) {
                AccumulatorMap accumulatorMap = accumulators.computeIfAbsent(id, accumulatorFactory);

                List<AbstractQueryStatisticsResponse.IndicatorStats> indicatorResults = new ArrayList<>();
                for (QueryIndicator indicator : indicators) {
                    List<Pair<Range<LocalDate>, Double>> indicatorStat = accumulatorMap.getIndicator(indicator);
                    List<GroupsStatisticResponse.RangeStat> rangeStats = new ArrayList<>();
                    if (withDiff) {
                        MapWithDiff.map(indicatorStat.iterator(), (r, v, d) -> {
                            rangeStats.add(new AbstractQueryStatisticsResponse.RangeStat(
                                    r.lowerEndpoint(), r.upperEndpoint(), v, d)
                            );
                        });
                    } else {
                        Pair<Range<LocalDate>, Double> range = indicatorStat.get(0);
                        rangeStats.add(new AbstractQueryStatisticsResponse.RangeStat(
                                range.getKey().lowerEndpoint(), range.getKey().upperEndpoint(), range.getValue(), null)
                        );
                    }
                    indicatorResults.add(new AbstractQueryStatisticsResponse.IndicatorStats(indicator, rangeStats));
                }

                result.add(new GetQueryListStatisticsResponse.QueryStat(new ViewerQuery(id, queryText), indicatorResults));
            }
        }
        return result;
    }

    @Required
    public void setQueryStatisticsService2(QueryStatisticsService2 queryStatisticsService2) {
        this.queryStatisticsService2 = queryStatisticsService2;
    }
}
