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.pojo.bo.SteadyTrendIndicatorDefinitionBO;
import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 谐波偶次与局部奇次基线关系规则。
 */
@Component
@RequiredArgsConstructor
public class SteadyChecksquareHarmonicParityRuleComponent {

    private static final DateTimeFormatter OUTPUT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final BigDecimal THRESHOLD_MULTIPLIER = new BigDecimal("2");
    private static final BigDecimal EVEN_HARMONIC_DEADBAND_VALUE = new BigDecimal("0.1");
    private static final int MIN_ODD_REFERENCE_COUNT = 2;

    private final SteadyChecksquareInfluxQueryComponent influxQueryComponent;

    public SteadyChecksquareHarmonicParityRuleVO check(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
                                                       LocalDateTime startTime, LocalDateTime endTime,
                                                       int intervalMinutes) {
        SteadyChecksquareHarmonicParityRuleVO result = new SteadyChecksquareHarmonicParityRuleVO();
        if (!supportHarmonicParityRule(indicator)) {
            return result;
        }
        for (String statType : indicator.getSupportStats()) {
            for (String phase : indicator.getPhaseCodes()) {
                Map<Integer, Map<LocalDateTime, BigDecimal>> valueMap = queryOrderValueMap(lineId, indicator, phase,
                        statType, startTime, endTime, intervalMinutes);
                appendAbnormalDetails(result, phase, statType, indicator, valueMap);
            }
        }
        result.setAbnormalPointCount(result.getAbnormalDetails().size());
        result.setAbnormal(result.getAbnormalPointCount() > 0);
        return result;
    }

    private boolean supportHarmonicParityRule(SteadyTrendIndicatorDefinitionBO indicator) {
        return indicator != null && Boolean.TRUE.equals(indicator.getHarmonic())
                && indicator.getHarmonicOrderStart() != null && indicator.getHarmonicOrderEnd() != null;
    }

    private Map<Integer, Map<LocalDateTime, BigDecimal>> queryOrderValueMap(String lineId,
                                                                            SteadyTrendIndicatorDefinitionBO indicator,
                                                                            String phase, String statType,
                                                                            LocalDateTime startTime,
                                                                            LocalDateTime endTime,
                                                                            int intervalMinutes) {
        Map<Integer, Map<LocalDateTime, BigDecimal>> result = new LinkedHashMap<Integer, Map<LocalDateTime, BigDecimal>>();
        List<SteadyTrendResolvedFieldBO> fields = new ArrayList<SteadyTrendResolvedFieldBO>();
        for (int order = indicator.getHarmonicOrderStart(); order <= indicator.getHarmonicOrderEnd(); order++) {
            fields.add(buildResolvedField(lineId, indicator, order, phase, statType));
        }
        Map<String, List<SteadyChecksquareValuePointBO>> fieldValueMap =
                influxQueryComponent.queryValuePointMap(fields, startTime, endTime, intervalMinutes);
        if (fieldValueMap == null) {
            fieldValueMap = Collections.emptyMap();
        }
        for (int order = indicator.getHarmonicOrderStart(); order <= indicator.getHarmonicOrderEnd(); order++) {
            result.put(order, toValueMap(fieldValueMap.get(indicator.getHarmonicFieldPrefix() + "_" + order)));
        }
        return result;
    }

    private void appendAbnormalDetails(SteadyChecksquareHarmonicParityRuleVO result, String phase, String statType,
                                       SteadyTrendIndicatorDefinitionBO indicator,
                                       Map<Integer, Map<LocalDateTime, BigDecimal>> valueMap) {
        for (int order = firstEvenOrder(indicator.getHarmonicOrderStart()); order <= indicator.getHarmonicOrderEnd(); order += 2) {
            Map<LocalDateTime, BigDecimal> evenValues = valueMap.get(order);
            if (evenValues == null || evenValues.isEmpty()) {
                continue;
            }
            for (Map.Entry<LocalDateTime, BigDecimal> entry : evenValues.entrySet()) {
                appendAbnormalDetailIfNecessary(result, phase, statType, order, entry.getKey(), entry.getValue(), valueMap);
            }
        }
    }

