package com.njcn.gather.steady.checksquare.component;

import com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO;
import com.njcn.gather.steady.datavie.component.SteadyTrendIndicatorCatalog;
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO;
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * 谐波奇偶关系规则测试。
 */
class SteadyChecksquareHarmonicParityRuleComponentTest {

    @Test
    void shouldRecordAbnormalWhenEvenHarmonicExceedsOddMedianThreshold() {
        SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
        SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent);
        LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
        when(influxQueryComponent.queryValuePointMap(any(),
                any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
                .thenAnswer(invocation -> {
                    Map<String, List<SteadyChecksquareValuePointBO>> values = emptyBatchResult(invocation.getArgument(0));
                    putPoint(values, "v_3", time, "10");
                    putPoint(values, "v_4", time, "31");
                    putPoint(values, "v_5", time, "12");
                    putPoint(values, "v_7", time, "14");
                    return values;
                });
        SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC");

        SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator,
                time, time, 1);

        Assertions.assertEquals(Boolean.TRUE, result.getAbnormal());
        Assertions.assertEquals(Integer.valueOf(1), result.getAbnormalPointCount());
        SteadyChecksquareHarmonicParityDetailVO detail = result.getAbnormalDetails().get(0);
        Assertions.assertEquals("2026-05-01 00:00:00", detail.getTime());
        Assertions.assertEquals("A", detail.getPhase());
        Assertions.assertEquals("AVG", detail.getStatType());
        Assertions.assertEquals(Integer.valueOf(4), detail.getEvenHarmonicOrder());
        Assertions.assertEquals(new BigDecimal("31"), detail.getEvenValue());
        Assertions.assertEquals(Arrays.asList(3, 5, 7), detail.getOddHarmonicOrders());
        Assertions.assertEquals(new BigDecimal("12"), detail.getOddMedianValue());
        Assertions.assertEquals(new BigDecimal("2"), detail.getThresholdMultiplier());
    }

    @Test
    void shouldSkipWhenOddReferenceCountLessThanTwo() {
        SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
        SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent);
        LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
        when(influxQueryComponent.queryValuePointMap(any(),
                any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
                .thenAnswer(invocation -> {
                    Map<String, List<SteadyChecksquareValuePointBO>> values = emptyBatchResult(invocation.getArgument(0));
                    putPoint(values, "v_2", time, "50");
                    putPoint(values, "v_3", time, "10");
                    return values;
                });
        SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC");

        SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator,
                time, time, 1);

        Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
        Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
        Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
    }

    @Test
    void shouldSkipEvenHarmonicWhenValueNotGreaterThanDeadband() {
        SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
        SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent);
        LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
        when(influxQueryComponent.queryValuePointMap(any(),
                any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
                .thenAnswer(invocation -> {
                    Map<String, List<SteadyChecksquareValuePointBO>> values = emptyBatchResult(invocation.getArgument(0));
                    putPoint(values, "v_3", time, "0.01");
                    putPoint(values, "v_4", time, "0.10");
                    putPoint(values, "v_5", time, "0.02");
                    return values;
                });
        SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_HARMONIC");

        SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator,
                time, time, 1);

        Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
        Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
        Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
    }

    @Test
    void shouldSkipNonHarmonicIndicator() {
        SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
        SteadyChecksquareHarmonicParityRuleComponent component = new SteadyChecksquareHarmonicParityRuleComponent(influxQueryComponent);
        SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorCatalog().getIndicator("V_RMS");

        SteadyChecksquareHarmonicParityRuleVO result = component.check("line-001", indicator,
                LocalDateTime.of(2026, 5, 1, 0, 0),
                LocalDateTime.of(2026, 5, 1, 0, 1), 1);

        Assertions.assertEquals(Boolean.FALSE, result.getAbnormal());
        Assertions.assertEquals(Integer.valueOf(0), result.getAbnormalPointCount());
        Assertions.assertTrue(result.getAbnormalDetails().isEmpty());
    }

    private SteadyChecksquareValuePointBO point(LocalDateTime time, String value) {
        SteadyChecksquareValuePointBO point = new SteadyChecksquareValuePointBO();
        point.setTime(time);
        point.setValue(new BigDecimal(value));
        return point;
    }

    private Map<String, List<SteadyChecksquareValuePointBO>> emptyBatchResult(List<SteadyTrendResolvedFieldBO> fields) {
        Map<String, List<SteadyChecksquareValuePointBO>> result =
                new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>();
        for (SteadyTrendResolvedFieldBO field : fields) {
            result.put(field.getField(), Collections.<SteadyChecksquareValuePointBO>emptyList());
        }
        return result;
    }

    private void putPoint(Map<String, List<SteadyChecksquareValuePointBO>> values, String field,
                          LocalDateTime time, String value) {
        if (values.containsKey(field)) {
            values.put(field, Collections.singletonList(point(time, value)));
        }
    }
}
