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.Autowired;
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.PathId;
import ru.yandex.webmaster3.core.searchquery.QueryIndicator;
import ru.yandex.webmaster3.core.searchquery.viewer.ViewerPath;
import ru.yandex.webmaster3.core.util.PageUtils;
import ru.yandex.webmaster3.storage.http.searchquery.statistic.StatisticsHelper;
import ru.yandex.webmaster3.storage.searchquery.*;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostAction;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author lester
 */
@ReadAction
@Category("searchquery")
public class GetUrlIndicatorAction extends
        AbstractUserVerifiedHostAction<GetUrlIndicatorRequest, UrlStatisticsResponse> {

    @Autowired
    private UrlStatisticsService2 urlStatisticsService2;

    @Override
    public UrlStatisticsResponse process(GetUrlIndicatorRequest request) throws WebmasterException {
        final LocalDate userRangeStart = request.getLocalDateFrom();
        final LocalDate userRangeEnd = request.getLocalDateTo();
        final List<PathId> userSelectedPathIds = StatisticsHelper.asPathIds(7, request.getPathId());
        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 pathCount;
        PageUtils.Pager pager;

        pathCount = urlStatisticsService2.countPaths(request.getHostId(), request.getSpecialGroup(),
                regions, request.getRegionInclusion(),
                userRangeStart, userRangeEnd, request.getDeviceType());
        pager = PageUtils.getPage(request.getPage(), request.getPageSize(), pathCount);
        if (pager.isEmpty()) {
            return new UrlStatisticsResponse.NormalResponse(pathCount, Collections.emptyList(), Collections.emptyList());
        }
        int limit;
        int startFrom;
        if (request.getPage() == 0) {
            startFrom = 0;
            limit = request.getPageSize() - userSelectedPathIds.size();
        } else {
            startFrom = pager.toRangeStart() - userSelectedPathIds.size();
            limit = request.getPageSize();
        }

        List<PathId> pathIds = urlStatisticsService2
                .getPathIds(request.getHostId(), request.getSpecialGroup(),
                        regions, request.getRegionInclusion(),
                        userRangeStart, userRangeEnd,
                        userSelectedPathIds, request.getDeviceType(),
                        request.getOrderBy(), request.getOrderDirection(),
                        startFrom, limit);
        if (pager.getPage() == 0) {
            List<PathId> tmpPaths = new ArrayList<>(pathIds.size() + userSelectedPathIds.size());
            tmpPaths.addAll(userSelectedPathIds);
            tmpPaths.addAll(pathIds);
            pathIds = tmpPaths;
        }
        Map<PathId, String> idToPathText = urlStatisticsService2.getPathTexts(request.getHostId(),
                request.getSpecialGroup(), pathIds);

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

        Range<LocalDate> pathRange = userRangeSet.span().span(intervalRangeSet.span());
        List<PathStat> wholeStat = urlStatisticsService2.getStatistics(request.getSpecialGroup(), request.getHostId(),
                indicators, pathIds,
                regions, request.getRegionInclusion(),
                pathRange.lowerEndpoint(), pathRange.upperEndpoint(), request.getDeviceType());

        List<UrlStatisticsResponse.PathStat> result = new ArrayList<>();

        Map<PathId, Pair<AccumulatorMap, AccumulatorMap>> accumulators = new HashMap<>();
        Function<PathId, Pair<AccumulatorMap, AccumulatorMap>> accumulatorFactory = q -> Pair.of(
                AccumulatorMap.create(indicators, userRangeSet),
                AccumulatorMap.create(indicators, intervalRangeSet)
        );
        for (PathStat pathStat : wholeStat) {
            Pair<AccumulatorMap, AccumulatorMap> ac = accumulators.computeIfAbsent(pathStat.getPathId(), accumulatorFactory);

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

        for (PathId id : pathIds) {
            String pathText = idToPathText.get(id);
            if (!StringUtils.isEmpty(pathText)) {
                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 UrlStatisticsResponse.PathStat(new ViewerPath(id, pathText),
                            indicatorStats));
                }
            }
        }

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

        return new UrlStatisticsResponse.NormalResponse(pathCount, ranges, result);
    }
}
