package cz.senslog.analytics.utils.newvalidator;

import cz.senslog.analytics.domain.AttributeType;
import cz.senslog.analytics.domain.TimeSeriesDatasource;
import cz.senslog.analytics.domain.VariableType;

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

import static cz.senslog.analytics.domain.VariableType.*;
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;

class ThresholdManager<DS extends TimeSeriesDatasource> {

    private final DataSource datasource;
    private final Validator<DS> validator;
    private final List<Threshold> thresholds;
    private final Map<Long, Map<AttributeType, NotifyTrigger>> notifyTriggers;
    private final MessageFormatter messageFormatter;

    protected ThresholdManager(DataSource datasource, Validator<DS> validator, List<Threshold> thresholds) {
        this.datasource = datasource;
        this.validator = validator;
        this.thresholds = thresholds;

        Set<AttributeType> supportedAttributes = validator.attributes();
        this.messageFormatter = MessageFormatter.createOf(Set.of(VariableType.values()), supportedAttributes);
        this.notifyTriggers = new HashMap<>(thresholds.size());
        for (Threshold th : thresholds) {
            // TODO check if Threshold#message is valid according to the dimension#attributeType
            for (Threshold.Dimension dim : th.dimensions()) {
                notifyTriggers.computeIfAbsent(th.id(), thId -> new HashMap<>(dim.rules().size()))
                        .computeIfAbsent(dim.attributeType(), attr -> dim.notifyTriggerMode().createInstance());
            }
        }
    }

    public ValidationReport accept(final DS data) {
        if (data == null || data.datasourceId() != this.datasource.id()) {
            return null;
        }

        OffsetDateTime timestamp = data.timestamp();
        List<String> messagesToNotify = new ArrayList<>(thresholds.size());

        boolean shouldProcess = false;
        boolean shouldNotify = false;

        for (Threshold threshold : thresholds) {
            ValidationResult[] validationResults = validator.validate(data, threshold);
            Map<AttributeType, NotifyTrigger> triggers = notifyTriggers.get(threshold.id());
            List<NotifyReport> thReports = new ArrayList<>(validationResults.length);

            boolean thIsViolated = true;
            boolean thShouldNotify = threshold.enableAlert(); // if alert is disabled then it is always false

            for (ValidationResult result : validationResults) {
                Optional<NotifyReport> dimNotifyReportOpt = triggers.get(result.violatedAttribute()).accept(result); // all trigger must have something to notify
                dimNotifyReportOpt.ifPresent(thReports::add);
                thShouldNotify = thShouldNotify && dimNotifyReportOpt.isPresent(); // isPresent() = true = violation should notify
                thIsViolated = thIsViolated && result.hasViolation(); // all must be violated
            }

            // should process if at least one threshold is valid over all dimensions OR the process is enabled if violation
            shouldProcess = shouldProcess || !thIsViolated || threshold.enableProcess();

            // none or at least one has something to notify
            shouldNotify = shouldNotify || thShouldNotify;

            if (thShouldNotify) {
                if (threshold.messagePattern() != null) {
                    String patternMessage = threshold.messagePattern();
                    Map<AttributeType, Double> messageArgs = new HashMap<>(thReports.size());
                    for (NotifyReport thR : thReports) {
                        messageArgs.put(thR.attributeType(), thR.value());
                    }

                    messagesToNotify.add(messageFormatter.format(patternMessage, messageArgs, Map.of(
                            TIMESTAMP, (f -> timestamp.format(f.map(DateTimeFormatter::ofPattern).orElse(ISO_OFFSET_DATE_TIME))),
                            MODULE, (f -> datasource.moduleName()),
                            DATASOURCE, (f -> datasource.moduleName())
                    )));

                } else {
                    StringBuilder thMessage = new StringBuilder();
                    NotifyReport r0 = thReports.get(0);
                    thMessage.append(r0.violatedRulesMessage());
                    for (int i = 1; i < thReports.size(); i++) {
                        thMessage.append("\n").append(thReports.get(i).violatedRulesMessage());
                    }
                    messagesToNotify.add(thMessage.toString());
                }
            }
        }

        return new ValidationReport(
                shouldProcess,
                shouldNotify ? Optional.of( String.join("\n\n", messagesToNotify)) : Optional.empty()
        );
    }
}
