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.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

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

class ThresholdCheckerTest {

    private static final Sensor SOURCE = new Sensor(100L, 100000L, 10343L, 10L);

    @Test
    void check_size_1_INSTANT() {

        ThresholdChecker<Observation> checker = new ThresholdChecker<>(
                List.of(
                    new Threshold(SOURCE.id(), NotifyTrigger.Type.INSTANT, true, AttributeName.VAL, List.of(new ThresholdRule("gt", 10.0)))
                ),
                Validator.<Observation>create()
                        .addMapping(AttributeName.VAL, o -> o::value),
                (report) -> {
                    assertEquals(SOURCE.id(), report.sourceId());
                    assertEquals(1, report.violatedData().length);
                    ValidationResult valResult = report.violatedData()[0];
                    assertEquals(AttributeName.VAL, valResult.attribute());
                    assertEquals(25.3, valResult.validatedValue());
                    assertEquals(1, valResult.records().size());
                }
        );

        long count = Stream.of(
                Observation.of(SOURCE, 25.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(SOURCE.id(), NotifyTrigger.Type.ON_CHANGE, true, AttributeName.VAL, List.of(new ThresholdRule("gt", 10.0)))
                ),
                Validator.<Observation>create()
                        .addMapping(AttributeName.VAL, o -> o::value),
                (report) -> {
                    assertEquals(SOURCE.id(), report.sourceId());
                    assertEquals(1, report.violatedData().length);
                    ValidationResult valResult = report.violatedData()[0];
                    assertEquals(AttributeName.VAL, valResult.attribute());
                    assertEquals(25.3, valResult.validatedValue());
                    assertEquals(1, valResult.records().size());
                }
        );

        long count = Stream.of(
                Observation.of(SOURCE, 9.9, OffsetDateTime.now()),
                Observation.of(SOURCE, 25.3, OffsetDateTime.now()),
                Observation.of(SOURCE, 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(SOURCE.id(), NotifyTrigger.Type.ON_CHANGE, true, AttributeName.VAL, List.of(new ThresholdRule("gt", 10.0)))
                ),
                Validator.<Observation>create()
                        .addMapping(AttributeName.VAL, o -> o::value),
                (report) -> {
                    assertEquals(SOURCE.id(), report.sourceId());
                    assertEquals(1, report.violatedData().length);
                    ValidationResult valResult = report.violatedData()[0];
                    assertEquals(AttributeName.VAL, valResult.attribute());

                    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());
                    }
                }
        );

        long count = Stream.of(
                Observation.of(SOURCE, 9.9, OffsetDateTime.now()),
                Observation.of(SOURCE, 25.3, OffsetDateTime.now()),
                Observation.of(SOURCE, 30.5, OffsetDateTime.now()),
                Observation.of(SOURCE, 8.5, OffsetDateTime.now())
        ).filter(checker.check()).count();
        assertEquals(4, count);
    }
    
    @Test
    void check_doubleStatistics_INSTANT() {
        long sourceId = SOURCE.groupId();

        ThresholdChecker<DoubleStatistics> checker = new ThresholdChecker<>(
                List.of(
                        new Threshold(sourceId, NotifyTrigger.Type.INSTANT, true, AttributeName.MIN, List.of(new ThresholdRule("lt", 0.0))),
                        new Threshold(sourceId, NotifyTrigger.Type.INSTANT, true, AttributeName.MAX, List.of(new ThresholdRule("gt", 5.0)))
                ),
                Validator.<DoubleStatistics>create()
                        .addMapping(AttributeName.MIN, s -> s::min)
                        .addMapping(AttributeName.MAX, s -> s::max)
                        .addMapping(AttributeName.AVG, s -> s::average),
                (report) -> {
                    assertEquals(sourceId, report.sourceId());
                    assertEquals(2, report.violatedData().length);
                    ValidationResult[] violatedData = report.violatedData();
                    ValidationResult minResult = violatedData[0].attribute().equals(AttributeName.MIN) ? violatedData[0] : violatedData[1];
                    ValidationResult maxResult = violatedData[0].attribute().equals(AttributeName.MAX) ? violatedData[0] : violatedData[1];

                    assertEquals(AttributeName.MIN, minResult.attribute());
                    assertEquals(-1.5, minResult.validatedValue());
                    assertEquals(1, minResult.records().size());

                    assertEquals(AttributeName.MAX, maxResult.attribute());
                    assertEquals(5.2, maxResult.validatedValue());
                    assertEquals(1, maxResult.records().size());
                }
        );

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


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