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

import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.core.http.ReadAction;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.searchquery.QueryGroupId;
import ru.yandex.webmaster3.core.searchquery.QueryIndicator;
import ru.yandex.webmaster3.core.searchquery.SpecialGroup;
import ru.yandex.webmaster3.core.searchquery.viewer.QueryGroupConverter;
import ru.yandex.webmaster3.core.searchquery.viewer.ViewerQueryGroup;
import ru.yandex.webmaster3.storage.searchquery.*;
import ru.yandex.webmaster3.storage.searchquery.util.Accumulator;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostAction;

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

import static ru.yandex.webmaster3.viewer.http.searchquery.rivals.GetRivalsStatisticsResponse.*;

/**
 * Created by Oleg Bazdyrev on 10/11/2017.
 */
@ReadAction
@Category("searchquery")
@Description("Получение статистики по конкурентам")
public class GetRivalsStatisticsAction extends AbstractUserVerifiedHostAction<GetRivalsStatisticsRequest, GetRivalsStatisticsResponse> {
    private static final List<QueryIndicator> DEFAULT_INDICATORS = Lists.newArrayList(
            QueryIndicator.WEIGHTED_CLICKS,
            QueryIndicator.WEIGHTED_RIVALS_CLICKS
    );

    @Autowired
    private GroupStatisticsService2 groupStatisticsService2;

    @Override
    public GetRivalsStatisticsResponse process(GetRivalsStatisticsRequest request) {
        final AggregatePeriod period = request.getPeriod();
        final LocalDate userRangeStart = request.getLocalDateFrom();
        final LocalDate userRangeEnd = request.getLocalDateTo();
        final List<QueryIndicator> indicators = ArrayUtils.isEmpty(request.getIndicator()) ?
                DEFAULT_INDICATORS : Arrays.asList(request.getIndicator());

        RangeSet<LocalDate> userRangeSet = RangeFactory.doubleRange(userRangeStart, userRangeEnd);
        RangeSet<LocalDate> intervalsRangeSet = RangeFactory.createRanges(
                userRangeStart.minus(period.getPeriod()), userRangeEnd, period, true, true);
        int rangesCount = intervalsRangeSet.asRanges().size() - 1;

        QueryGroupId queryGroupId = QueryGroupId.createThematicsId(request.getHostId(), request.getThematics());
        ViewerQueryGroup viewerQueryGroup = QueryGroupConverter.toViewerGroup(SpecialGroup.RIVALS_STATS);
        Range<LocalDate> queryRange = userRangeSet.span().span(intervalsRangeSet.span());

        LocalDate normUpperEndpoint = userRangeStart;
        LocalDate normLowerEndpoint = userRangeStart.minusMonths(1);
        LocalDate lowestEndpoint = queryRange.lowerEndpoint();
        if (normLowerEndpoint.isBefore(lowestEndpoint)) {
            lowestEndpoint = normLowerEndpoint;
        }

        Map<QueryGroupId, List<GroupStat>> groupStatsAll = groupStatisticsService2.getStatistics(
                request.getHostId(),
                Pair.of(lowestEndpoint, queryRange.upperEndpoint()),
                null, Collections.emptySet(),
                Collections.singleton(queryGroupId), indicators, request.getDeviceType()
        );

        Map<QueryGroupId, List<GroupStat>> groupStats = new HashMap<>();
        Map<QueryIndicator, Accumulator> normAccumulators = GroupStatisticsService2.getAccumulators(indicators);
        GroupStatisticsService2.separateNormStats(groupStatsAll, normLowerEndpoint, normUpperEndpoint, queryRange.lowerEndpoint(), groupStats, normAccumulators);

        List<GroupStat> stats = groupStats.getOrDefault(queryGroupId, Collections.emptyList());
        AccumulatorMap userRangeAccumulator = AccumulatorMap.create(indicators, userRangeSet);
        AccumulatorMap intervalAccumulators = AccumulatorMap.create(indicators, intervalsRangeSet);

        stats.forEach(userRangeAccumulator::apply);
        stats.forEach(intervalAccumulators::apply);
        Map<QueryIndicator, Double> normFactors = GroupStatisticsService2.getNormFactors(normAccumulators);
        Map<QueryIndicator, Double> fallBackNormFactors = intervalAccumulators.findFirstNotZeroValue(indicators, 1);

        Map<DateRange, RivalsStatistic> statisticsByPeriod = new LinkedHashMap<>();
        for (QueryIndicator indicator : indicators) {
            // нормализуем
            Double normFactor = normFactors.get(indicator);
            if (normFactor == null) {
                normFactor = fallBackNormFactors.get(indicator);
            }

            List<Pair<Range<LocalDate>, Double>> intervalStat = intervalAccumulators
                    .getIndicatorNormalized(indicator, normFactor);
            DoubleSummaryStatistics summaryStatistics = new DoubleSummaryStatistics();
            // последнее значение
            MapWithDiff.map(intervalStat.iterator(), (r, v, d) -> {
                        if (v != null) {
                            summaryStatistics.accept(v);
                        }
                        RivalsIndicator ri = new RivalsIndicator(indicator, v, d);
                        statisticsByPeriod.computeIfAbsent(new DateRange(r, false), k -> new RivalsStatistic(
                                viewerQueryGroup, k, new ArrayList<>())).getIndicators().add(ri);
                    }
            );

            // общий интервал
            List<Pair<Range<LocalDate>, Double>> totalStats = userRangeAccumulator.getIndicator(indicator);
            Double prevTotalValue = totalStats.get(0).getValue();
            Double currTotalValue = totalStats.get(1).getValue();
            Range<LocalDate> currTotalRange = totalStats.get(1).getKey();
            Double diffTotal = prevTotalValue == null || currTotalValue == null ? null :
                    (currTotalValue - prevTotalValue) / prevTotalValue;
            Double avgTotalValue = summaryStatistics.getCount() > 0 ? summaryStatistics.getAverage() : null;
            if (currTotalValue != null && normFactor != null) {
                avgTotalValue = summaryStatistics.getAverage();
            }
            RivalsIndicator ri = new RivalsIndicator(indicator, avgTotalValue, diffTotal);
            statisticsByPeriod.computeIfAbsent(new DateRange(currTotalRange, true), k -> new RivalsStatistic(
                    viewerQueryGroup, k, new ArrayList<>())).getIndicators().add(ri);
        }
        // не забываем про реверс
        List<RivalsStatistic> statistics = new ArrayList<>(statisticsByPeriod.values());
        Collections.reverse(statistics);
        statistics = statistics.stream().skip(request.getP() * request.getPSize())
                .limit(request.getPSize()).collect(Collectors.toList());
        return new GetRivalsStatisticsResponse.NormalResponse(rangesCount, statistics);
    }
}
