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

import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.stereotype.Component;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.data.WebmasterHostId;
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.QueryGroupId;
import ru.yandex.webmaster3.core.searchquery.QueryIndicator;
import ru.yandex.webmaster3.core.searchquery.viewer.ViewerQueryGroup;
import ru.yandex.webmaster3.storage.http.searchquery.statistic.StatisticsHelper;
import ru.yandex.webmaster3.storage.searchquery.*;
import ru.yandex.webmaster3.viewer.http.AbstractUserVerifiedHostAction;
import ru.yandex.webmaster3.viewer.http.searchquery.statistic.AbstractQueryStatisticsResponse.RangeStat;

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

/**
 * @author aherman
 */
@ReadAction
@Category("searchquery")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component("/searchquery/statistics/group/history")
public class GetGroupHistoryAction extends AbstractUserVerifiedHostAction<GetGroupHistoryRequest, GroupsStatisticResponse> {

    // TODO костыль для нормировки только определенных индикаторов
    private static final EnumSet<QueryIndicator> INDICATORS_TO_NORMALIZE = EnumSet.of(
            QueryIndicator.WEIGHTED_CLICKS,
            QueryIndicator.WEIGHTED_RIVALS_CLICKS);

    protected final QueryGroupService queryGroupService;
    protected final GroupStatisticsService2 groupStatisticsService2;

    @Override
    public GroupsStatisticResponse process(GetGroupHistoryRequest request) {
        final LocalDate userRangeStart = request.getLocalDateFrom();
        final LocalDate userRangeEnd = request.getLocalDateTo();
        final List<QueryIndicator> indicators = StatisticsHelper.asList(7, request.getIndicator());
        final List<QueryGroupId> groupIds = StatisticsHelper.asGroupIds(7, request.getHostId(),
                request.getGroupId());

        final AggregatePeriod period = request.getPeriod();
        final Set<Integer> regionIds = StatisticsHelper.asSet(10, request.getRegionId());

        //noinspection Duplicates
        if (period == AggregatePeriod.WEEK) {
            if (userRangeStart.getYear() == userRangeEnd.getYear()
                    && userRangeStart.getWeekOfWeekyear() == userRangeEnd.getWeekOfWeekyear())
            {
                throw new WebmasterException("Same week",
                        new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "dateTo", null));
            }
        } else if (period == AggregatePeriod.MONTH) {
            if (userRangeStart.getYear() == userRangeEnd.getYear()
                    && userRangeStart.getMonthOfYear() == userRangeEnd.getMonthOfYear())
            {
                throw new WebmasterException("Same month",
                        new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "dateTo", null));
            }
        }

        List<GroupsStatisticResponse.GroupStat> result = getStatistics(request.getHostId(), groupIds, indicators,
                userRangeStart, userRangeEnd, period, request.getRegionInclusion(), regionIds, request.getDeviceType()
        );
        return new GroupsStatisticResponse.NormalResponse(null, result);
    }

    @NotNull
    protected List<GroupsStatisticResponse.GroupStat> getStatistics(WebmasterHostId hostId,
                                                                    List<QueryGroupId> groupIds, List<QueryIndicator> indicators,
                                                                    LocalDate userRangeStart, LocalDate userRangeEnd, AggregatePeriod period, RegionInclusion regionInclusion,
                                                                    Set<Integer> regionIds, DeviceType deviceType)
    {
        Map<QueryGroupId, ViewerQueryGroup> groups = resolveQueryGroups(groupIds, queryGroupService);

        RangeSet<LocalDate> ranges = RangeFactory.createRanges(userRangeStart, userRangeEnd, period, false, false);
        boolean firstRangeIsFull = RangeFactory.getPeriodStart(userRangeStart, period).equals(userRangeStart);

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

        List<GroupsStatisticResponse.GroupStat> result = new ArrayList<>();

        for (QueryGroupId groupId : groupIds) {
            ViewerQueryGroup viewerQueryGroup = groups.get(groupId);
            if (viewerQueryGroup == null) {
                continue;
            }
            List<GroupStat> s = stats.getOrDefault(groupId, Collections.emptyList());

            AccumulatorMap accumulatorMap = AccumulatorMap.create(indicators, ranges);
            s.forEach(accumulatorMap::apply);
            Map<QueryIndicator, Double> normFactors = accumulatorMap.findFirstNotZeroValue(
                    CollectionUtils.retainAll(INDICATORS_TO_NORMALIZE, indicators), firstRangeIsFull ? 0 : 1);

            for (QueryIndicator indicator : indicators) {
                List<Pair<Range<LocalDate>, Double>> indicatorStat = accumulatorMap
                        .getIndicatorNormalized(indicator, normFactors.get(indicator));
                List<RangeStat> rs = indicatorStat.stream().map(e -> createRangeStat(e.getKey(), e.getValue()))
                        .collect(Collectors.toList());
                result.add(new GroupsStatisticResponse.GroupStat(viewerQueryGroup,
                        new GroupsStatisticResponse.IndicatorStats(indicator, rs)));
            }
        }
        return result;
    }

    protected RangeStat createRangeStat(Range<LocalDate> range, Double value) {
        return new RangeStat(range.lowerEndpoint(), range.upperEndpoint(), value, null);
    }
}
