package ru.yandex.webmaster3.viewer.http.indexing2.urls;

import org.joda.time.Instant;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.Action;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.http.ReadAction;
import ru.yandex.webmaster3.core.util.TimeUtils;
import ru.yandex.webmaster3.storage.indexing2.history.IndexedUrlsCountHistoryService;
import ru.yandex.webmaster3.storage.indexing2.history.data.IndexingHistoryData;
import ru.yandex.webmaster3.viewer.http.indexing2.urls.data.IndexedUrlStatusGroup;
import ru.yandex.webmaster3.core.link.DatePoint;

import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.AbstractMap.SimpleImmutableEntry;
import static java.util.stream.Collectors.toMap;
import static ru.yandex.webmaster3.viewer.http.indexing2.urls.IndexedUrlsCountHistoryResponse.IndexedUrlsHistory;

/**
 * @author avhaliullin
 */
@ReadAction
@Description("Получить историю количества загруженных документов")
@Category("indexing")
public class IndexedUrlsCountHistoryAction extends Action<IndexedUrlsCountHistoryRequest, IndexedUrlsCountHistoryResponse> {
    private IndexedUrlsCountHistoryService indexedUrlsCountHistoryService;

    @Override
    public IndexedUrlsCountHistoryResponse process(IndexedUrlsCountHistoryRequest request)
            throws WebmasterException {
        NavigableMap<Integer, IndexingHistoryData> rawHistory =
                indexedUrlsCountHistoryService.getIndexedUrlsCountHistory(
                        request.getHostId(),
                        request.getNodeId(),
                        request.getDateFrom(),
                        request.getDateTo()
                );

        if (rawHistory.isEmpty()) {
            return new IndexedUrlsCountHistoryResponse(Collections.emptyList());
        }

        // Получаем все даты, за которые есть данные хоть по одному показателю
        Set<LocalDate> allHistoryDates = rawHistory.values()
                .stream()
                .flatMap(data -> data.getTimestamp2Value().keySet().stream())
                .map(TimeUtils::instantToMSKDate)
                .collect(Collectors.toSet());

        // Переводит мапку (Instant, count) в (LocalDate, count),
        // группируя таймстампы, которые приходятся на один день
        Function<Map<Instant, Long>, NavigableMap<LocalDate, Long>> mapConverter = m -> {
            NavigableMap<LocalDate, Long> result = m.entrySet().stream().collect(toMap(
                    e -> TimeUtils.instantToMSKDate(e.getKey()),
                    Map.Entry::getValue,
                    Long::sum,
                    TreeMap::new));
            // Заполняем нулями даты, за которые по другим показателям что-то есть
            for (LocalDate date : allHistoryDates) {
                result.putIfAbsent(date, 0L);
            }
            return result;
        };

        // Обьединяет две (LocalDate, count) мапки в одну, суммируя значения для count
        BinaryOperator<NavigableMap<LocalDate, Long>> mapMerger = (m1, m2) -> {
            return Stream.of(m1, m2)
                    .map(Map::entrySet)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum, TreeMap::new));
        };

        // Обьединенная мапка, в которой ключами являются группы кодов,
        // а значениями - мапки (LocalDate, count)
        Map<IndexedUrlStatusGroup, NavigableMap<LocalDate, Long>> groupedHistory = rawHistory.entrySet().stream()
                .map(e -> new SimpleImmutableEntry<>(
                        IndexedUrlStatusGroup.of(e.getKey()),
                        mapConverter.apply(e.getValue().getTimestamp2Value())))
                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, mapMerger, () -> new EnumMap<>(IndexedUrlStatusGroup.class)));

        // Приводим полученную мапку к нужному виду
        Function<NavigableMap<LocalDate, Long>, List<DatePoint>> toDatePointList = m -> {
            return m.entrySet().stream()
                    .map(e -> new DatePoint(e.getKey(), e.getValue()))
                    .collect(Collectors.toList());
        };

        List<IndexedUrlsHistory> indexedUrlsHistories = groupedHistory.entrySet().stream()
                .map(e -> new IndexedUrlsHistory(toDatePointList.apply(e.getValue()), e.getKey()))
                .collect(Collectors.toList());
        return new IndexedUrlsCountHistoryResponse(indexedUrlsHistories);
    }

    @Required
    public void setIndexedUrlsCountHistoryService(IndexedUrlsCountHistoryService indexedUrlsCountHistoryService) {
        this.indexedUrlsCountHistoryService = indexedUrlsCountHistoryService;
    }
}
