package cz.senslog.analytics.repository;

import cz.senslog.analytics.domain.*;
import cz.senslog.analytics.utils.Tuple;
import cz.senslog.analytics.domain.ComparisonOperator;
import cz.senslog.analytics.utils.validator.NotifyTrigger;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.sqlclient.Pool;
import io.vertx.sqlclient.RowSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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

public final class AnalyticsRepositoryImpl implements AnalyticsRepository {

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

    private final Pool client;

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


    @Override
    public Future<Long> updateLastTimestamp(long unitSensorId, OffsetDateTime timestamp) {
        return client.withTransaction(tr -> tr.preparedQuery("UPDATE analytics.sensor_to_unit SET last_observation = GREATEST(last_observation, $2) WHERE id = $1")
                .execute(io.vertx.sqlclient.Tuple.of(unitSensorId, timestamp))
                .map(RowSet::iterator)
                .map(it -> it.hasNext() ? it.next().getLong(0) : 0)
        );
    }

    @Override
    public Future<Integer> saveStatisticRecord(StatisticRecord record) {
        if (record == null) {
            return Future.failedFuture("StatisticRecord to save is null.");
        }
        return client.preparedQuery("INSERT INTO analytics.record(analytic_group_id, attribute_type, calculated_value, time_interval, time_stamp) " +
                        "VALUES ($1, $2, $3, $4, $5) RETURNING (id)")
                .execute(io.vertx.sqlclient.Tuple.of(record.analyticGroupId(),
                        record.attributeType(),
                        record.calculatedValue(),
                        record.timeInterval(),
                        record.timestamp()
                ))
                .map(rs -> rs.iterator().next().getInteger("id"))
                .onFailure(logger::error);
    }

    @Override
    public Future<List<Sensor>> loadAllSensors() {
        return client.query("SELECT id, unit_id, sensor_id FROM analytics.sensor_to_unit")
                .execute()
                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                        .map(row -> Sensor.of(
                                row.getLong("id"),
                                row.getLong("unit_id"),
                                row.getLong("sensor_id")
                        )).toList()
                );
    }

    @Override
    public Future<List<Threshold>> loadThresholdsForSensor() {
        return client.query("SELECT stutt.sensor_to_unit_id AS datasource_id, stutt.threshold_id, t.enable, t.enable_process, t.enable_alert, t.custom_alert_message " +
                "FROM analytics.sensor_to_unit_to_threshold stutt " +
                "JOIN analytics.threshold t on t.id = stutt.threshold_id")
                .execute()
                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                        .map(row -> Threshold.of(
                                row.getLong("threshold_id"),
                                row.getLong("datasource_id"),
                                row.getBoolean("enable"),
                                row.getBoolean("enable_process"),
                                row.getBoolean("enable_alert"),
                                row.getString("custom_alert_message"),
                                new ArrayList<>()
                        )).toList()
                ).compose(thresholds -> Future.join(thresholds.stream()
                        .map(th -> client.preparedQuery("SELECT e.id, e.notify_trigger_mode, e.attribute_type, er.comparison_operator, er.value FROM analytics.threshold_dimension e " +
                                            "JOIN analytics.threshold_dimension_rule er on e.id = er.threshold_dimension_id WHERE e.threshold_id = $1")
                            .execute(io.vertx.sqlclient.Tuple.of(th.id()))
                            .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                                    .map(row -> ThresholdDimension.of(
                                            row.getLong("id"),
                                            NotifyTrigger.Mode.of(row.getString("notify_trigger_mode")),
                                            AttributeType.of(row.getString("attribute_type")),
                                                    new ArrayList<>() {{
                                                        add(ThresholdDimensionRule.of(
                                                                ComparisonOperator.of(row.getString("comparison_operator")),
                                                                row.getDouble("value")
                                                        ));
                                                    }}
                                            )
                                    ).collect(Collectors.toMap(ThresholdDimension::id, t -> t, (p, q) -> {
                                        p.rules().add(q.rules().get(0)); return p;
                                    })).values().stream().collect(Collectors.toCollection(th::thresholdDimensions))
                            ).compose(edges -> Future.succeededFuture(th))).toList()).map(CompositeFuture::list)
                );
    }

    @Override
    public Future<List<Tuple<Long, AnalyticGroup>>> loadAllAnalyticGroupsWithDatasource() {
        return client.query("SELECT sstag.sensor_to_unit_id AS datasource_id, sstag.analytic_group_id, ag.collector_type, ag.name FROM analytics.analytic_group ag " +
                "JOIN analytics.sensor_to_unit_to_analytic_group sstag on ag.id = sstag.analytic_group_id")
                .execute()
                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                        .map(row -> Tuple.of(
                                row.getLong("datasource_id"),
                                AnalyticGroup.of(
                                        row.getLong("analytic_group_id"),
                                        row.getString("name"),
                                        CollectorType.of(row.getString("collector_type"))
                                )
                        )).toList()
                );
    }

    @Override
    public Future<Map<Long, AnalyticGroup>> 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 -> AnalyticGroup.of(
                                row.getLong("id"),
                                row.getString("name"),
                                row.getInteger("time_interval"),
                                row.getBoolean("persistence"),
                                type
                        )))
                        .collect(Collectors.toMap(AnalyticGroup::id, Function.identity()))
        );
    }

    @Override
    public Future<List<Threshold>> loadGroupThresholds(CollectorType type) {
        return Future.succeededFuture(Collections.emptyList());
        /*
        return client.preparedQuery("")
                .execute(io.vertx.sqlclient.Tuple.of(type.name()))
                .map(rs -> StreamSupport.stream(rs.spliterator(), false)
                        .map(row -> new Threshold(
                                        row.getLong("threshold_id"),
                                        row.getLong("datasource_id"),
                                        NotifyTrigger.Mode.valueOf(row.getString("notify_trigger_mode")),
                                        AttributeType.valueOf(row.getString("attribute_type")),
                                        row.getBoolean("process_on_fail"),
                                        row.getBoolean("alert_enable"),
                                        new ArrayList<>() {{
                                            add(new ThresholdEdgeRule(
                                                    ComparisonOperator.of(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()
                );

         */
    }
}
