package com.njcn.gather.steady.checksquare.service.impl;

import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareCalculator;
import com.njcn.gather.steady.checksquare.component.SteadyChecksquareInfluxQueryComponent;
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemVO;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareSegmentVO;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatDetailVO;
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatSummaryVO;
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareService;
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 com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendSeriesFieldBO;
import com.njcn.gather.tool.adddata.component.AddDataTimeSlotCalculator;
import com.njcn.gather.tool.addledger.pojo.constant.AddLedgerConst;
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
import com.njcn.gather.tool.addledger.service.AddLedgerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 数据校验服务实现。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {

    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final String EMPTY_TEXT = "-";
    private static final int FLICKER_SHORT_INTERVAL_MINUTES = 10;
    private static final int FLICKER_LONG_INTERVAL_MINUTES = 120;

    private final SteadyTrendIndicatorCatalog indicatorCatalog;
    private final SteadyChecksquareInfluxQueryComponent influxQueryComponent;
    private final SteadyChecksquareCalculator calculator;
    private final AddDataTimeSlotCalculator timeSlotCalculator;
    private final AddLedgerService addLedgerService;

    @Override
    public SteadyChecksquareQueryVO query(SteadyChecksquareQueryParam param) {
        validateParam(param);
        String lineId = trimToNull(param.getLineId());
        LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
        LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
        if (startTime.isAfter(endTime)) {
            throw fail("开始时间不能大于结束时间");
        }
        AddLedgerLinePathVO linePath = requireLinePath(lineId);
        int intervalMinutes = resolveIntervalMinutes(linePath);
        SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO();
        result.setLineId(lineId);
        result.setLineName(trimToNull(linePath.getLineName()) == null ? EMPTY_TEXT : linePath.getLineName());
        result.setTimeStart(param.getTimeStart());
        result.setTimeEnd(param.getTimeEnd());
        result.setIntervalMinutes(intervalMinutes);

        long startMillis = System.currentTimeMillis();
        List<String> indicatorCodes = normalizeTextList(param.getIndicatorCodes());
        List<Integer> harmonicOrders = normalizeHarmonicOrders(param.getHarmonicOrders());
        log.info("数据校验查询开始，lineId={}，indicatorCount={}，timeStart={}，timeEnd={}，intervalMinutes={}",
                lineId, indicatorCodes.size(), startTime, endTime, intervalMinutes);
        for (String indicatorCode : indicatorCodes) {
            SteadyTrendIndicatorDefinitionBO indicator = requireIndicator(indicatorCode);
            int itemIntervalMinutes = resolveIndicatorIntervalMinutes(indicator, intervalMinutes);
            List<LocalDateTime> itemSlots = timeSlotCalculator.buildTimeSlots(startTime, endTime, itemIntervalMinutes);
            result.getItems().addAll(buildIndicatorItems(lineId, indicator, harmonicOrders, startTime, endTime, itemSlots, itemIntervalMinutes));
        }
        log.info("数据校验查询结束，lineId={}，itemCount={}，costMs={}", lineId, result.getItems().size(), System.currentTimeMillis() - startMillis);
        return result;
    }

    private List<SteadyChecksquareItemVO> buildIndicatorItems(String lineId, SteadyTrendIndicatorDefinitionBO indicator,
                                                              List<Integer> harmonicOrders,
                                                              LocalDateTime startTime, LocalDateTime endTime,
                                                              List<LocalDateTime> slots, int intervalMinutes) {
        List<SteadyChecksquareItemVO> result = new ArrayList<SteadyChecksquareItemVO>();
        if (Boolean.TRUE.equals(indicator.getHarmonic())) {
            for (Integer order : requireValidHarmonicOrders(indicator, harmonicOrders)) {
                result.add(buildItem(lineId, indicator, order, startTime, endTime, slots, intervalMinutes));
            }
            return result;
        }
        result.add(buildItem(lineId, indicator, null, startTime, endTime, slots, intervalMinutes));
        return result;
    }

    private SteadyChecksquareItemVO buildItem(String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder,
                                              LocalDateTime startTime, LocalDateTime endTime,
                                              List<LocalDateTime> slots, int intervalMinutes) {
        SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
        item.setItemKey(buildItemKey(lineId, indicator, harmonicOrder));
        item.setIndicatorCode(indicator.getIndicatorCode());
        item.setIndicatorName(indicator.getName());
        item.setHarmonicOrder(harmonicOrder);
        item.setIntervalMinutes(intervalMinutes);

        int totalExpected = 0;
        int totalActual = 0;
        int maxContinuousMissingMinutes = 0;
        boolean hasData = false;
        for (String statType : indicator.getSupportStats()) {
            Set<LocalDateTime> actualSlots = queryMergedActualSlots(lineId, indicator, harmonicOrder, statType, startTime, endTime, intervalMinutes);
            Set<LocalDateTime> effectiveActualSlots = retainExpectedSlots(slots, actualSlots);
            List<SteadyChecksquareSegmentVO> segments = calculator.buildSegments(slots, effectiveActualSlots, intervalMinutes);
            SteadyChecksquareStatSummaryVO summary = buildSummary(statType, slots.size(), effectiveActualSlots.size(), segments);
            SteadyChecksquareStatDetailVO detail = buildDetail(statType, segments);
            item.getStatSummaries().add(summary);
            item.getStatDetails().add(detail);
            totalExpected += summary.getExpectedPointCount();
            totalActual += summary.getActualPointCount();
            maxContinuousMissingMinutes = Math.max(maxContinuousMissingMinutes, summary.getMaxContinuousMissingMinutes());
            hasData = hasData || Boolean.TRUE.equals(summary.getHasData());
        }

        item.setHasData(hasData);
        item.setExpectedPointCount(totalExpected);
        item.setActualPointCount(totalActual);
        item.setMissingPointCount(Math.max(0, totalExpected - totalActual));
        item.setMissingRate(calculateRate(item.getMissingPointCount(), totalExpected));
        item.setMissingRateText(formatRateText(item.getMissingRate()));
        item.setMaxContinuousMissingMinutes(maxContinuousMissingMinutes);
        return item;
    }

    private Set<LocalDateTime> queryMergedActualSlots(String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder,
                                                      String statType, LocalDateTime startTime, LocalDateTime endTime,
                                                      int intervalMinutes) {
        Set<LocalDateTime> result = new HashSet<LocalDateTime>();
        for (String phase : indicator.getPhaseCodes()) {
            SteadyTrendResolvedFieldBO field = buildResolvedField(lineId, indicator, harmonicOrder, phase, statType);
            result.addAll(influxQueryComponent.queryExistingSlots(field, startTime, endTime, intervalMinutes));
        }
        return result;
    }

    private Set<LocalDateTime> retainExpectedSlots(List<LocalDateTime> slots, Set<LocalDateTime> actualSlots) {
        Set<LocalDateTime> result = new HashSet<LocalDateTime>();
        if (slots == null || actualSlots == null || actualSlots.isEmpty()) {
            return result;
        }
        for (LocalDateTime slot : slots) {
            if (actualSlots.contains(slot)) {
                result.add(slot);
            }
        }
        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(resolveField(indicator, harmonicOrder));
        field.setLineId(lineId);
        field.setIndicatorCode(indicator.getIndicatorCode());
        field.setIndicatorName(indicator.getName());
        field.setPhase(phase);
        field.setStatType(statType);
        field.setUnit(indicator.getUnit());
        return field;
    }

    private String resolveField(SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder) {
        if (Boolean.TRUE.equals(indicator.getHarmonic())) {
            return indicator.getHarmonicFieldPrefix() + "_" + harmonicOrder;
        }
        List<SteadyTrendSeriesFieldBO> fields = indicator.getSeriesFields();
        if (fields == null || fields.isEmpty()) {
            throw fail("稳态指标不支持：" + indicator.getIndicatorCode());
        }
        return fields.get(0).getField();
    }

    private SteadyChecksquareStatSummaryVO buildSummary(String statType, int expectedCount, int actualCount,
                                                        List<SteadyChecksquareSegmentVO> segments) {
        SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO();
        summary.setStatType(statType);
        summary.setSupported(true);
        summary.setHasData(actualCount > 0);
        summary.setExpectedPointCount(expectedCount);
        summary.setActualPointCount(actualCount);
        summary.setMissingPointCount(Math.max(0, expectedCount - actualCount));
        summary.setMissingRate(calculateRate(summary.getMissingPointCount(), expectedCount));
        summary.setMissingRateText(formatRateText(summary.getMissingRate()));
        summary.setMaxContinuousMissingMinutes(calculator.maxContinuousMissingMinutes(segments));
        return summary;
    }

    private SteadyChecksquareStatDetailVO buildDetail(String statType, List<SteadyChecksquareSegmentVO> segments) {
        SteadyChecksquareStatDetailVO detail = new SteadyChecksquareStatDetailVO();
        detail.setStatType(statType);
        detail.setSupported(true);
        detail.setSegments(segments);
        return detail;
    }

    private String buildItemKey(String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder) {
        if (harmonicOrder == null) {
            return lineId + "|" + indicator.getIndicatorCode();
        }
        return lineId + "|" + indicator.getIndicatorCode() + "|" + harmonicOrder;
    }

    private void validateParam(SteadyChecksquareQueryParam param) {
        if (param == null) {
            throw fail("数据校验参数不能为空");
        }
        if (trimToNull(param.getLineId()) == null) {
            throw fail("监测点 ID 不能为空");
        }
        if (normalizeTextList(param.getIndicatorCodes()).isEmpty()) {
            throw fail("指标不能为空");
        }
        parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
        parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
    }

    private LocalDateTime parseRequiredTime(String time, String emptyMessage) {
        String text = trimToNull(time);
        if (text == null) {
            throw fail(emptyMessage);
        }
        try {
            return LocalDateTime.parse(text, TIME_FORMATTER);
        } catch (DateTimeParseException ex) {
            throw fail("时间格式不正确，仅支持 yyyy-MM-dd HH:mm:ss");
        }
    }

    private AddLedgerLinePathVO requireLinePath(String lineId) {
        Map<String, AddLedgerLinePathVO> linePathMap = addLedgerService.listLinePathByLineIds(Collections.singletonList(lineId));
        AddLedgerLinePathVO linePath = linePathMap.get(lineId);
        if (linePath == null) {
            throw fail("监测点不存在或不可用");
        }
        return linePath;
    }

    private int resolveIntervalMinutes(AddLedgerLinePathVO linePath) {
        Integer interval = linePath.getLineInterval();
        if (interval == null || interval <= 0) {
            return AddLedgerConst.LINE_INTERVAL_DEFAULT;
        }
        return interval;
    }

    private int resolveIndicatorIntervalMinutes(SteadyTrendIndicatorDefinitionBO indicator, int lineIntervalMinutes) {
        String indicatorCode = indicator == null ? null : indicator.getIndicatorCode();
        if ("FLUC".equals(indicatorCode) || "PST".equals(indicatorCode)) {
            return FLICKER_SHORT_INTERVAL_MINUTES;
        }
        if ("PLT".equals(indicatorCode)) {
            return FLICKER_LONG_INTERVAL_MINUTES;
        }
        return lineIntervalMinutes;
    }

    private SteadyTrendIndicatorDefinitionBO requireIndicator(String indicatorCode) {
        SteadyTrendIndicatorDefinitionBO indicator = indicatorCatalog.getIndicator(indicatorCode);
        if (indicator == null) {
            throw fail("稳态指标不支持：" + indicatorCode);
        }
        return indicator;
    }

    private BigDecimal calculateRate(int missingCount, int expectedCount) {
        if (expectedCount <= 0) {
            return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP);
        }
        return new BigDecimal(missingCount).divide(new BigDecimal(expectedCount), 6, RoundingMode.HALF_UP);
    }

    private String formatRateText(BigDecimal rate) {
        if (rate == null) {
            return null;
        }
        return rate.multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP).toPlainString() + "%";
    }

    private List<String> normalizeTextList(List<String> values) {
        if (values == null || values.isEmpty()) {
            return new ArrayList<String>();
        }
        Set<String> result = new LinkedHashSet<String>();
        for (String value : values) {
            String text = trimToNull(value);
            if (text != null) {
                result.add(text);
            }
        }
        return new ArrayList<String>(result);
    }

    private List<Integer> normalizeHarmonicOrders(List<Integer> values) {
        if (values == null || values.isEmpty()) {
            return new ArrayList<Integer>();
        }
        List<Integer> result = new ArrayList<Integer>();
        for (Integer value : values) {
            if (value != null && !result.contains(value)) {
                result.add(value);
            }
        }
        return result;
    }

    private List<Integer> requireValidHarmonicOrders(SteadyTrendIndicatorDefinitionBO indicator, List<Integer> harmonicOrders) {
        if (harmonicOrders == null || harmonicOrders.isEmpty()) {
            throw fail("谐波次数不能为空");
        }
        for (Integer order : harmonicOrders) {
            if (order < indicator.getHarmonicOrderStart() || order > indicator.getHarmonicOrderEnd()) {
                throw fail("谐波次数只能在 " + indicator.getHarmonicOrderStart() + " 到 " + indicator.getHarmonicOrderEnd() + " 之间");
            }
        }
        return harmonicOrders;
    }

    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    private BusinessException fail(String message) {
        return new BusinessException(CommonResponseEnum.FAIL, message);
    }
}
