package ru.yandex.webmaster3.storage.download.searchquery;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.base.Stopwatch;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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 org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
import ru.yandex.webmaster3.core.download.FileFormat;
import ru.yandex.webmaster3.core.download.searchquery.AbstractWriter;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.searchquery.IndicatorUsage;
import ru.yandex.webmaster3.core.searchquery.OrderDirection;
import ru.yandex.webmaster3.core.searchquery.QueryGroup;
import ru.yandex.webmaster3.core.searchquery.QueryGroupId;
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.QueryGroupConverter;
import ru.yandex.webmaster3.core.searchquery.viewer.ViewerQueryGroup;
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.DeviceType;
import ru.yandex.webmaster3.storage.searchquery.GroupStat;
import ru.yandex.webmaster3.storage.searchquery.GroupStatisticsService2;
import ru.yandex.webmaster3.storage.searchquery.MapWithDiff;
import ru.yandex.webmaster3.storage.searchquery.QueryGroupService;
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.storage.searchquery.util.Accumulator;

/**
 * Created by ifilippov5 on 05.03.17.
 */
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class QueriesExportService {
    protected final QueryGroupService queryGroupService;
    protected final GroupStatisticsService2 groupStatisticsService2;
    protected final QueryStatisticsService2 queryStatisticsService2;

    static final List<QueryIndicator> DEFAULT_INDICATORS = Arrays.stream(QueryIndicator.values())
            .filter(qi -> qi.isUsable(IndicatorUsage.IN_STATISTICS))
            .collect(Collectors.toList());

    public byte[] getAllIndicatorsForGroup(WebmasterHostId hostId, LocalDate userRangeStart, LocalDate userRangeEnd,
                                           Integer[] regionId, RegionInclusion regionInclusion, String groupId,
                                           DeviceType deviceType, AggregatePeriod period, FileFormat exportFormat) {
        List<QueryIndicator> indicators = DEFAULT_INDICATORS;

        final Map<QueryGroupId, ViewerQueryGroup> groups = new HashMap<>();
        Optional<QueryGroupId> groupIdO = QueryGroupId.byGroupIdStr(hostId, groupId);
        if (!groupIdO.isPresent()) {
            throw new WebmasterException("Group not set",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "groupId", null));
        }
        if (groupIdO.get().isSpecial()) {
            groups.put(groupIdO.get(), QueryGroupConverter.toViewerGroup(groupIdO.get().getSpecialGroup()));
        } else {
            QueryGroup group = queryGroupService.getGroup(groupIdO.get());
            if (group != null) {
                groups.put(groupIdO.get(), QueryGroupConverter.toViewerGroup(group));
            }
        }
        if (groups.isEmpty()) {
            throw new WebmasterException("Group not set",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "groupId", null));
        }

        final Set<Integer> regionIds = StatisticsHelper.asSet(10, regionId);

        RangeSet<LocalDate> userRangeSet = RangeFactory.singleRange(userRangeStart, userRangeEnd);
        RangeSet<LocalDate> intervalRangeSet = RangeFactory.createRanges(userRangeStart, userRangeEnd, period, false, false);

        Range<LocalDate> queryRange = userRangeSet.span().span(userRangeSet.span());
        Map<QueryGroupId, List<GroupStat>> groupStats = groupStatisticsService2.getStatistics(
                hostId, Pair.of(queryRange.lowerEndpoint(), queryRange.upperEndpoint()), regionInclusion, regionIds,
                groups.keySet(), indicators, deviceType
        );

        List<Object> names = new ArrayList<>();
        names.add("Indicator");
        names.add(rangeToText(userRangeSet.span()));
        for (Range<LocalDate> range : intervalRangeSet.asDescendingSetOfRanges()) {
            names.add(rangeToText(range));
        }

        AbstractWriter writer = exportFormat.getWriter();
        writer.write(names);

        AccumulatorMap userRangeMap = AccumulatorMap.create(indicators, userRangeSet);
        AccumulatorMap periodMap = AccumulatorMap.create(indicators, intervalRangeSet);
        for (List<GroupStat> listGroupStats : groupStats.values())
            listGroupStats.forEach(ds -> {
                userRangeMap.apply(ds);
                periodMap.apply(ds);
            });

        for (QueryIndicator indicator : indicators) {
            List<Object> line = new ArrayList<>();
            line.add(translateIndicator(indicator));
            {
                List<Pair<Range<LocalDate>, Double>> userIndicatorStat = userRangeMap.getIndicator(indicator);
                line.add(userIndicatorStat.get(0).getValue());
            }

            {
                List<Pair<Range<LocalDate>, Double>> periodIndicatorStat = periodMap.getIndicator(indicator);
                Collections.reverse(periodIndicatorStat);
                for (Pair<Range<LocalDate>, Double> pair : periodIndicatorStat) {
                    line.add(indicator.format(pair.getValue()));
                }
            }
            writer.write(line);
        }

        try {
            writer.close();
        } catch (IOException e) {
            log.error("Error with close writer", e);
        }
        return writer.getBytes();
    }

    public byte[] getAllGroupsAllIndicatorsStatistics(
            WebmasterHostId hostId, LocalDate localDateFrom, LocalDate localDateTo, Integer[] regionId,
            RegionInclusion regionInclusion, DeviceType deviceType, QueryIndicator orderBy,
            OrderDirection orderDirection, FileFormat exportFormat) {

        RangeSet<LocalDate> rangeSet = RangeFactory.doubleRange(localDateFrom, localDateTo);
        final Set<Integer> regionIds = StatisticsHelper.asSet(10, regionId);

        Map<QueryGroupId, ViewerQueryGroup> groups = new HashMap<>();
        groups.put(new QueryGroupId(hostId, SpecialGroup.ALL_QUERIES), QueryGroupConverter.toViewerGroup(SpecialGroup.ALL_QUERIES));
        groups.put(new QueryGroupId(hostId, SpecialGroup.SELECTED_QUERIES), QueryGroupConverter.toViewerGroup(SpecialGroup.SELECTED_QUERIES));
        List<QueryGroup> queryGroups = queryGroupService.listGroups(hostId);
        for (QueryGroup group : queryGroups) {
            groups.put(group.getQueryGroupId(), QueryGroupConverter.toViewerGroup(group));
        }

        List<QueryIndicator> indicators = DEFAULT_INDICATORS;

        Map<QueryGroupId, List<GroupStat>> stats = groupStatisticsService2.getStatistics(hostId,
                Pair.of(rangeSet.span().lowerEndpoint(), rangeSet.span().upperEndpoint()),
                regionInclusion, regionIds,
                groups.keySet(), indicators, deviceType);

        List<Object> names = new ArrayList<>();
        names.add("Group name");
        names.add("Dates range");
        names.add("Special group");
        for (QueryIndicator indicator : indicators) {
            names.add(translateIndicator(indicator));
        }

        AbstractWriter writer = exportFormat.getWriter();
        log.debug("Write {} to export file", names);
        writer.write(names);

        List<QueryGroupStatistics> result = new ArrayList<>();
        for (Map.Entry<QueryGroupId, ViewerQueryGroup> entry : groups.entrySet()) {
            try {
                AccumulatorMap accumulatorMap = AccumulatorMap.create(indicators, rangeSet);
                List<GroupStat> groupStats = stats.getOrDefault(entry.getKey(), Collections.emptyList());
                groupStats.forEach(accumulatorMap::apply);
                List<QueryIndicatorStatistics> groupStatResult = new ArrayList<>();
                for (QueryIndicator indicator : indicators) {
                    List<Pair<Range<LocalDate>, Double>> indicatorStat = accumulatorMap.getIndicator(indicator);
                    List<RangeStatistics> rs = new ArrayList<>();
                    MapWithDiff.map(indicatorStat.iterator(), (r, current, diff) ->
                            rs.add(new RangeStatistics(r.lowerEndpoint(), r.upperEndpoint(), current, diff)
                            )
                    );
                    groupStatResult.add(new QueryIndicatorStatistics(indicator, rs));
                }
                QueryGroupStatistics gs =
                        new QueryGroupStatistics(entry.getValue(), groupStatResult);
                result.add(gs);
            } catch (Exception e) {
                log.error("Error", e);
            }
        }

        if (orderBy == null) {
            orderBy = QueryIndicator.NAME;
            orderDirection = OrderDirection.ASC;
        }

        Comparator<QueryGroupStatistics> comparator = GroupComparators.SPECIAL_GROUPS_FIRST;
        if (orderBy == QueryIndicator.NAME) {
            comparator = comparator.thenComparing(QueryGroupStatistics.BY_NAME);
        } else {
            Comparator<QueryGroupStatistics> ic =
                    comparator.thenComparing(GroupComparators.comparatorByIndicator(orderBy));
            ic = orderDirection == OrderDirection.ASC ? ic : ic.reversed();
            comparator = comparator.thenComparing(ic);
        }
        result.sort(comparator);

        for (QueryGroupStatistics groupStat : result) {
            List<Object> line = new ArrayList<>();
            line.add(Optional.of(groupStat).map(QueryGroupStatistics::getGroup).map(ViewerQueryGroup::getName).orElse(""));
            line.add(rangeToText(RangeFactory.singleRange(localDateFrom, localDateTo).span()));
            line.add(Optional.of(groupStat).map(QueryGroupStatistics::getGroup).map(ViewerQueryGroup::getSpecialGroup).map(SpecialGroup::toString).orElse(""));
            for (QueryIndicatorStatistics indicator : groupStat.getIndicators()) {
                line.add(indicator.getName().format(indicator.getRanges().get(0).getValue()));
            }
            log.debug("Write {} to export file", line);
            writer.write(line);
        }
        try {
            writer.close();
        } catch (IOException e) {
            log.error("Error with close writer", e);
        }
        return writer.getBytes();
    }

    public byte[] getAllGroupsIndicatorStatistics(WebmasterHostId hostId, LocalDate localDateFrom, LocalDate localDateTo,
                                                  Integer[] regionId, RegionInclusion regionInclusion,
                                                  QueryIndicator queryIndicator, DeviceType deviceType,
                                                  AggregatePeriod periodParam, FileFormat exportFormat) {
        final LocalDate userRangeStart = localDateFrom;
        final LocalDate userRangeEnd = localDateTo;
        final List<QueryIndicator> indicators;
        AggregatePeriod period = periodParam;
        if (period == null) {
            period = AggregatePeriod.DAY;
        }
        final Map<QueryGroupId, ViewerQueryGroup> groups = new HashMap<>();
        final Set<Integer> regionIds = StatisticsHelper.asSet(10, regionId);

        RangeSet<LocalDate> userRangeSet = RangeFactory.doubleRange(userRangeStart, userRangeEnd);
        RangeSet<LocalDate> intervalsRangeSet = RangeFactory.createRanges(userRangeStart, userRangeEnd, period, false, false);

        indicators = StatisticsHelper.asList(1, queryIndicator);

        groups.put(
                new QueryGroupId(hostId, SpecialGroup.ALL_QUERIES),
                QueryGroupConverter.toViewerGroup(SpecialGroup.ALL_QUERIES)
        );
        groups.put(
                new QueryGroupId(hostId, SpecialGroup.SELECTED_QUERIES),
                QueryGroupConverter.toViewerGroup(SpecialGroup.SELECTED_QUERIES)
        );
        List<QueryGroup> queryGroups = queryGroupService.listGroups(hostId);
        for (QueryGroup group : queryGroups) {
            groups.put(group.getQueryGroupId(), QueryGroupConverter.toViewerGroup(group));
        }

        Range<LocalDate> queryRange = userRangeSet.span().span(intervalsRangeSet.span());

        Map<QueryGroupId, List<GroupStat>> groupStats = groupStatisticsService2.getStatistics(
                hostId,
                Pair.of(queryRange.lowerEndpoint(), queryRange.upperEndpoint()),
                regionInclusion, regionIds,
                groups.keySet(), indicators, deviceType
        );

        List<QueryGroupStatistics> result = new ArrayList<>();
        for (Map.Entry<QueryGroupId, ViewerQueryGroup> entry : groups.entrySet()) {
            try {
                List<QueryGroupStatistics> result11 = new ArrayList<>();
                List<GroupStat> stat = groupStats.getOrDefault(entry.getKey(), Collections.emptyList());
                AccumulatorMap userRangeAccumulator = AccumulatorMap.create(indicators, userRangeSet);
                AccumulatorMap intervalAccumulators = AccumulatorMap.create(indicators, intervalsRangeSet);

                stat.forEach(userRangeAccumulator::apply);
                stat.forEach(intervalAccumulators::apply);

                for (QueryIndicator indicator : indicators) {
                    List<RangeStatistics> ranges = new ArrayList<>();

                    List<Pair<Range<LocalDate>, Double>> intervalStat = intervalAccumulators.getIndicator(indicator);
                    MapWithDiff.map(intervalStat.iterator(), true, (r, v, d) -> {
                        ranges.add(new RangeStatistics(r.lowerEndpoint(), r.upperEndpoint(), v, d));
                    });

                    List<Pair<Range<LocalDate>, Double>> userRangeStat = userRangeAccumulator.getIndicator(indicator);
                    MapWithDiff.map(userRangeStat.iterator(), (r, v, d) -> {
                        ranges.add(new RangeStatistics(r.lowerEndpoint(), r.upperEndpoint(), v, d));
                    });
                    Collections.reverse(ranges);
                    result11.add(new QueryGroupStatistics(entry.getValue(),
                            new QueryIndicatorStatistics(indicator, ranges)));
                }
                result.addAll(result11);
            } catch (Exception e) {
                log.error("Error", e);
            }
        }

        List<QueryStatisticsDateRange> ranges = intervalsRangeSet.asRanges().stream()
                .skip(1)
                .map(r -> new QueryStatisticsDateRange(r.lowerEndpoint(), r.upperEndpoint(), false))
                .collect(Collectors.toList());
        ranges.add(new QueryStatisticsDateRange(userRangeStart, userRangeEnd, true));
        Collections.reverse(ranges);
        result.sort(GroupComparators.SPECIAL_GROUPS_FIRST.thenComparing(GroupComparators.comparatorByIndicator().reversed()));

        List<Object> names = new ArrayList<>();
        names.add("Group name");
        names.add("Dates range");
        names.add("Special group");
        names.add("Total");
        for (Range<LocalDate> range : intervalsRangeSet.asDescendingSetOfRanges()) {
            names.add(rangeToText(range));
        }

        AbstractWriter writer = exportFormat.getWriter();
        writer.write(names);

        for (QueryGroupStatistics groupStat : result) {
            List<Object> line = new ArrayList<>();
            line.add(Optional.of(groupStat).map(QueryGroupStatistics::getGroup).map(ViewerQueryGroup::getName).orElse(""));
            line.add(rangeToText(RangeFactory.singleRange(localDateFrom, localDateTo).span()));
            line.add(Optional.of(groupStat).map(QueryGroupStatistics::getGroup).map(ViewerQueryGroup::getSpecialGroup).map(SpecialGroup::toString).orElse(""));
            QueryIndicator indicator = groupStat.getIndicator().getName();
            groupStat.getIndicator().getRanges().forEach(range -> line.add(indicator.format(range.getValue())));
            writer.write(line);
        }

        try {
            writer.close();
        } catch (IOException e) {
            log.error("Error with close writer", e);
        }
        return writer.getBytes();
    }

    public byte[] getAllQueriesAllIndicatorsStatistics(
            WebmasterHostId hostId, LocalDate userRangeStart, LocalDate userRangeEnd, Integer[] regionId,
            RegionInclusion regionInclusion, SpecialGroup specialGroup, DeviceType deviceType, QueryIndicator orderBy,
            OrderDirection orderDirection, FileFormat exportFormat) {
        if (orderBy == null) {
            orderBy = QueryIndicator.TOTAL_SHOWS_COUNT;
        }
        if (orderDirection == null) {
            orderDirection = OrderDirection.DESC;
        }

        List<QueryIndicator> indicators = DEFAULT_INDICATORS;
        final Set<Integer> regionIds = StatisticsHelper.asSet(10, regionId);

        Stopwatch sw = Stopwatch.createStarted();
        List<QueryId> queryIds =
                queryStatisticsService2.getQueryIds(hostId, specialGroup,
                        regionIds, regionInclusion,
                        userRangeStart, userRangeEnd,
                        Collections.emptyList(), deviceType,
                        orderBy, orderDirection, 0, 3000
                );
        sw.stop();
        log.info("Get queries: {}", sw.elapsed(TimeUnit.MILLISECONDS));

        RangeSet<LocalDate> rangeSet = RangeFactory.singleRange(userRangeStart, userRangeEnd);

        sw.start();
        Pair<Map<QueryId, String>, List<QueryStat>> statistics = queryStatisticsService2.getQueryStatistics(
                hostId, specialGroup, indicators,
                regionInclusion, regionIds,
                queryIds,
                rangeSet.span().lowerEndpoint(), rangeSet.span().upperEndpoint(), deviceType);
        sw.stop();
        log.info("Get data: {}", sw.elapsed(TimeUnit.MILLISECONDS));

        List<Object> names = new ArrayList<>();
        names.add("Query");
        names.add("Dates range");
        for (QueryIndicator indicator : indicators) {
            names.add(translateIndicator(indicator));
        }

        AbstractWriter writer = exportFormat.getWriter();
        log.debug("Write {} to export file", names);
        writer.write(names);

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

        for (QueryId id : queryIds) {
            String queryText = statistics.getKey().get(id);
            if (!StringUtils.isEmpty(queryText)) {
                List<Object> line = new ArrayList<>();
                line.add(queryText);
                line.add(rangeToText(rangeSet.span()));

                AccumulatorMap accumulatorMap = accumulators
                        .computeIfAbsent(id, q -> AccumulatorMap.create(indicators, rangeSet));

                for (QueryIndicator indicator : indicators) {
                    List<Pair<Range<LocalDate>, Double>> indicatorStat = accumulatorMap.getIndicator(indicator);
                    Pair<Range<LocalDate>, Double> pair = indicatorStat.get(0);
                    line.add(indicator.format(pair.getValue()));
                }
                writer.write(line);
            }
        }
        sw.stop();
        log.info("Create : {}", sw.elapsed(TimeUnit.MILLISECONDS));
        try {
            writer.close();
        } catch (IOException e) {
            log.error("Error with close writer", e);
        }
        return writer.getBytes();
    }

    public byte[] getAllQueriesIndicatorStatistics(
            WebmasterHostId hostId, LocalDate userRangeStart, LocalDate userRangeEnd, Integer[] regionId,
            RegionInclusion regionInclusion, SpecialGroup specialGroup, QueryIndicator queryIndicator, DeviceType deviceType,
            QueryIndicator orderBy, OrderDirection orderDirection, AggregatePeriod period, FileFormat exportFormat) {

        if (orderBy == null) {
            orderBy = QueryIndicator.TOTAL_SHOWS_COUNT;
        }
        if (orderDirection == null) {
            orderDirection = OrderDirection.DESC;
        }
        if (period == null) {
            period = AggregatePeriod.DAY;
        }

        final Set<Integer> regionIds = StatisticsHelper.asSet(10, regionId);
        final List<QueryIndicator> indicators = StatisticsHelper.asList(1, queryIndicator);

        List<QueryId> queryIds =
                queryStatisticsService2.getQueryIds(hostId, specialGroup,
                        regionIds, regionInclusion,
                        userRangeStart, userRangeEnd,
                        Collections.emptyList(), deviceType,
                        orderBy, orderDirection, 0, 3000
                );

        Map<QueryId, String> idToQueryText = queryStatisticsService2.getQueryTexts(hostId, specialGroup, queryIds);

        RangeSet<LocalDate> userRangeSet = RangeFactory.singleRange(userRangeStart, userRangeEnd);
        RangeSet<LocalDate> intervalRangeSet = RangeFactory.createRanges(userRangeStart, userRangeEnd, period, false, false);

        Range<LocalDate> queryRange = userRangeSet.span().span(intervalRangeSet.span());

        List<QueryStat> wholeStat = queryStatisticsService2.getStatistics(specialGroup, hostId,
                indicators, queryIds,
                regionIds, regionInclusion,
                queryRange.lowerEndpoint(), queryRange.upperEndpoint(), deviceType);

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

            ac.apply(queryStat);
        }

        List<Object> names = new ArrayList<>();
        names.add("Query");

        for (Range<LocalDate> range : intervalRangeSet.asDescendingSetOfRanges()) {
            names.add(rangeToText(range));
        }

        AbstractWriter writer = exportFormat.getWriter();
        writer.write(names);

        for (QueryId id : queryIds) {
            String queryText = idToQueryText.get(id);
            if (!StringUtils.isEmpty(queryText)) {
                List<Object> line = new ArrayList<>();
                AccumulatorMap ac = accumulators.computeIfAbsent(id, accumulatorFactory);
                line.add(queryText);
                for (QueryIndicator indicator : indicators) {
                    List<Pair<Range<LocalDate>, Double>> indicatorStat = ac.getIndicator(indicator);
                    Collections.reverse(indicatorStat);
                    for (Pair<Range<LocalDate>, Double> pair : indicatorStat) {
                        line.add(indicator.format(pair.getValue()));
                    }
                }
                writer.write(line);
            }
        }

        try {
            writer.close();
        } catch (IOException e) {
            log.error("Error with close writer", e);
        }
        return writer.getBytes();
    }

    public byte[] getAllIndicatorsForQuery(
            WebmasterHostId hostId, LocalDate userRangeStart, LocalDate userRangeEnd, Integer[] regionId,
            RegionInclusion regionInclusion, SpecialGroup specialGroup, QueryId queryId, DeviceType deviceType,
            AggregatePeriod periodParam, FileFormat exportFormat) {
        List<QueryIndicator> indicators = DEFAULT_INDICATORS;
        AggregatePeriod period = periodParam;
        if (period == null) {
            period = AggregatePeriod.DAY;
        }

        final Set<Integer> regionIds = StatisticsHelper.asSet(10, regionId);

        RangeSet<LocalDate> userRangeSet = RangeFactory.singleRange(userRangeStart, userRangeEnd);
        RangeSet<LocalDate> intervalRangeSet = RangeFactory.createRanges(userRangeStart, userRangeEnd, period, false, false);

        List<QueryId> queryIds = Collections.singletonList(queryId);

        Range<LocalDate> queryRange = userRangeSet.span().span(userRangeSet.span());
        List<QueryStat> dayStat = queryStatisticsService2
                .getStatistics(specialGroup, hostId,
                        indicators, queryIds,
                        regionIds, regionInclusion,
                        queryRange.lowerEndpoint(), queryRange.upperEndpoint(), deviceType);

        List<Object> names = new ArrayList<>();
        names.add("Indicator");
        names.add(rangeToText(userRangeSet.span()));
        for (Range<LocalDate> range : intervalRangeSet.asDescendingSetOfRanges()) {
            names.add(rangeToText(range));
        }

        AbstractWriter writer = exportFormat.getWriter();
        writer.write(names);

        AccumulatorMap userRangeMap = AccumulatorMap.create(indicators, userRangeSet);
        AccumulatorMap periodMap = AccumulatorMap.create(indicators, intervalRangeSet);
        dayStat.forEach(ds -> {
            userRangeMap.apply(ds);
            periodMap.apply(ds);
        });

        for (QueryIndicator indicator : indicators) {
            List<Object> line = new ArrayList<>();
            line.add(translateIndicator(indicator));
            {
                List<Pair<Range<LocalDate>, Double>> userIndicatorStat = userRangeMap.getIndicator(indicator);
                line.add(userIndicatorStat.get(0).getValue());
            }

            {
                List<Pair<Range<LocalDate>, Double>> periodIndicatorStat = periodMap.getIndicator(indicator);
                Collections.reverse(periodIndicatorStat);
                for (Pair<Range<LocalDate>, Double> pair : periodIndicatorStat) {
                    line.add(indicator.format(pair.getValue()));
                }
            }
            writer.write(line);
        }

        try {
            writer.close();
        } catch (IOException e) {
            log.error("Error with close writer", e);
        }
        return writer.getBytes();
    }

    public byte[] getRivalsStatistics(WebmasterHostId hostId, LocalDate userRangeStart, LocalDate userRangeEnd,
                                      DeviceType deviceType, AggregatePeriod period, FileFormat exportFormat,
                                      String thematics) {
        List<QueryIndicator> indicators = Arrays.asList(QueryIndicator.WEIGHTED_CLICKS, QueryIndicator.WEIGHTED_RIVALS_CLICKS);
        boolean firstRangeIsFull = RangeFactory.getPeriodStart(userRangeStart, period).equals(userRangeStart);

        QueryGroupId queryGroupId = QueryGroupId.createThematicsId(hostId, thematics);
        RangeSet<LocalDate> intervalRangeSet = RangeFactory.createRanges(userRangeStart, userRangeEnd, period, false, false);

        LocalDate normUpperEndpoint = userRangeStart;
        LocalDate normLowerEndpoint = normUpperEndpoint.minusMonths(1);

        Map<QueryGroupId, List<GroupStat>> groupStatsAll = groupStatisticsService2.getStatistics(
                hostId, Pair.of(normLowerEndpoint, userRangeEnd), null, Collections.emptySet(),
                Collections.singleton(queryGroupId), indicators, deviceType
        );

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

        List<Object> names = new ArrayList<>();
        names.add("Dates range");
        for (QueryIndicator indicator : indicators) {
            names.add(translateIndicator(indicator));
        }

        AbstractWriter writer = exportFormat.getWriter();
        log.debug("Write {} to export file", names);
        writer.write(names);

        List<GroupStat> stats = groupStats.getOrDefault(queryGroupId, Collections.emptyList());
        AccumulatorMap intervalAccumulators = AccumulatorMap.create(indicators, intervalRangeSet);
        stats.forEach(intervalAccumulators::apply);
        Map<QueryIndicator, Double> fallbackNormFactors = intervalAccumulators.findFirstNotZeroValue(indicators, firstRangeIsFull ? 0 : 1);

        LinkedHashMap<Range<LocalDate>, EnumMap<QueryIndicator, Double>> 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);
            intervalStat.forEach(pair ->
                    statisticsByPeriod.computeIfAbsent(pair.getLeft(), k -> new EnumMap<>(QueryIndicator.class))
                        .put(indicator, indicator.format(pair.getRight()))
            );
        }
        // реверсируем и пишем в файл
        List<Map.Entry<Range<LocalDate>, EnumMap<QueryIndicator, Double>>> statistics =
                new ArrayList<>(statisticsByPeriod.entrySet());
        for (int i = statistics.size() - 1; i >= 0; i--) {
            List<Object> line = new ArrayList<>();
            line.add(rangeToText(statistics.get(i).getKey()));
            line.addAll(statistics.get(i).getValue().values());
            writer.write(line);
        }

        try {
            writer.close();
        } catch (IOException e) {
            log.error("Error with close writer", e);
        }
        return writer.getBytes();
    }

    protected static String translateIndicator(QueryIndicator indicator) {
        switch (indicator) {
//            "GROUP-AVERAGE-CLICK-POSITION": "Avg. click position in top results",
//                    "GROUP-AVERAGE-SHOW-POSITION": "Average position in top results",
//                    "GROUP-CLICKS-COUNT": "Clicks from positions",
//                    "GROUP-CTR": "CTR % for positions",
//                    "GROUP-DEFAULT": "General indicators",
//                    "GROUP-SHOWS-COUNT": "Impressions for positions",
            case AVERAGE_CLICK_POSITION:
                return "Avg. click position";
            case AVERAGE_CLICK_POSITION_11_50:
                return "Avg. click position from pos. 11-50";
            case AVERAGE_CLICK_POSITION_2_3:
                return "Avg. click position from pos. 2-3";
            case AVERAGE_CLICK_POSITION_4_10:
                return "Avg. click position from pos. 4-10";
            case AVERAGE_SHOW_POSITION:
                return "Avg. position";
            case AVERAGE_SHOW_POSITION_11_50:
                return "Avg. impression position for pos. 11-50";
            case AVERAGE_SHOW_POSITION_2_3:
                return "Avg. impression position for pos. 2-3";
            case AVERAGE_SHOW_POSITION_4_10:
                return "Avg. impression position for pos. 4-10";
            case CLICKS_COUNT_1:
                return "Clicks from pos. 1";
            case CLICKS_COUNT_11_50:
                return "Clicks from pos. 11-50";
            case CLICKS_COUNT_2_3:
                return "Clicks from pos. 2-3";
            case CLICKS_COUNT_4_10:
                return "Clicks from pos. 4-10";
            case CTR_1:
                return "CTR % for pos. 1";
            case CTR_11_50:
                return "CTR % for pos. 11-50";
            case CTR_2_3:
                return "CTR % for pos. 2-3";
            case CTR_4_10:
                return "CTR % for pos. 4-10";
            case SHOWS_COUNT_1:
                return "Impressions for 1st position";
            case SHOWS_COUNT_11_50:
                return "Impressions for pos. 11-50";
            case SHOWS_COUNT_2_3:
                return "Impressions for pos. 2-3";
            case SHOWS_COUNT_4_10:
                return "Impressions for pos. 4-10";
            case TOTAL_CLICKS_COUNT:
                return "Clicks";
            case TOTAL_CTR:
                return "CTR %";
            case TOTAL_SERPS_COUNT:
                return "SERP impressions";
            case TOTAL_SHOWS_COUNT:
                return "Impressions";
            case WEIGHTED_CLICKS:
                return "My site";
            case WEIGHTED_RIVALS_CLICKS:
                return "Similar sites";
        }
        return indicator.name();
    }

    protected static String rangeToText(Range<LocalDate> range) {
        LocalDate lowerEndpoint = range.lowerEndpoint();
        LocalDate upperEndpoint = range.upperEndpoint();
        if (lowerEndpoint.equals(upperEndpoint)) {
            return lowerEndpoint.toString("yyyy-MM-dd");
        } else {
            return lowerEndpoint.toString("yyyy-MM-dd") + " - " + upperEndpoint.toString("yyyy-MM-dd");
        }
    }
}

