package ru.yandex.webmaster3.monitoring.task.periodic;

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

import lombok.Setter;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.storage.logging.TaskLogEntry;
import ru.yandex.webmaster3.storage.logging.TasksLoggingCHDao;

/**
 * Created by leonidrom on 23/06/2017.
 */
public class PeriodicTasksMonitoringService {
    private static final Logger log = LoggerFactory.getLogger(PeriodicTasksMonitoringService.class);

    private static final String DATA_TYPE = "periodic_tasks";

    private static final int AVERAGE_SENSORS_SIZE = 500;
    private static Duration MIN_TASK_RUN_TIME = Duration.standardMinutes(1);
    private static Duration MAX_AGE = Duration.standardDays(30);

    @Setter
    private HandleCommonMetricsService handleCommonMetricsService;
    @Setter
    private TasksLoggingCHDao mdbPeriodicTasksLoggingCHDao;
    private Map<String, TaskLogEntry> latestEntries = new HashMap<>();

    @Setter
    private boolean enabled;
    @Setter
    private long refreshIntervalSeconds;

    @Scheduled(cron = "${webmaster3.monitoring.solomon.tasks.periodic.refresh-cron}")
    private void push() {
        if (!enabled) {
            log.warn("Periodic tasks push metrics service disabled");
            return;
        }

        List<TaskLogEntry> entriesFinish = mdbPeriodicTasksLoggingCHDao.listLatestEntries(TaskLogEntry.EventType.FINISH);
        Map<String, TaskLogEntry> entriesErrorMap = mdbPeriodicTasksLoggingCHDao.listLatestEntries(TaskLogEntry.EventType.ERROR)
                .stream()
                .collect(Collectors.toMap(TaskLogEntry::getTaskType, Function.identity()));

        long now = System.currentTimeMillis();
        List<SolomonSensor> sensors = new ArrayList<>();

        entriesFinish.forEach(e -> {
            if (PeriodicTaskType.R.valueOfOrNull(e.getTaskType()) == null){
                log.info("Skipping unknown task {}", e.getTaskType());
                return;
            }

            long delta = (now - e.getDate().getMillis());
            if (delta < 0) {
                log.error("Entry " + e + " has finish time " + e.getDate().getMillis() + " which is after now " + now);
                return;
            }

            if (delta < MAX_AGE.getMillis()) {
                TaskLogEntry latestFail = entriesErrorMap.get(e.getTaskType());
                boolean isFailing = latestFail != null && latestFail.getDate().isAfter(e.getDate());

                sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, isFailing? delta / 1000 : 0)
                        .withLabel(SolomonSensor.LABEL_TASK, e.getTaskType())
                        .withLabel(SolomonSensor.LABEL_DATA_TYPE, DATA_TYPE)
                        .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.DATA_AGE)
                );
            }
        });

        // Смотрим, новый ли это результат для каждой таски, и если да,
        // то отправляем в соломон время ее выполнения
        entriesFinish.forEach(e -> {
            TaskLogEntry curEntry = latestEntries.get(e.getTaskType());
            if (curEntry != null && !curEntry.getRunId().equals(e.getRunId())) {
                if (e.getRunTime() > MIN_TASK_RUN_TIME.getMillis()) {
                    sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, e.getRunTime() / 1000)
                            .withLabel(SolomonSensor.LABEL_TASK, e.getTaskType())
                            .withLabel(SolomonSensor.LABEL_DATA_TYPE, DATA_TYPE)
                            .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.RUN_TIME)
                    );
                }
            }

            latestEntries.put(e.getTaskType(), e);
        });

        if (!sensors.isEmpty()) {
            handleCommonMetricsService.handle(sensors, AVERAGE_SENSORS_SIZE);
        }
    }
}
