package cz.senslog.analytics.repository;

import cz.senslog.analytics.domain.*;
import cz.senslog.analytics.utils.Tuple;
import cz.senslog.analytics.utils.validator.NotifyTrigger;
import io.vertx.core.Future;
import io.vertx.sqlclient.Pool;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public final class AnalyticsConfigRepository implements ConfigurationRepository {

    private final Pool client;

    public AnalyticsConfigRepository(Pool client) {
        this.client = client;
    }


    @Override
    public Future<Map<Sensor, List<Group>>> loadSensorToGroupsMapping() {
        return client.query("SELECT s.sensor_id, s.unit_id, sg.analytic_group_id, g.collector_type FROM analytics.entity_source_to_analytic_group sg " +
                        "    JOIN analytics.analytic_group g on g.id = sg.id " +
                        "    JOIN analytics.entity_source s on s.id = sg.entity_source_id")
                .execute()
                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                        .map(row -> Tuple.of(
                                new Sensor(-1, // TODO is ID important?
                                    row.getLong("unit_id"),
                                    row.getLong("sensor_id"),
                                    row.getLong("analytic_group_id")
                                ), new Group(
                                    row.getLong("analytic_group_id"),
                                    CollectorType.valueOf(row.getString("collector_type"))
                                ))
                        ).collect(Collectors.groupingBy(
                                Tuple::getItem1, HashMap::new, Collectors.mapping(Tuple::getItem2, Collectors.toList()))
                        )
                );
    }

    @Override
    public Future<List<Threshold>> loadSensorThresholds() {
        return client.query("SELECT th.id, th.analytic_group_id, th.notify_trigger_mode, th.attribute_type, th.process_on_fail, tr.threshold_mode, tr.threshold_value " +
                        "FROM analytics.threshold AS th " +
                        "JOIN analytics.analytic_group AS gi ON gi.id = th.analytic_group_id " +
                        "JOIN analytics.threshold_rule tr on th.id = tr.threshold_id " +
                        "WHERE gi.collector_type IS NULL")
                .execute()
                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                        .map(row -> new Threshold(
                                        row.getLong("id"),
                                        row.getLong("analytic_group_id"),
                                        NotifyTrigger.Type.valueOf(row.getString("notify_trigger_mode")),
                                        row.getBoolean("process_on_fail"),
                                        AttributeName.valueOf(row.getString("attribute_type")),
                                        new ArrayList<>() {{
                                            add(new ThresholdRule(
                                                    row.getString("threshold_mode"),
                                                    row.getDouble("threshold_value")
                                            ));
                                        }}
                                    )
                        ).collect(Collectors.toMap(Threshold::id, t -> t, (p, q) -> {
                            p.rules().add(q.rules().get(0)); return p;
                        })).values().stream().toList()
                );
    }

    @Override
    public Future<List<MessageBrokerConfig>> loadMessageBrokers() {
        // TODO waits to redesign the messaging
        return Future.succeededFuture(Collections.emptyList());
    }

    @Override
    public Future<List<SourceToMessageBroker>> loadSourceToMessageBrokerMapping() {
        // TODO waits to redesign the messaging
        return Future.succeededFuture(Collections.emptyList());
    }

    @Override
    public Future<Map<Long, Group>> loadGroupsByCollectorType(CollectorType type) {
        return client.preparedQuery("SELECT id, name, time_interval, persistence FROM analytics.analytic_group WHERE collector_type = $1")
                .execute(io.vertx.sqlclient.Tuple.of(type.name()))
                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                        .map((row -> new Group(
                                row.getLong("id"),
                                row.getString("name"),
                                row.getInteger("time_interval"),
                                row.getBoolean("persistence"),
                                type
                        )))
                        .collect(Collectors.toMap(Group::id, Function.identity()))
        );
    }

    @Override
    public Future<List<Threshold>> loadGroupThresholds(CollectorType type) {
        return client.preparedQuery("SELECT th.id, th.analytic_group_id, th.notify_trigger_mode, th.attribute_type, th.process_on_fail, tr.threshold_mode, tr.threshold_value " +
                        "FROM analytics.threshold AS th " +
                        "JOIN analytics.analytic_group AS gi ON gi.id = th.analytic_group_id " +
                        "JOIN analytics.threshold_rule tr on th.id = tr.threshold_id " +
                        "WHERE gi.collector_type = $1")
                .execute(io.vertx.sqlclient.Tuple.of(type.name()))
                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                        .map(row -> new Threshold(
                                        row.getLong("id"),
                                        row.getLong("analytic_group_id"),
                                        NotifyTrigger.Type.valueOf(row.getString("notify_trigger_mode")),
                                        row.getBoolean("process_on_fail"),
                                        AttributeName.valueOf(row.getString("attribute_type")),
                                        new ArrayList<>() {{
                                            add(new ThresholdRule(
                                                    row.getString("threshold_mode"),
                                                    row.getDouble("threshold_value")
                                            ));
                                        }}
                                )
                        ).collect(Collectors.toMap(Threshold::id, t -> t, (p, q) -> {
                            p.rules().add(q.rules().get(0)); return p;
                        })).values().stream().toList()
                );
    }
}
