package ru.yandex.webmaster3.api.queries2.action;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;

import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.api.http.auth.ActionPermission;
import ru.yandex.webmaster3.api.http.auth.Permission;
import ru.yandex.webmaster3.api.http.rest.AbstractApiAction;
import ru.yandex.webmaster3.api.queries.data.ApiQueryIndicator;
import ru.yandex.webmaster3.api.queries.data.ApiQueryStats;
import ru.yandex.webmaster3.core.WebmasterException;
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.QueryId;
import ru.yandex.webmaster3.core.searchquery.QueryIndicator;
import ru.yandex.webmaster3.core.searchquery.SpecialGroup;
import ru.yandex.webmaster3.core.util.TimeUtils;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.searchquery.QueryStat;
import ru.yandex.webmaster3.storage.searchquery.QueryStatisticsService2;
import ru.yandex.webmaster3.storage.searchquery.RegionInclusion;
import ru.yandex.webmaster3.storage.searchquery.SearchqueryDatesService;
import ru.yandex.webmaster3.storage.searchquery.util.Accumulator;
import ru.yandex.webmaster3.storage.searchquery.util.ExtractorFactory;

/**
 * @author leonidrom
 */
@Description("Получить список топ-3000 популярных поисковых запросов, по которым сайт показывался на поиске за последнюю неделю")
@Category("searchquery")
@Component
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@ActionPermission(Permission.COMMON)
public class ListSearchQueriesAction extends AbstractApiAction<ListSearchQueriesRequest, ListSearchQueriesResponse> {
    private static final int PERIOD_DAYS = 7;

    private final QueryStatisticsService2 queryStatisticsService2;
    private final SearchqueryDatesService searchqueryDatesService;

    @Override
    public ListSearchQueriesResponse process(ListSearchQueriesRequest request) {
        var apiIndicators = request.getQueryIndicators();
        var hostId = request.getHostId();
        var dateFrom = request.getDateFrom();
        var dateTo = request.getDateTo();
        var deviceType = request.getDeviceTypeIndicator().getCoreDeviceType();

        try {
            LocalDate minDate, maxDate;
            var minMaxDates = searchqueryDatesService.getDates();
            if (dateFrom == null || dateTo == null) {
                /*
                 * В версии API v4.0 пользователь не может управлять диапазоном дат,
                 * вместо этого диапазон выбирается вот таким вот образом.
                 */
                maxDate = minMaxDates.getRight();
                LocalDate desiredMinDate = maxDate.minusDays(PERIOD_DAYS - 1);
                minDate = TimeUtils.latestOf(desiredMinDate, minMaxDates.getLeft());
            } else {
                minDate = TimeUtils.latestOf(dateFrom.toLocalDate(), minMaxDates.getLeft());
                maxDate = TimeUtils.earliestOf(dateTo.toLocalDate(), minMaxDates.getRight());
            }

            int count = queryStatisticsService2.countQueries(
                    hostId,
                    SpecialGroup.TOP_3000_QUERIES,
                    Collections.emptySet(),
                    RegionInclusion.INCLUDE_ALL,
                    minDate,
                    maxDate,
                    deviceType
            );

            List<QueryId> queryIds = queryStatisticsService2.getQueryIds(
                    hostId,
                    SpecialGroup.TOP_3000_QUERIES,
                    Collections.emptySet(),
                    RegionInclusion.INCLUDE_ALL,
                    minDate,
                    maxDate,
                    Collections.emptyList(),
                    deviceType,
                    request.getOrderBy().getQueryIndicator(),
                    OrderDirection.DESC,
                    request.getOffset(),
                    request.getLimit()
            );

            List<QueryIndicator> requestedIndicators  = apiIndicators.stream()
                    .map(ApiQueryIndicator::getCoreQueryIndicator)
                    .collect(Collectors.toList());

            Pair<Map<QueryId, String>, List<QueryStat>> queryTextsAndStats =
                    queryStatisticsService2.getQueryStatistics(
                            hostId,
                            SpecialGroup.TOP_3000_QUERIES,
                            requestedIndicators,
                            RegionInclusion.INCLUDE_ALL,
                            Collections.emptySet(),
                            queryIds,
                            minDate,
                            maxDate,
                            deviceType
                    );

            List<ApiQueryStats> result = getApiQueryStats(apiIndicators, queryIds, queryTextsAndStats);
            return new ListSearchQueriesResponse(count, result, minDate, maxDate);
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to list search queries",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    @NotNull
    private List<ApiQueryStats> getApiQueryStats(Set<ApiQueryIndicator> apiIndicators, List<QueryId> queryIds,
                                                 Pair<Map<QueryId, String>, List<QueryStat>> queryTextsAndStats) {
        Map<QueryId, List<QueryStat>> query2Stat = queryTextsAndStats.getRight().stream()
                .collect(Collectors.groupingBy(QueryStat::getQueryId));

        List<ApiQueryStats> result = new ArrayList<>();
        for (QueryId queryId : queryIds) {
            Map<ApiQueryIndicator, Accumulator> indicatorAccs = new EnumMap<>(ApiQueryIndicator.class);
            for (ApiQueryIndicator queryIndicator : apiIndicators) {
                var extractor = ExtractorFactory.createExtractor(queryIndicator.getCoreQueryIndicator());
                indicatorAccs.put(queryIndicator, extractor);
            }

            if (query2Stat.containsKey(queryId)) {
                for (QueryStat stat : query2Stat.get(queryId)) {
                    for (Accumulator acc : indicatorAccs.values()) {
                        acc.apply(stat);
                    }
                }
            }

            Map<ApiQueryIndicator, Double> indicatorValues = new EnumMap<>(ApiQueryIndicator.class);
            for (Map.Entry<ApiQueryIndicator, Accumulator> accEntry : indicatorAccs.entrySet()) {
                Double value = accEntry.getValue().getValue();
                indicatorValues.put(accEntry.getKey(), value);
            }

            result.add(new ApiQueryStats(
                    queryId,
                    queryTextsAndStats.getLeft().get(queryId),
                    indicatorValues
            ));
        }

        return result;
    }

}
