package ru.yandex.webmaster3.monitoring.ytimport;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.google.common.collect.ImmutableMap;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.Duration;
import org.joda.time.Instant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;

import ru.yandex.webmaster3.core.solomon.HandleCommonMetricsService;
import ru.yandex.webmaster3.core.solomon.Indicators;
import ru.yandex.webmaster3.core.solomon.SolomonSensor;
import ru.yandex.webmaster3.storage.searchquery.importing.dao.YtClickhouseDataLoadYDao;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoad;
import ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType;

import static org.joda.time.Duration.standardDays;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.DIGEST_MESSAGES;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.DISPLAY_NAME;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.DISPLAY_NAME_CHANGES;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.EXTENDED_RECOMMENDED_QUERIES;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.EXTERNAL_GONE_LINKS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.EXTERNAL_LINKS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.FAVORITE;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.GROUP;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.IKS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.IKS_HISTORY;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.INTERNAL_LINKS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.LINK_STATISTICS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.METRIKA_STATS_BY_TIME;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.MIRRORS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.MIRRORS_CHANGES;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.NICHE_QUERIES;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.NICHE_QUERIES2;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.NICHE_RIVAL_QUERIES_REPORT;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.NICHE_RIVAL_QUERIES_REPORT2;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.OWNER_THREATS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.RECOMMENDED_QUERIES;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.RECOMMENDED_URLS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.RIVALS_STATS2;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.TOP;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.TOP_URLS;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.TURBO_DOMAINS_STATE;
import static ru.yandex.webmaster3.storage.ytimport.YtClickhouseDataLoadType.WEEK;

/**
 * WMC-10854: Мониторинг отставания импортов
 */
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class YtClickhouseDataLoadMonitoringService {

    private static final String SECTION_NAME = "yt2ch_import";
    private static final int AVERAGE_SENSORS_SIZE = 100;

    private final HandleCommonMetricsService handleCommonMetricsService;
    private final YtClickhouseDataLoadYDao ytClickhouseDataLoadYDao;

    private static final Config DEFAULT_CONFIG = Config.of(true, standardDays(2), standardDays(3));
    private static final EnumMap<YtClickhouseDataLoadType, Config> CONFIGS = new EnumMap<>(ImmutableMap.<YtClickhouseDataLoadType, Config>builder()
            .put(INTERNAL_LINKS, Config.disabled())
            .put(EXTERNAL_LINKS, Config.disabled())
            .put(EXTERNAL_GONE_LINKS, Config.disabled())
            .put(LINK_STATISTICS, Config.disabled())
            .put(TURBO_DOMAINS_STATE, Config.of(true, standardDays(1), standardDays(2)))
            .put(OWNER_THREATS, Config.of(true, standardDays(1), standardDays(2)))
            .put(DIGEST_MESSAGES, Config.of(true, standardDays(8), standardDays(10)))
            .put(IKS, Config.of(true, standardDays(8), standardDays(10)))
            .put(IKS_HISTORY, Config.of(true, standardDays(8), standardDays(10)))
            .put(RECOMMENDED_QUERIES, Config.of(true, standardDays(8), standardDays(10)))
            .put(EXTENDED_RECOMMENDED_QUERIES, Config.of(true, standardDays(8), standardDays(10)))
            .put(DISPLAY_NAME, Config.of(true, standardDays(4), standardDays(5)))
            .put(DISPLAY_NAME_CHANGES, Config.of(true, standardDays(4), standardDays(5)))
            .put(MIRRORS, Config.of(true, standardDays(4), standardDays(5)))
            .put(MIRRORS_CHANGES, Config.of(true, standardDays(4), standardDays(5)))
            .put(GROUP, Config.of(true, standardDays(3), standardDays(4)))
            .put(FAVORITE, Config.of(true, standardDays(3), standardDays(4)))
            .put(WEEK, Config.of(true, standardDays(3), standardDays(4)))
            .put(TOP, Config.of(true, standardDays(3), standardDays(4)))
            .put(TOP_URLS, Config.of(true, standardDays(3), standardDays(4)))
            .put(METRIKA_STATS_BY_TIME, Config.of(true, standardDays(3), standardDays(4)))
            .put(RIVALS_STATS2, Config.of(true, standardDays(3), standardDays(4)))
            .put(RECOMMENDED_URLS, Config.of(true, standardDays(7), standardDays(8)))
            .put(NICHE_QUERIES, Config.disabled())
            .put(NICHE_RIVAL_QUERIES_REPORT, Config.disabled())
            .put(NICHE_QUERIES2, Config.disabled())
            .put(NICHE_RIVAL_QUERIES_REPORT2, Config.disabled())
            .build()
    );

    @Value("${webmaster3.monitoring.solomon.yt2ch.enabled:true}")
    private boolean enabled;
    @Value("${webmaster3.monitoring.solomon.yt2ch.refresh-interval:1200}")
    private long refreshIntervalSeconds;

    @Scheduled(cron = "0 */20 * * * *")
    protected void push() throws Exception {
        if (!enabled) {
            log.warn("YT to Clickhouse data load monitoring service disabled");
            return;
        }

        List<SolomonSensor> sensors = new ArrayList<>();
        Map<YtClickhouseDataLoadType, YtClickhouseDataLoad> dataLoads = ytClickhouseDataLoadYDao.listAll().stream()
                .filter(dataLoad -> dataLoad.getType() != null)
                .collect(Collectors.toMap(YtClickhouseDataLoad::getType, Function.identity()));
        Instant now = Instant.now();
        for (YtClickhouseDataLoadType dataLoadType : YtClickhouseDataLoadType.values()) {
            Config config = CONFIGS.getOrDefault(dataLoadType, DEFAULT_CONFIG);
            YtClickhouseDataLoad dataLoad = dataLoads.get(dataLoadType);
            if (dataLoad == null) {
                continue;
            }
            Instant maxProcessedDate = Objects.requireNonNullElse(dataLoad.getLastSuccess(),
                    dataLoad.getMaxProcessedDate().toDateTimeAtStartOfDay().toInstant());
            boolean warn = config.isEnabled() && maxProcessedDate.plus(config.getWarnDuration()).isBefore(now);
            boolean error = config.isEnabled() && maxProcessedDate.plus(config.getErrorDuration()).isBefore(now);

            sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, (now.getMillis() - maxProcessedDate.getMillis()) / 1000)
                    .withLabel(SolomonSensor.LABEL_SECTION, SECTION_NAME)
                    .withLabel(SolomonSensor.LABEL_DATA_TYPE, dataLoadType.name().toLowerCase())
                    .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.DATA_AGE));

            int state = error ? 2 : warn ? 1 : 0;
            sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, state)
                    .withLabel(SolomonSensor.LABEL_SECTION, SECTION_NAME)
                    .withLabel(SolomonSensor.LABEL_DATA_TYPE, dataLoadType.name().toLowerCase())
                    .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.ALERT_STATE));
        }

        handleCommonMetricsService.handle(sensors, AVERAGE_SENSORS_SIZE * sensors.size());
    }

    @lombok.Value(staticConstructor = "of")
    private static class Config {
        boolean enabled;
        Duration warnDuration;
        Duration errorDuration;

        public static Config disabled() {
            return Config.of(false, null, null);
        }
    }

}
