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.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.ReadAction;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.searchquery.IndicatorUsage;
import ru.yandex.webmaster3.core.searchquery.QueryId;
import ru.yandex.webmaster3.core.searchquery.QueryIndicator;
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.AggregatePeriod;
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.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;
import java.util.stream.Collectors;

/**
 * @author aherman
 */
@ReadAction
@Category("searchquery")
public class GetQueriesIndicatorAction extends
        AbstractUserVerifiedHostAction<GetQueriesIndicatorRequest, QueryStatisticsResponse> {
    private QueryStatisticsService2 queryStatisticsService2;

    @Override
    public QueryStatisticsResponse process(GetQueriesIndicatorRequest request) throws WebmasterException {
        final LocalDate userRangeStart = request.getLocalDateFrom();
        final LocalDate userRangeEnd = request.getLocalDateTo();
        final List<QueryId> userSelectedQueryIds = StatisticsHelper.asQueryIds(7, request.getQueryId());
        final Set<Integer> regions = StatisticsHelper.asSet(10, request.getRegionId());
        if (!request.getIndicator().isUsable(IndicatorUsage.IN_STATISTICS)) {
            throw new WebmasterException("Indicators is empty",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "indicator", null));
        }
        List<QueryIndicator> indicators = Collections.singletonList(request.getIndicator());
        final AggregatePeriod period = request.getPeriod();

        int queriesCount;
        PageUtils.Pager pager;

        queriesCount = queryStatisticsService2.countQueries(request.getHostId(), request.getSpecialGroup(),
                regions, request.getRegionInclusion(),
                userRangeStart, userRangeEnd, request.getDeviceType());
        pager = PageUtils.getPage(request.getPage(), request.getPageSize(), queriesCount);
        if (pager.isEmpty()) {
            return new QueryStatisticsResponse.NormalResponse(queriesCount, Collections.emptyList(), 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<QueryId> queryIds = queryStatisticsService2
                .getQueryIds(request.getHostId(), request.getSpecialGroup(),
                        regions, request.getRegionInclusion(),
                        userRangeStart, userRangeEnd,
                        userSelectedQueryIds, request.getDeviceType(),
                        request.getOrderBy(), request.getOrderDirection(),
                        startFrom, limit);
        if (pager.getPage() == 0) {
            List<QueryId> tmpQueries = new ArrayList<>(queryIds.size() + userSelectedQueryIds.size());
            tmpQueries.addAll(userSelectedQueryIds);
            tmpQueries.addAll(queryIds);
            queryIds = tmpQueries;
        }
        Map<QueryId, String> idToQueryText = queryStatisticsService2.getQueryTexts(request.getHostId(),
                request.getSpecialGroup(), queryIds);

        RangeSet<LocalDate> userRangeSet = RangeFactory.doubleRange(userRangeStart, userRangeEnd);
        RangeSet<LocalDate> intervalRangeSet = RangeFactory.createRanges(userRangeEnd, period, request.getMaxRanges() + 1, false);

        Range<LocalDate> queryRange = userRangeSet.span().span(intervalRangeSet.span());
        List<QueryStat> wholeStat = queryStatisticsService2.getStatistics(request.getSpecialGroup(), request.getHostId(),
                indicators, queryIds,
                regions, request.getRegionInclusion(),
                queryRange.lowerEndpoint(), queryRange.upperEndpoint(), request.getDeviceType());

        List<QueryStatisticsResponse.QueryStat> result = new ArrayList<>();

        Map<QueryId, Pair<AccumulatorMap, AccumulatorMap>> accumulators = new HashMap<>();
        Function<QueryId, Pair<AccumulatorMap, AccumulatorMap>> accumulatorFactory = q -> Pair.of(
                AccumulatorMap.create(indicators, userRangeSet),
                AccumulatorMap.create(indicators, intervalRangeSet)
        );
        for (QueryStat queryStat : wholeStat) {
            Pair<AccumulatorMap, AccumulatorMap> ac = accumulators.computeIfAbsent(queryStat.getQueryId(), accumulatorFactory);

            ac.getLeft().apply(queryStat);
            ac.getRight().apply(queryStat);
        }

        for (QueryId id : queryIds) {
            String queryText = idToQueryText.get(id);
            if (!StringUtils.isEmpty(queryText)) {
//                List<QueryStat> qStat =
//                        wholeStat.stream().filter(qs -> qs.getQueryId().equals(id)).collect(Collectors.toList());

//                AccumulatorMap userRangeAccumulator = AccumulatorMap.create(indicators, userRangeSet);
//                AccumulatorMap intervalAccumulator = AccumulatorMap.create(indicators, intervalRangeSet);

//                qStat.forEach(userRangeAccumulator::apply);
//                qStat.forEach(intervalAccumulator::apply);

                Pair<AccumulatorMap, AccumulatorMap> ac = accumulators.computeIfAbsent(id, accumulatorFactory);
                AccumulatorMap userRangeAccumulator = ac.getLeft();
                AccumulatorMap intervalAccumulator = ac.getRight();

                for (QueryIndicator indicator : indicators) {
                    List<Pair<Range<LocalDate>, Double>> indicatorStat = intervalAccumulator.getIndicator(indicator);

                    List<GroupsStatisticResponse.RangeStat> rangeStats = new ArrayList<>();
                    MapWithDiff.map(indicatorStat.iterator(), (r, v, d) -> {
                        rangeStats.add(new AbstractQueryStatisticsResponse.RangeStat(r.lowerEndpoint(), r.upperEndpoint(), v, d));
                    });

                    List<Pair<Range<LocalDate>, Double>> intervalStat = userRangeAccumulator.getIndicator(indicator);
                    MapWithDiff.map(intervalStat.iterator(), (r, v, d) -> {
                        rangeStats.add(new AbstractQueryStatisticsResponse.RangeStat(r.lowerEndpoint(), r.upperEndpoint(), v, d));
                    });
                    Collections.reverse(rangeStats);

                    AbstractQueryStatisticsResponse.IndicatorStats indicatorStats =
                            new AbstractQueryStatisticsResponse.IndicatorStats(indicator, rangeStats);

                    result.add(new QueryStatisticsResponse.QueryStat(new ViewerQuery(id, queryText),
                            indicatorStats));
                }
            }
        }

        List<QueryStatisticsResponse.DateRange> ranges = intervalRangeSet.asRanges().stream()
                .skip(1)
                .map(r -> new QueryStatisticsResponse.DateRange(r.lowerEndpoint(), r.upperEndpoint(), false))
                .collect(Collectors.toList());
        ranges.add(new QueryStatisticsResponse.DateRange(userRangeStart, userRangeEnd, true));
        Collections.reverse(ranges);

        return new QueryStatisticsResponse.NormalResponse(queriesCount, ranges, result);
    }

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