    private void appendAbnormalDetailIfNecessary(SteadyChecksquareHarmonicParityRuleVO result, String phase,
                                                  String statType, int evenOrder, LocalDateTime time,
                                                  BigDecimal evenValue,
                                                  Map<Integer, Map<LocalDateTime, BigDecimal>> valueMap) {
        if (evenValue == null || evenValue.compareTo(EVEN_HARMONIC_DEADBAND_VALUE) <= 0) {
            return;
        }
        List<Integer> oddOrders = buildOddReferenceOrders(evenOrder);
        List<BigDecimal> oddValues = new ArrayList<BigDecimal>();
        List<Integer> effectiveOddOrders = new ArrayList<Integer>();
        for (Integer oddOrder : oddOrders) {
            Map<LocalDateTime, BigDecimal> values = valueMap.get(oddOrder);
            BigDecimal oddValue = values == null ? null : values.get(time);
            if (oddValue != null) {
                effectiveOddOrders.add(oddOrder);
                oddValues.add(oddValue);
            }
        }
        if (oddValues.size() < MIN_ODD_REFERENCE_COUNT) {
            return;
        }
        BigDecimal median = calculateMedian(oddValues);
        if (median == null || evenValue.compareTo(median.multiply(THRESHOLD_MULTIPLIER)) <= 0) {
            return;
        }
        result.getAbnormalDetails().add(buildDetail(time, phase, statType, evenOrder, evenValue,
                effectiveOddOrders, oddValues, median));
    }

    private SteadyChecksquareHarmonicParityDetailVO buildDetail(LocalDateTime time, String phase, String statType,
                                                                Integer evenOrder, BigDecimal evenValue,
                                                                List<Integer> oddOrders, List<BigDecimal> oddValues,
                                                                BigDecimal median) {
        SteadyChecksquareHarmonicParityDetailVO detail = new SteadyChecksquareHarmonicParityDetailVO();
        detail.setTime(OUTPUT_TIME_FORMATTER.format(time));
        detail.setPhase(phase);
        detail.setStatType(statType);
        detail.setEvenHarmonicOrder(evenOrder);
        detail.setEvenValue(evenValue);
        detail.setOddHarmonicOrders(new ArrayList<Integer>(oddOrders));
        detail.setOddValues(new ArrayList<BigDecimal>(oddValues));
        detail.setOddMedianValue(median);
        detail.setThresholdMultiplier(THRESHOLD_MULTIPLIER);
        return detail;
    }

    private List<Integer> buildOddReferenceOrders(int evenOrder) {
        List<Integer> result = new ArrayList<Integer>();
        result.add(evenOrder - 3);
        result.add(evenOrder - 1);
        result.add(evenOrder + 1);
        result.add(evenOrder + 3);
        return result;
    }

    private BigDecimal calculateMedian(List<BigDecimal> values) {
        if (values == null || values.isEmpty()) {
            return null;
        }
        List<BigDecimal> sorted = new ArrayList<BigDecimal>(values);
        Collections.sort(sorted, Comparator.naturalOrder());
        int middleIndex = sorted.size() / 2;
        if (sorted.size() % 2 == 1) {
            return sorted.get(middleIndex);
        }
        return sorted.get(middleIndex - 1).add(sorted.get(middleIndex)).divide(new BigDecimal("2"));
    }

    private int firstEvenOrder(int startOrder) {
        return startOrder % 2 == 0 ? startOrder : startOrder + 1;
    }

    private Map<LocalDateTime, BigDecimal> toValueMap(List<SteadyChecksquareValuePointBO> points) {
        Map<LocalDateTime, BigDecimal> result = new LinkedHashMap<LocalDateTime, BigDecimal>();
        if (points == null || points.isEmpty()) {
            return result;
        }
        for (SteadyChecksquareValuePointBO point : points) {
            if (point != null && point.getTime() != null && point.getValue() != null) {
                result.put(point.getTime(), point.getValue());
            }
        }
        return result;
    }

    private SteadyTrendResolvedFieldBO buildResolvedField(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
                                                          Integer harmonicOrder, String phase, String statType) {
        SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
        field.setMeasurement(indicator.getTableName());
        field.setField(indicator.getHarmonicFieldPrefix() + "_" + harmonicOrder);
        field.setLineId(lineId);
        field.setIndicatorCode(indicator.getIndicatorCode());
        field.setIndicatorName(indicator.getName());
        field.setPhase(phase);
        field.setStatType(statType);
        field.setUnit(indicator.getUnit());
        return field;
    }
}
