package cz.senslog.analytics.utils.validator;

import cz.senslog.analytics.domain.*;
import cz.senslog.analytics.utils.newvalidator.MessageFormatter;
import cz.senslog.analytics.utils.validator.domain.ValidationReport;
import cz.senslog.analytics.utils.validator.domain.ValidationResult;
import cz.senslog.analytics.utils.validator.domain.ViolationReport;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.OffsetDateTime;
import java.util.*;
import java.util.function.Consumer;

import static java.util.Collections.emptyList;

public class ThresholdChecker<DS extends TimeSeriesDatasource> {

    private static final Logger logger = LogManager.getLogger(ThresholdChecker.class);

    public static <T extends TimeSeriesDatasource> ThresholdChecker<T> disabled() {
        return ThresholdChecker.create(emptyList(), null, null, true);
    }

    private final Map<Long, ThresholdManager> datasourceThresholdManagers;

    private final Validator<DS> validator;

    private final Consumer<ViolationReport> ifViolated;

    private final boolean defaultIfNotPresent;

    public static <DS extends TimeSeriesDatasource> ThresholdChecker<DS> create(List<Threshold> thresholds,
                                                           Validator<DS> validator,
                                                           Consumer<ViolationReport> ifViolated, boolean defaultIfNotPresent)
    {
        return new ThresholdChecker<>(thresholds, validator, ifViolated, defaultIfNotPresent);
    }

    public ThresholdChecker(List<Threshold> thresholds,
                            Validator<DS> validator,
                            Consumer<ViolationReport> ifViolated,
                            boolean defaultIfNotPresent
    ) {
        this.validator = validator;
        this.ifViolated = ifViolated;
        this.defaultIfNotPresent = defaultIfNotPresent;
        this.datasourceThresholdManagers = new HashMap<>();

        Map<Long, Map<NotifyTrigger.Mode, NotifyTrigger>> dataSourceToNotifyTriggers = new HashMap<>();
        for (Threshold th : thresholds) {
            Set<NotifyTrigger> triggers = new HashSet<>(th.thresholdDimensions().size());
            for (int i = 0; i < th.thresholdDimensions().size(); i++) {
                ThresholdDimension thDim = th.thresholdDimensions().get(i);
                triggers.add(dataSourceToNotifyTriggers
                    .computeIfAbsent(th.datasourceId(), dataSource -> new HashMap<>(NotifyTrigger.Mode.values().length))
                    .computeIfAbsent(thDim.notifyTriggerMode(), NotifyTrigger.Mode::createInstance));
            }

            this.datasourceThresholdManagers.computeIfAbsent(th.datasourceId(), ThresholdManager::new).register(th, triggers.toArray(new NotifyTrigger[0]));
        }
    }

    /**
     * The method checks the DS as datasource over all thresholds and returns boolean value of enable/disable further processing.
     * If the validation result if 'false' but processing on failure is true, the return value is true.
     * @param data DS to check
     * @return boolean of enable/disable further processing
     */
    public boolean check(DS data) {
        return validateThreshold(data);
    }

    private boolean validateThreshold(DS data) {

        long sourceId = data.datasourceId();
        OffsetDateTime timestamp = data.timestamp();

        if (sourceId <= 0 || !datasourceThresholdManagers.containsKey(sourceId)) {
            return defaultIfNotPresent;
        }
        boolean process = true;

        ThresholdManager thresholdManager = datasourceThresholdManagers.get(sourceId);
        for (Threshold th : thresholdManager.thresholds()) {
            ValidationResult[] res = validator.validate(data, th);
            thresholdManager.accept(th, res);

            if (!th.enableProcess()) {
                // process on failure is set to false then calculate if the validation is true or false
                boolean isNotValid = res[0].isNotValid();
                for (int i = 1; i < res.length; i++) {
                    isNotValid &= res[i].isNotValid();
                }
                if (isNotValid) {
                    process = false;
                }
            }
        }

        if (thresholdManager.shouldNotify()) {
            ValidationReport report = thresholdManager.resultsToNotify();
            ifViolated.accept(new ViolationReport(sourceId, timestamp, report.message(),  report.validationResults()));
        }

        return process;
    }
}