package cz.senslog.analytics.utils.newvalidator;

import cz.senslog.analytics.domain.*;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.stream.Stream;

import static cz.senslog.analytics.domain.AttributeType.TIME;
import static cz.senslog.analytics.domain.AttributeType.VAL;
import static cz.senslog.analytics.domain.ComparisonOperator.*;
import static cz.senslog.analytics.utils.newvalidator.NotifyTrigger.Mode.INSTANT;
import static cz.senslog.analytics.utils.newvalidator.NotifyTrigger.Mode.ON_CHANGE;
import static org.junit.jupiter.api.Assertions.*;

class ThresholdCheckerTest {

    private static final Sensor SENSOR = new Sensor(100L, 1000L, 1001L);

    private static Validator<Observation> validator;

    @BeforeAll
    static void setUp() {
        validator = Validator.<Observation>create()
                .addMapping(VAL, o -> o::value)
                .addMapping(AttributeType.TIME, o -> () ->  (double) o.timestamp().toLocalTime().toSecondOfDay());
    }

    @Test
    void test_1() {

        final OffsetDateTime startInterval = OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneOffset.ofHours(2));
        final OffsetDateTime endInterval = OffsetDateTime.of(2024, 1, 1, 15, 0, 0, 0, ZoneOffset.ofHours(2));


        ThresholdChecker<Observation> checker = ThresholdChecker.create(
                "testModule",
                List.of(
                        new Threshold(1, SENSOR.id(), false, true, "$val", List.of(
                                new Threshold.Dimension(VAL, INSTANT, List.of(
                                                new Threshold.Dimension.Rule(GT, 25)
                                        )
                                ),
                                new Threshold.Dimension(TIME, INSTANT, List.of(
                                            new Threshold.Dimension.Rule(GE, 43200),
                                            new Threshold.Dimension.Rule(LE, 54000)
                                        )
                                )
                        ))
                ),
                validator,
                (alert) -> {
                    assertEquals(25.3, Double.parseDouble(alert.message()));
                    assertEquals(SENSOR.id(), alert.dataSourceId());
                    assertFalse(alert.timestamp().isBefore(startInterval) || alert.timestamp().isAfter(endInterval));
                },
                true
        );

