package ru.yandex.webmaster3.storage.searchquery;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
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.Service;

import ru.yandex.webmaster3.core.data.WebmasterHostId;
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.storage.searchquery.dao.GroupStatistics2CHDao;
import ru.yandex.webmaster3.storage.searchquery.rivals.RivalsStats2CHDao;
import ru.yandex.webmaster3.storage.searchquery.util.Accumulator;
import ru.yandex.webmaster3.storage.searchquery.util.ExtractorFactory;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseException;

/**
 * @author aherman
 */
@Service("groupStatisticsService2")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class GroupStatisticsService2 {

    private final GroupStatistics2CHDao mdbGroupStatistics2CHDao;
    private final RivalsStats2CHDao mdbRivalsStats2CHDao;

    @NotNull
    public Map<QueryGroupId, List<GroupStat>> getStatistics(WebmasterHostId hostId,
                                                            Pair<LocalDate, LocalDate> dateRange,
                                                            RegionInclusion regionInclusion, Set<Integer> regionIds,
                                                            Collection<QueryGroupId> groupIds, List<QueryIndicator> indicators,
                                                            DeviceType deviceType) {
        Map<QueryGroupId, List<GroupStat>> result = new HashMap<>();
        // не все запросы хранятся в group_statistics, есть особая группа RIVALS_STATS
        QueryGroupId rivalsGroupId = groupIds.stream()
                .filter(groupId -> groupId.getSpecialGroup() == SpecialGroup.RIVALS_STATS).findAny().orElse(null);
        if (rivalsGroupId != null) {
            result.put(rivalsGroupId, getRivalsStats(rivalsGroupId, dateRange, deviceType));
        }
        List<GroupStat> queryStat = mdbGroupStatistics2CHDao.getQueryStat(hostId, indicators,
                        dateRange.getLeft(), dateRange.getRight(),
                        regionInclusion, regionIds,
                        groupIds, deviceType);

        for (GroupStat groupStat : queryStat) {
            QueryGroupId id = groupStat.getGroupId();
            result.computeIfAbsent(id, i -> new ArrayList<>()).add(groupStat);
        }
        return result;
    }

    private List<GroupStat> getRivalsStats(QueryGroupId rivalsGroupId, Pair<LocalDate, LocalDate> dateRange,
                                           DeviceType deviceType) throws ClickhouseException {
        WebmasterHostId hostId = rivalsGroupId.getHostId();

        Map<LocalDate, Double> rivalsClicks = mdbRivalsStats2CHDao.getClicks(
                rivalsGroupId.getThematics(), dateRange.getLeft(), dateRange.getRight(), deviceType, 5);

        Map<LocalDate, Long> ownerClicks = mdbGroupStatistics2CHDao.getQueryStat(
                rivalsGroupId.getHostId(), Collections.singletonList(QueryIndicator.TOTAL_CLICKS_COUNT),
                dateRange.getLeft(), dateRange.getRight(),
                null, Collections.emptySet(),
                Collections.singleton(new QueryGroupId(hostId, SpecialGroup.ALL_QUERIES)), deviceType).stream()
                .collect(Collectors.toMap(GroupStat::getDate, GroupStat::getTotalClicksCount));

        /*if (ownerClicks.isEmpty()) {
            return Collections.emptyList();
        }*/
        List<GroupStat> result = new ArrayList<>();
        // костыль - свои клики всегда есть (хотя бы 0), а клики конкурентов показываем только, когда они реально есть
        for (LocalDate date = dateRange.getLeft(); !date.isAfter(dateRange.getRight()); date = date.plusDays(1)) {
            result.add(new GroupStat(date, rivalsGroupId, ownerClicks.getOrDefault(date, 0L),
                    rivalsClicks.getOrDefault(date, Double.NaN)));
        }
        return result;
    }

    public static void separateNormStats(Map<QueryGroupId, List<GroupStat>> statsAll, LocalDate normLowerEndpoint,
                                         LocalDate normUpperEndpoint, LocalDate keepAfterEndpoint, Map<QueryGroupId,
            List<GroupStat>> statsCalc, Map<QueryIndicator, Accumulator> normAccMap) {

        for (Map.Entry<QueryGroupId, List<GroupStat>> entry : statsAll.entrySet()) {
            List<GroupStat> listCalc = new ArrayList<>();
            for (GroupStat groupStat : entry.getValue()) {
                if (groupStat.getDate().compareTo(normLowerEndpoint) >= 0 && groupStat.getDate().compareTo(normUpperEndpoint) < 0) {
                    normAccMap.values().forEach(acc -> acc.apply(groupStat));
                }
                if (groupStat.getDate().compareTo(keepAfterEndpoint) >= 0) {
                    listCalc.add(groupStat);
                }
            }
            statsCalc.put(entry.getKey(), listCalc);
        }
    }

    @NotNull
    public  static Map<QueryIndicator, Double> getNormFactors(Map<QueryIndicator, Accumulator> normAccumulators) {
        Map<QueryIndicator, Double> result = new EnumMap<>(QueryIndicator.class);
        for (var entry : normAccumulators.entrySet()) {
            Accumulator acc = entry.getValue();
            if (acc.getValue() != null && acc.getValue() != 0 && acc.getItemsCount() != 0) {
                result.put(entry.getKey(), acc.getValue() / acc.getItemsCount());
            }
        }
        return result;
    }

    @NotNull
    public static Map<QueryIndicator, Accumulator> getAccumulators(Collection<QueryIndicator> indicators) {
        return indicators.stream()
                .collect(Collectors.toMap(UnaryOperator.identity(), ExtractorFactory::createExtractor));
    }
}
