package cz.senslog.analytics.utils;

import cz.senslog.analytics.domain.*;
import cz.senslog.analytics.utils.validator.NotifyTrigger;
import cz.senslog.analytics.utils.validator.ThresholdChecker;
import cz.senslog.analytics.utils.validator.Validator;
import org.junit.jupiter.api.Test;

import java.time.OffsetDateTime;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;

class ThresholdCheckerTest {

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

    @Test
    void check_size_1_INSTANT() {

        ThresholdChecker<Observation> checker = new ThresholdChecker<>(
                List.of(
                    new Threshold(1, SENSOR.id(), NotifyTrigger.Mode.INSTANT, AttributeType.VAL, true, false, List.of(
                            new ThresholdRule(ThresholdMode.GT, 10.0)
                    ))
                ),
                Validator.<Observation>create()
                        .addMapping(AttributeType.VAL, o -> o::value),
                (report) -> {
                    assertEquals(SENSOR.id(), report.datasourceId());
                    assertEquals(1, report.violatedData().length);
                    ValidationResult valResult = report.violatedData()[0];
                    assertEquals(AttributeType.VAL, valResult.attributeType());
                    assertEquals(23.3, valResult.validatedValue());
                    assertEquals(1, valResult.records().size());
                },
                true
        );

        long count = Stream.of(
                Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 23.3, OffsetDateTime.now()))
        ).filter(checker::check).count();
        assertEquals(1, count);
    }

    @Test
    void check_size_3_ON_CHANGE() {

        ThresholdChecker<Observation> checker = new ThresholdChecker<>(
                List.of(
                        new Threshold(1, SENSOR.id(), NotifyTrigger.Mode.ON_CHANGE, AttributeType.VAL, true, false, List.of(
                                new ThresholdRule(ThresholdMode.GT, 10.0)
                        ))
                ),
                Validator.<Observation>create()
                        .addMapping(AttributeType.VAL, o -> o::value),
                (report) -> {
                    assertEquals(SENSOR.id(), report.datasourceId());
                    assertEquals(1, report.violatedData().length);
                    ValidationResult valResult = report.violatedData()[0];
                    assertEquals(AttributeType.VAL, valResult.attributeType());
                    assertEquals(25.3, valResult.validatedValue());
                    assertEquals(1, valResult.records().size());
                },
                true
        );

        long count = Stream.of(
                Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(),9.9, OffsetDateTime.now())),
                Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(),25.3, OffsetDateTime.now())),
                Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(),30.5, OffsetDateTime.now()))
        ).filter(checker::check).count();
        assertEquals(3, count);
    }

    @Test
    void check_size_4_ON_CHANGE() {
        AtomicInteger reportStep = new AtomicInteger(1);
        ThresholdChecker<Observation> checker = new ThresholdChecker<>(
                List.of(
                        new Threshold(1, SENSOR.id(), NotifyTrigger.Mode.ON_CHANGE, AttributeType.VAL, true, false, List.of(
                                new ThresholdRule(ThresholdMode.GT, 10.0)
                        ))
                ),
                Validator.<Observation>create()
                        .addMapping(AttributeType.VAL, o -> o::value),
                (report) -> {
                    assertEquals(SENSOR.id(), report.datasourceId());
                    assertEquals(1, report.violatedData().length);
                    ValidationResult valResult = report.violatedData()[0];
                    assertEquals(AttributeType.VAL, valResult.attributeType());

                    if (reportStep.compareAndSet(1, 2)) {
                        assertEquals(25.3, valResult.validatedValue());
                        assertTrue(valResult.isNotValid());
                        assertEquals(1, valResult.records().size());

                    } else if (reportStep.compareAndSet(2, 3)) {
                        assertEquals(8.5, valResult.validatedValue());
                        assertTrue(valResult.isValid());
                        assertEquals(0, valResult.records().size());
                    }
                },
                true
        );

        long count = Stream.of(
                Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 9.9, OffsetDateTime.now())),
                Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 25.3, OffsetDateTime.now())),
                Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(),30.5, OffsetDateTime.now())),
                Observation.of(SENSOR, RawObservation.of(SENSOR.unitId(), SENSOR.sensorId(), 8.5, OffsetDateTime.now()))
        ).filter(checker::check).count();
        assertEquals(4, count);
    }
    
    @Test
    void check_doubleStatistics_INSTANT() {
        long datasourceId = SENSOR.id();

        ThresholdChecker<DoubleStatistics> checker = new ThresholdChecker<>(
                List.of(
                        new Threshold(1, -1, NotifyTrigger.Mode.INSTANT, AttributeType.MIN, true, false, List.of(new ThresholdRule(ThresholdMode.LT, 0.0))),
                        new Threshold(1, -1, NotifyTrigger.Mode.INSTANT, AttributeType.MAX, true, false, List.of(new ThresholdRule(ThresholdMode.GT, 5.0)))
                ),
                Validator.<DoubleStatistics>create()
                        .addMapping(AttributeType.MIN, s -> s::min)
                        .addMapping(AttributeType.MAX, s -> s::max)
                        .addMapping(AttributeType.AVG, s -> s::average),
                (report) -> {
                    assertEquals(datasourceId, report.datasourceId());
                    assertEquals(2, report.violatedData().length);
                    ValidationResult[] violatedData = report.violatedData();
                    ValidationResult minResult = violatedData[0].attributeType().equals(AttributeType.MIN) ? violatedData[0] : violatedData[1];
                    ValidationResult maxResult = violatedData[0].attributeType().equals(AttributeType.MAX) ? violatedData[0] : violatedData[1];

                    assertEquals(AttributeType.MIN, minResult.attributeType());
                    assertEquals(-1.5, minResult.validatedValue());
                    assertEquals(1, minResult.records().size());

                    assertEquals(AttributeType.MAX, maxResult.attributeType());
                    assertEquals(5.2, maxResult.validatedValue());
                    assertEquals(1, maxResult.records().size());
                },
                true
        );

        DoubleStatistics ds = DoubleStatistics.init(datasourceId, OffsetDateTime.now());
        ds.accept(datasourceId, -1.5);
        ds.accept(datasourceId, 3.3);
        ds.accept(datasourceId, 5.2);


        long count = Stream.of(ds)
                .filter(checker::check).count();
        assertEquals(1, count);
    }
}