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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.OrderDirection;
import ru.yandex.webmaster3.core.searchquery.QueryGroup;
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.http.searchquery.statistic.StatisticsHelper;
import ru.yandex.webmaster3.storage.searchquery.AccumulatorMap;
import ru.yandex.webmaster3.storage.searchquery.AggregatePeriod;
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.RangeFactory;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostAction;

/**
 * @author aherman
 */
@ReadAction
@Category("searchquery")
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class GetGroupStatisticsAction
        extends AbstractUserVerifiedHostAction<GetGroupStatisticsRequest, GroupsStatisticResponse> {
    private static final Logger log = LoggerFactory.getLogger(GetGroupStatisticsAction.class);

    private Mode mode;
    private final QueryGroupService queryGroupService;
    private final GroupStatisticsService2 groupStatisticsService2;

    static List<QueryIndicator> DEFAULT_INDICATORS = Lists.newArrayList(
            QueryIndicator.TOTAL_SHOWS_COUNT,
            QueryIndicator.TOTAL_CLICKS_COUNT,
            QueryIndicator.TOTAL_CTR,
            QueryIndicator.TOTAL_SERPS_COUNT,
            QueryIndicator.SHOWS_COUNT_1,
            QueryIndicator.SHOWS_COUNT_2_3,
            QueryIndicator.SHOWS_COUNT_4_10,
            QueryIndicator.SHOWS_COUNT_11_50,
            QueryIndicator.CLICKS_COUNT_1,
            QueryIndicator.CLICKS_COUNT_2_3,
            QueryIndicator.CLICKS_COUNT_4_10,
            QueryIndicator.CLICKS_COUNT_11_50,
            QueryIndicator.CTR_1,
            QueryIndicator.CTR_2_3,
            QueryIndicator.CTR_4_10,
            QueryIndicator.CTR_11_50,
            QueryIndicator.AVERAGE_CLICK_POSITION,
            QueryIndicator.AVERAGE_CLICK_POSITION_2_3,
            QueryIndicator.AVERAGE_CLICK_POSITION_4_10,
            QueryIndicator.AVERAGE_CLICK_POSITION_11_50,
            QueryIndicator.AVERAGE_SHOW_POSITION,
            QueryIndicator.AVERAGE_SHOW_POSITION_2_3,
            QueryIndicator.AVERAGE_SHOW_POSITION_4_10,
            QueryIndicator.AVERAGE_SHOW_POSITION_11_50
    );

    @Override
    public GroupsStatisticResponse process(GetGroupStatisticsRequest request) {
        final LocalDate userRangeStart = request.getLocalDateFrom();
        final LocalDate userRangeEnd = request.getLocalDateTo();
        final List<QueryIndicator> indicators;
        final AggregatePeriod period = request.getPeriod();
        final Map<QueryGroupId, ViewerQueryGroup> groups = new HashMap<>();
        final Set<Integer> regionIds = StatisticsHelper.asSet(10, request.getRegionId());

        RangeSet<LocalDate> userRangeSet = RangeFactory.doubleRange(userRangeStart, userRangeEnd);
        RangeSet<LocalDate> intervalsRangeSet = RangeFactory.createRanges(userRangeEnd, period, request.getMaxRanges() + 1, false);
        EnumSet<QueryIndicator> filterIndicators = EnumSet.noneOf(QueryIndicator.class);

        QueryIndicator orderBy = request.getOrderBy();
        if (mode == Mode.ALL_GROUPS__ONE_INDICATOR__TIME_RANGES) {
            indicators = StatisticsHelper.asList(1, request.getIndicator());
            if (indicators.size() != 1) {
                throw new WebmasterException("Indicators wrong number",
                        new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "indicators", null));
            }
            QueryIndicator indicator = indicators.get(0);
            if (orderBy == null) {
                orderBy = indicator;
            } else {
                QueryIndicator orderIndicator;
                orderIndicator = orderBy.isDynamics() ? orderBy.getDynamicsOf() : orderBy;
                if (orderIndicator != indicator) {
                    indicators.add(orderIndicator);
                    filterIndicators.add(orderIndicator);
                }
            }
            groups.put(
                    new QueryGroupId(request.getHostId(), SpecialGroup.ALL_QUERIES),
                    QueryGroupConverter.toViewerGroup(SpecialGroup.ALL_QUERIES)
            );
            groups.put(
                    new QueryGroupId(request.getHostId(), SpecialGroup.SELECTED_QUERIES),
                    QueryGroupConverter.toViewerGroup(SpecialGroup.SELECTED_QUERIES)
            );
            List<QueryGroup> queryGroups = queryGroupService.listGroups(request.getHostId());
            for (QueryGroup group : queryGroups) {
                groups.put(group.getQueryGroupId(), QueryGroupConverter.toViewerGroup(group));
            }

        } else {
            Optional<QueryGroupId> groupIdO = QueryGroupId.byGroupIdStr(request.getHostId(), request.getGroupId());
            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));
            }
            indicators = DEFAULT_INDICATORS;
        }

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

        Map<QueryGroupId, List<GroupStat>> groupStats = groupStatisticsService2.getStatistics(
                request.getHostId(),
                Pair.of(queryRange.lowerEndpoint(), queryRange.upperEndpoint()),
                request.getRegionInclusion(), regionIds,
                groups.keySet(), indicators, request.getDeviceType()
        );

        List<GroupsStatisticResponse.GroupStat> result = new ArrayList<>();
        for (Map.Entry<QueryGroupId, ViewerQueryGroup> entry : groups.entrySet()) {
            try {
                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);

                List<AbstractQueryStatisticsResponse.IndicatorStats> indicatorStats = new ArrayList<>();
                for (QueryIndicator indicator : indicators) {
                    List<GroupsStatisticResponse.RangeStat> ranges = new ArrayList<>();

                    List<Pair<Range<LocalDate>, Double>> intervalStat = intervalAccumulators.getIndicator(indicator);
                    MapWithDiff.map(intervalStat.iterator(), (r, v, d) -> {
                        ranges.add(new AbstractQueryStatisticsResponse.RangeStat(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 AbstractQueryStatisticsResponse.RangeStat(r.lowerEndpoint(), r.upperEndpoint(), v, d));
                    });
                    Collections.reverse(ranges);
                    indicatorStats.add(new GroupsStatisticResponse.IndicatorStats(indicator, ranges));
                }
                result.add(new GroupsStatisticResponse.GroupStat(entry.getValue(), indicatorStats));
            } catch (Exception e) {
                log.error("Error", e);
            }
        }

        List<GroupsStatisticResponse.DateRange> ranges = intervalsRangeSet.asRanges().stream()
                .skip(1)
                .map(r -> new GroupsStatisticResponse.DateRange(r.lowerEndpoint(), r.upperEndpoint(), false))
                .collect(Collectors.toList());
        ranges.add(new GroupsStatisticResponse.DateRange(userRangeStart, userRangeEnd, true));
        Collections.reverse(ranges);
        if (mode == Mode.ALL_GROUPS__ONE_INDICATOR__TIME_RANGES) {
            Comparator<GroupsStatisticResponse.GroupStat> indicatorComparator = GroupComparators.comparatorByIndicator(orderBy);
            if (request.getOrderDirection() == OrderDirection.DESC) {
                indicatorComparator = indicatorComparator.reversed();
            }
            if (request.isSpecialFirst()) {
                indicatorComparator = GroupComparators.SPECIAL_GROUP_FIRST.thenComparing(indicatorComparator);
            }
            result.sort(indicatorComparator);
        }
        result = result.stream()
                .flatMap(
                        groupStat -> groupStat.getIndicators().stream()
                                .filter(stat -> !filterIndicators.contains(stat.getName()))
                                .map(ind -> new GroupsStatisticResponse.GroupStat(groupStat.getGroup(), ind))
                ).collect(Collectors.toList());
        return new GroupsStatisticResponse.NormalResponse(ranges, result);
    }

    public enum Mode {
        ALL_GROUPS__ONE_INDICATOR__TIME_RANGES,
        SELECTED_GROUP__ALL_INDICATORS__TIME_RANGES,
    }


    public void setMode(Mode mode) {
        this.mode = mode;
    }
}
