package ru.yandex.webmaster3.monitoring.turbo.api;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import lombok.RequiredArgsConstructor;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

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.solomon.metric.SolomonCounter;
import ru.yandex.webmaster3.core.solomon.metric.SolomonCounterImpl;
import ru.yandex.webmaster3.core.solomon.metric.SolomonHistogram;
import ru.yandex.webmaster3.core.solomon.metric.SolomonHistogramImpl;
import ru.yandex.webmaster3.core.solomon.metric.SolomonKey;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboApiTaskWithResult;
import ru.yandex.webmaster3.core.turbo.model.feed.TurboCrawlState;
import ru.yandex.webmaster3.core.util.IdUtils;
import ru.yandex.webmaster3.storage.turbo.dao.api.TurboApiHostTasksYDao;
import ru.yandex.webmaster3.storage.util.yt.YtColumn;
import ru.yandex.webmaster3.storage.util.yt.YtNode;
import ru.yandex.webmaster3.storage.util.yt.YtNodeAttributes;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtSchema;
import ru.yandex.webmaster3.storage.util.yt.YtService;

/**
 * Created by ifilippov5 on 06.06.18.
 */
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class TurboApiTasksMonitoringService {
    private static final Logger log = LoggerFactory.getLogger(TurboApiTasksMonitoringService.class);

    private static final String SECTION_LABEL_VALUE = "turbo_api";
    private static final int AVERAGE_SENSORS_SIZE = 600;
    private static final List<Duration> BUCKETS = Arrays.asList(
            Duration.standardMinutes(10),
            Duration.standardMinutes(30),
            Duration.standardHours(1),
            Duration.standardHours(2),
            Duration.standardHours(4),
            Duration.standardHours(12),
            Duration.standardDays(1),
            Duration.standardDays(2),
            Duration.standardDays(4),
            Duration.standardDays(8),
            Duration.standardDays(14),
            Duration.standardDays(30)
    );
    private static final long OLD_TASK_MIN_AGE = Duration.standardMinutes(30L).getMillis();

    private interface F {
        YtSchema SCHEMA = new YtSchema();
        YtColumn<String> HOST = SCHEMA.addColumn("Host", YtColumn.Type.STRING);
        YtColumn<String> TASK_ID = SCHEMA.addColumn("TaskId", YtColumn.Type.STRING);
        YtColumn<Long> TIMESTAMP = SCHEMA.addColumn("Timestamp", YtColumn.Type.INT_64);
    }

    private final HandleCommonMetricsService handleCommonMetricsService;
    private final TurboApiHostTasksYDao turboApiHostTasksYDao;
    private final YtService ytService;

    @Value("${external.yt.service.hahn.root.default}/turbo/old-api-tasks")
    private YtPath oldTasksTablePath;
    @Value("${webmaster3.monitoring.turbo-api.tasks.refresh-interval}")
    private long refreshIntervalSeconds;

    @Scheduled(cron = "${webmaster3.monitoring.turbo-api.tasks.refresh-cron}")
    private void push() throws Exception {
        long now = System.currentTimeMillis();
        DateTime minAddDate = DateTime.now().minusDays(30);
        Map<SolomonKey, SolomonCounterImpl> sensorsAcc = new HashMap<>();
        SolomonHistogram<Duration> done = createHistogram(sensorsAcc, true);
        SolomonHistogram<Duration> pending = createHistogram(sensorsAcc, false);
        List<TurboApiTaskWithResult> oldTasks = new ArrayList<>();
        turboApiHostTasksYDao.foreach(task -> {
            // игнорим таски старше 30 дней
            if (task.getAddDate().isBefore(minAddDate)) {
                return;
            }
            if (task.getState() == TurboCrawlState.PROCESSING) {
                Duration millis = Duration.millis(now - task.getAddDate().getMillis());
                log.debug("Found unprocessed task: host_id = {}, task_id = {}, age = {}", task.getHostId(), task.getTaskId(), millis.getStandardMinutes());
                pending.update(millis);
                if (millis.getMillis() >= OLD_TASK_MIN_AGE) {
                    oldTasks.add(task);
                }
            } else {
                done.update(Duration.millis(task.getImportDate().getMillis() - task.getAddDate().getMillis()));
            }
        });

        List<SolomonSensor> sensors = sensorsAcc.entrySet().stream()
                .map(e -> SolomonSensor.createAligned(
                        e.getKey(),
                        now,
                        refreshIntervalSeconds,
                        e.getValue().getAsLong()
                ))
                .collect(Collectors.toList());

        handleCommonMetricsService.handle(sensors, AVERAGE_SENSORS_SIZE);

        // сольем в yt старые таски
        ytService.inTransaction(oldTasksTablePath).execute(cypressService -> {
            cypressService.remove(oldTasksTablePath, false, true);
            cypressService.create(oldTasksTablePath, YtNode.NodeType.TABLE, true,
                    new YtNodeAttributes().setSchema(F.SCHEMA), true);
            cypressService.writeTable(oldTasksTablePath, tableWriter -> {
                for (TurboApiTaskWithResult task : oldTasks) {
                    F.HOST.set(tableWriter, IdUtils.hostIdToUrl(task.getHostId()));
                    F.TASK_ID.set(tableWriter, task.getTaskId().toString());
                    F.TIMESTAMP.set(tableWriter, task.getAddDate().getMillis());
                    tableWriter.rowEnd();
                }
            });
            return true;
        });
    }

    private static SolomonHistogram<Duration> createHistogram(Map<SolomonKey, SolomonCounterImpl> sensorsAcc, boolean done) {
        return SolomonHistogramImpl.create(duration -> createCounter(sensorsAcc, done, duration), BUCKETS);
    }

    private static SolomonCounter createCounter(Map<SolomonKey, SolomonCounterImpl> sensorsAcc, boolean done, Duration duration) {
        return sensorsAcc.computeIfAbsent(SolomonKey.create(
                SolomonKey.LABEL_INDICATOR, Indicators.QUEUE_SIZE)
                        .withLabel(SolomonSensor.LABEL_SECTION, SECTION_LABEL_VALUE)
                        .withLabel("task_status", done ? "done" : "processing")
                        .withLabel(SolomonKey.LABEL_TIME_BUCKET, "<" + duration.getStandardMinutes() + "m"),
                ign -> SolomonCounterImpl.create(false)
        );
    }
}