        List<Observation> passedObservations = Stream.of(
                /* pass | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.1, OffsetDateTime.of(2024, 1, 1, 11, 59, 59, 0, ZoneOffset.ofHours(2)))),
                /* fail | alert */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneOffset.ofHours(2)))),
                /* fail | alert */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 12, 0, 1, 0, ZoneOffset.ofHours(2)))),
                /* fail | alert */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 14, 59, 59, 0, ZoneOffset.ofHours(2)))),
                /* fail | alert */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 15, 0, 0, 0, ZoneOffset.ofHours(2)))),
                /* pass | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.1, OffsetDateTime.of(2024, 1, 1, 15, 0, 1, 0, ZoneOffset.ofHours(2))))
        ).filter(checker::check).toList();

        assertEquals(2, passedObservations.size());
    }

    @Test
    void test_2() {

        final OffsetDateTime startInterval = OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneOffset.ofHours(2));
        final OffsetDateTime endInterval = OffsetDateTime.of(2024, 1, 1, 15, 0, 0, 0, ZoneOffset.ofHours(2));


        ThresholdChecker<Observation> checker = ThresholdChecker.create(
                "testModule",
                List.of(
                        new Threshold(1, SENSOR.id(), false, true, "$val(%.2f) >= 25 & 12h <= $timestamp(HH:mm) <= 15h v čase $timestamp.", List.of(
                                new Threshold.Dimension(VAL, ON_CHANGE, List.of(
                                        new Threshold.Dimension.Rule(GT, 25)
                                )
                                ),
                                new Threshold.Dimension(TIME, INSTANT, List.of(
                                        new Threshold.Dimension.Rule(GE, 43200), // 12h
                                        new Threshold.Dimension.Rule(LE, 54000) // 15h
                                )
                                )
                        )),
                        new Threshold(2, SENSOR.id(), false, true, null, List.of(
                                new Threshold.Dimension(VAL, INSTANT, List.of(
                                        new Threshold.Dimension.Rule(GT, 25)
                                    )
                                ),
                                new Threshold.Dimension(TIME, ON_CHANGE, List.of(
                                        new Threshold.Dimension.Rule(GE, 43200), // 12h
                                        new Threshold.Dimension.Rule(LE, 54000) // 15h
                                    )
                                )
                        ))
                ),
                validator,
                (alert) -> {
                    System.out.println(alert.message()+"\n");
                    assertEquals(SENSOR.id(), alert.dataSourceId());
                    assertTrue(alert.timestamp().isEqual(startInterval.plusSeconds(1)) || alert.timestamp().isEqual(endInterval));
                },
                true
        );

        List<Observation> passedObservations = Stream.of(
                /* pass | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 24.1, OffsetDateTime.of(2024, 1, 1, 11, 59, 59, 0, ZoneOffset.ofHours(2)))),
                /* pass | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 24.2, OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneOffset.ofHours(2)))),
                /* fail | alert */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.1, OffsetDateTime.of(2024, 1, 1, 12, 0, 1, 0, ZoneOffset.ofHours(2)))),
                /* fail | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.2, OffsetDateTime.of(2024, 1, 1, 14, 59, 59, 0, ZoneOffset.ofHours(2)))),
                /* pass | alert */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 24.3, OffsetDateTime.of(2024, 1, 1, 15, 0, 0, 0, ZoneOffset.ofHours(2)))),
                /* pass | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 24.3, OffsetDateTime.of(2024, 1, 1, 15, 0, 1, 0, ZoneOffset.ofHours(2)))),
                /* pass | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 15, 1, 1, 0, ZoneOffset.ofHours(2))))
        ).filter(checker::check).toList();

        assertEquals(5, passedObservations.size());
    }

    @Test
    void test_3() {

        final OffsetDateTime startInterval = OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneOffset.ofHours(2));
        final OffsetDateTime endInterval = OffsetDateTime.of(2024, 1, 1, 15, 0, 0, 0, ZoneOffset.ofHours(2));


        ThresholdChecker<Observation> checker = ThresholdChecker.create(
                "testModule",
                List.of(
                        new Threshold(1, SENSOR.id(), false, true, null, List.of(
                                new Threshold.Dimension(VAL, ON_CHANGE, List.of(
                                        new Threshold.Dimension.Rule(GT, 25)
                                    )
                                ),
                                new Threshold.Dimension(TIME, INSTANT, List.of(
                                        new Threshold.Dimension.Rule(GE, 43200),
                                        new Threshold.Dimension.Rule(LE, 54000)
                                    )
                                )
                        )),
                        new Threshold(2, SENSOR.id(), false, true, null, List.of(
                                new Threshold.Dimension(VAL, INSTANT, List.of(
                                        new Threshold.Dimension.Rule(GT, 25)
                                    )
                                ),
                                new Threshold.Dimension(TIME, ON_CHANGE, List.of(
                                        new Threshold.Dimension.Rule(GE, 43200),
                                        new Threshold.Dimension.Rule(LE, 54000)
                                    )
                                )
                        ))
                ),
                validator,
                (alert) -> {
                    System.out.println(alert.message()+"\n");
                    assertEquals(SENSOR.id(), alert.dataSourceId());
                    assertTrue(alert.timestamp().isEqual(startInterval) || alert.timestamp().isEqual(endInterval));
                },
                true
        );

        List<Observation> passedObservations = Stream.of(
                /* pass | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 11, 59, 59, 0, ZoneOffset.ofHours(2)))),
                /* fail | alert */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneOffset.ofHours(2)))),
                /* fail | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 12, 0, 1, 0, ZoneOffset.ofHours(2)))),
                /* fail | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.of(2024, 1, 1, 14, 59, 59, 0, ZoneOffset.ofHours(2)))),
                /* pass | alert */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 24.3, OffsetDateTime.of(2024, 1, 1, 15, 0, 0, 0, ZoneOffset.ofHours(2)))),
                /* pass | none  */ Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 24.3, OffsetDateTime.of(2024, 1, 1, 15, 0, 1, 0, ZoneOffset.ofHours(2))))
        ).filter(checker::check).toList();

        assertEquals(3, passedObservations.size());
    }
}