package com.aucma.report.service.impl;

import com.aucma.base.domain.BaseDeviceParam;
import com.aucma.base.domain.BaseDeviceParamVal;
import com.aucma.base.mapper.BaseDeviceParamMapper;
import com.aucma.base.service.IDeviceParamReadFacade;
import com.aucma.report.domain.DeviceParamReportQuery;
import com.aucma.report.domain.vo.DeviceParamAnomalyVo;
import com.aucma.report.domain.vo.DeviceParamOptionVo;
import com.aucma.report.domain.vo.DeviceParamSpcPointVo;
import com.aucma.report.domain.vo.DeviceParamSpcReportVo;
import com.aucma.report.domain.vo.DeviceParamSwitchTraceVo;
import com.aucma.report.mapper.DeviceParamAnalysisMapper;
import com.aucma.report.service.IDeviceParamAnalysisService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 设备参数分析Service实现
 */
@Service
public class DeviceParamAnalysisServiceImpl implements IDeviceParamAnalysisService {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final List<String> SWITCH_PARAM_NAMES = Arrays.asList(
            "机台状态-模具数据名称", "机台状态-物料描述", "机台状态-产品描述", "机台状态-产品描述1");
    private static final List<String> STATUS_PARAM_NAMES = Arrays.asList(
            "机台状态-三色灯机器运行", "机台状态-三色灯机器暂停", "机台状态-三色灯机器待机", "机台状态-三色灯机器报警");
    private static final String OUTPUT_PARAM_NAME = "机台状态-实际产出数量";

    @Autowired
    private DeviceParamAnalysisMapper deviceParamAnalysisMapper;

    @Autowired
    private BaseDeviceParamMapper baseDeviceParamMapper;

    @Autowired
    private IDeviceParamReadFacade deviceParamReadFacade;

    @Override
    public List<DeviceParamOptionVo> selectParamOptions(DeviceParamReportQuery query) {
        normalizeQuery(query);
        return deviceParamAnalysisMapper.selectParamOptions(query);
    }

    @Override
    public List<DeviceParamAnomalyVo> selectParamAnomalyList(DeviceParamReportQuery query) {
        normalizeQuery(query);
        Date beginTime = parseDate(query.getStartTime());
        Date endTime = parseDate(query.getEndTime());
        if (beginTime == null || endTime == null) {
            return Collections.emptyList();
        }

        List<BaseDeviceParam> alertParams = loadAlertParams(query.getDeviceCode(), query.getParamCode());
        if (alertParams.isEmpty()) {
            return Collections.emptyList();
        }

        Map<String, List<BaseDeviceParam>> alertParamsByDevice = new HashMap<String, List<BaseDeviceParam>>();
        for (BaseDeviceParam alertParam : alertParams) {
            if (StringUtils.isBlank(alertParam.getDeviceCode())) {
                continue;
            }
            if (!alertParamsByDevice.containsKey(alertParam.getDeviceCode())) {
                alertParamsByDevice.put(alertParam.getDeviceCode(), new ArrayList<BaseDeviceParam>());
            }
            alertParamsByDevice.get(alertParam.getDeviceCode()).add(alertParam);
        }

        List<DeviceParamAnomalyVo> resultList = new ArrayList<DeviceParamAnomalyVo>();
        for (Map.Entry<String, List<BaseDeviceParam>> entry : alertParamsByDevice.entrySet()) {
            List<BaseDeviceParamVal> historyList = deviceParamReadFacade.listHistoryParamValues(
                    entry.getKey(), null, null, beginTime, endTime, null);
            if (historyList.isEmpty()) {
                continue;
            }
            historyList.sort(Comparator.comparing(this::resolveEventTime));
            Map<String, BaseDeviceParam> alertConfigMap = buildAlertConfigMap(entry.getValue());
            Map<String, List<NumericSample>> sampleMap = new HashMap<String, List<NumericSample>>();
            for (BaseDeviceParamVal history : historyList) {
                BaseDeviceParam config = alertConfigMap.get(buildParamKey(history.getParamCode(), history.getParamName()));
                if (config == null) {
                    continue;
                }
                Double numericValue = parseNumeric(history.getParamValue());
                if (numericValue == null) {
                    continue;
                }
                String paramKey = buildParamKey(config.getParamCode(), config.getParamName());
                if (!sampleMap.containsKey(paramKey)) {
                    sampleMap.put(paramKey, new ArrayList<NumericSample>());
                }
                sampleMap.get(paramKey).add(new NumericSample(history, numericValue.doubleValue()));
            }
            for (BaseDeviceParam alertParam : entry.getValue()) {
                DeviceParamAnomalyVo anomalyVo = buildAnomalyVo(
                        alertParam,
                        sampleMap.get(buildParamKey(alertParam.getParamCode(), alertParam.getParamName()))
                );
                if (anomalyVo != null) {
                    resultList.add(anomalyVo);
                }
            }
        }

        resultList.sort(Comparator
                .comparing(DeviceParamAnomalyVo::getOverLimitCount, Comparator.nullsLast(Comparator.reverseOrder()))
                .thenComparing(DeviceParamAnomalyVo::getAbnormalDurationMinutes, Comparator.nullsLast(Comparator.reverseOrder()))
                .thenComparing(DeviceParamAnomalyVo::getLastAbnormalTime, Comparator.nullsLast(Comparator.reverseOrder())));
        return resultList;
    }

    @Override
    public DeviceParamSpcReportVo getSpcReport(DeviceParamReportQuery query) {
        normalizeQuery(query);
        BaseDeviceParam config = loadSingleParam(query.getDeviceCode(), query.getParamCode());
        List<BaseDeviceParamVal> historyList = deviceParamReadFacade.listHistoryParamValues(
                query.getDeviceCode(), query.getParamCode(), null, parseDate(query.getStartTime()), parseDate(query.getEndTime()), 1500);
        List<DeviceParamSpcPointVo> points = buildSpcPoints(historyList, config);
        DeviceParamSpcReportVo reportVo = new DeviceParamSpcReportVo();
        reportVo.setPoints(points != null ? points : new ArrayList<DeviceParamSpcPointVo>());
        if (points == null || points.isEmpty()) {
            reportVo.setSampleSize(0);
            reportVo.setOutOfControlCount(0);
            return reportVo;
        }

        DeviceParamSpcPointVo firstPoint = points.get(0);
        reportVo.setDeviceCode(firstPoint.getDeviceCode());
        reportVo.setDeviceName(firstPoint.getDeviceName());
        reportVo.setParamCode(firstPoint.getParamCode());
        reportVo.setParamName(firstPoint.getParamName());
        reportVo.setUpperLimit(firstPoint.getUpperLimit());
        reportVo.setLowerLimit(firstPoint.getLowerLimit());
        reportVo.setSampleSize(points.size());

        List<Double> values = new ArrayList<Double>(points.size());
        double sum = 0D;
        double max = Double.NEGATIVE_INFINITY;
        double min = Double.POSITIVE_INFINITY;
        for (DeviceParamSpcPointVo point : points) {
            double value = point.getParamValue();
            values.add(value);
            sum += value;
            max = Math.max(max, value);
            min = Math.min(min, value);
        }

        double mean = sum / values.size();
        double variance = 0D;
        for (Double value : values) {
            variance += Math.pow(value - mean, 2);
        }
        double stdDev = values.size() > 1 ? Math.sqrt(variance / values.size()) : 0D;
        double cl = mean;
        double ucl = mean + 3 * stdDev;
        double lcl = mean - 3 * stdDev;
        int outOfControlCount = 0;
        for (Double value : values) {
            if (value > ucl || value < lcl) {
                outOfControlCount++;
            }
        }

        reportVo.setMean(scale(mean));
        reportVo.setMaxValue(scale(max));
        reportVo.setMinValue(scale(min));
        reportVo.setRangeValue(scale(max - min));
        reportVo.setStdDev(scale(stdDev));
        reportVo.setCoefficientVariation(mean == 0D ? 0D : scale(stdDev / mean * 100));
        reportVo.setCl(scale(cl));
        reportVo.setUcl(scale(ucl));
        reportVo.setLcl(scale(lcl));
        reportVo.setCpk(calcCpk(mean, stdDev, firstPoint.getUpperLimit(), firstPoint.getLowerLimit()));
        reportVo.setOutOfControlCount(outOfControlCount);
        return reportVo;
    }

    @Override
    public List<DeviceParamSwitchTraceVo> selectSwitchTraceList(DeviceParamReportQuery query) {
        normalizeQuery(query);
        Date beginTime = parseDate(query.getStartTime());
        Date endTime = parseDate(query.getEndTime());
        if (beginTime == null || endTime == null) {
            return Collections.emptyList();
        }

        int observeMinutes = query.getObserveMinutes() == null ? 30 : query.getObserveMinutes();
        Date observeEndTime = addMinutes(endTime, observeMinutes);
        List<BaseDeviceParam> switchParams = loadSwitchParams(query.getDeviceCode(), query.getSwitchType());
        if (switchParams.isEmpty()) {
            return Collections.emptyList();
        }

        Map<String, List<BaseDeviceParam>> switchParamsByDevice = new HashMap<String, List<BaseDeviceParam>>();
        for (BaseDeviceParam switchParam : switchParams) {
            if (StringUtils.isBlank(switchParam.getDeviceCode())) {
                continue;
            }
            if (!switchParamsByDevice.containsKey(switchParam.getDeviceCode())) {
                switchParamsByDevice.put(switchParam.getDeviceCode(), new ArrayList<BaseDeviceParam>());
            }
            switchParamsByDevice.get(switchParam.getDeviceCode()).add(switchParam);
        }

        Map<String, List<BaseDeviceParam>> alertParamsByDevice = loadAlertParamsByDevice(query.getDeviceCode());
        List<DeviceParamSwitchTraceVo> resultList = new ArrayList<DeviceParamSwitchTraceVo>();
        for (Map.Entry<String, List<BaseDeviceParam>> entry : switchParamsByDevice.entrySet()) {
            appendSwitchTraceRows(resultList, entry.getKey(), entry.getValue(), alertParamsByDevice.get(entry.getKey()), beginTime, endTime, observeEndTime, observeMinutes, query.getSwitchType());
        }
        resultList.sort(Comparator.comparing(DeviceParamSwitchTraceVo::getChangeTime, Comparator.nullsLast(Comparator.reverseOrder())));
        return resultList;
    }

    private void normalizeQuery(DeviceParamReportQuery query) {
        if (query == null) {
            return;
        }
        query.setScene(normalizeScene(query.getScene()));
        query.setDeviceCode(trimToNull(query.getDeviceCode()));
        query.setParamCode(trimToNull(query.getParamCode()));
        query.setStartTime(normalizeDateTime(query.getStartTime()));
        query.setEndTime(normalizeDateTime(query.getEndTime()));
        query.setSwitchType(normalizeSwitchType(query.getSwitchType()));
        Integer observeMinutes = query.getObserveMinutes();
        if (observeMinutes == null) {
            query.setObserveMinutes(30);
        } else if (observeMinutes < 1) {
            query.setObserveMinutes(1);
        } else if (observeMinutes > 1440) {
            query.setObserveMinutes(1440);
        }
    }

    private String normalizeScene(String scene) {
        if ("anomaly".equalsIgnoreCase(scene)) {
            return "anomaly";
        }
        if ("switch".equalsIgnoreCase(scene)) {
            return "switch";
        }
        return "spc";
    }

    private String normalizeSwitchType(String switchType) {
        if (StringUtils.isBlank(switchType)) {
            return "ALL";
        }
        String normalized = switchType.trim().toUpperCase();
        if ("MOLD".equals(normalized) || "MATERIAL".equals(normalized) || "PRODUCT".equals(normalized)) {
            return normalized;
        }
        return "ALL";
    }

    private String normalizeDateTime(String dateTime) {
        if (StringUtils.isBlank(dateTime)) {
            return null;
        }
        try {
            LocalDateTime parsed = LocalDateTime.parse(dateTime.trim(), DATE_TIME_FORMATTER);
            return DATE_TIME_FORMATTER.format(parsed);
        } catch (DateTimeParseException ex) {
            throw new IllegalArgumentException("时间格式必须为yyyy-MM-dd HH:mm:ss");
        }
    }

    private Double calcCpk(double mean, double stdDev, Double upperLimit, Double lowerLimit) {
        if (stdDev <= 0D) {
            return 0D;
        }
        Double cpu = upperLimit == null ? null : (upperLimit - mean) / (3 * stdDev);
        Double cpl = lowerLimit == null ? null : (mean - lowerLimit) / (3 * stdDev);
        if (cpu == null && cpl == null) {
            return null;
        }
        if (cpu == null) {
            return scale(cpl);
        }
        if (cpl == null) {
            return scale(cpu);
        }
        return scale(Math.min(cpu, cpl));
    }

    private Double scale(Double value) {
        if (value == null) {
            return null;
        }
        return BigDecimal.valueOf(value).setScale(4, RoundingMode.HALF_UP).doubleValue();
    }

    private List<BaseDeviceParam> loadAlertParams(String deviceCode, String paramCode) {
        BaseDeviceParam query = new BaseDeviceParam();
        query.setDeviceCode(trimToNull(deviceCode));
        query.setParamCode(trimToNull(paramCode));
        query.setIsFlag(1L);
        query.setAlertEnabled("1");
        List<BaseDeviceParam> paramList = baseDeviceParamMapper.selectBaseDeviceParamList(query);
        if (paramList == null || paramList.isEmpty()) {
            return Collections.emptyList();
        }
        List<BaseDeviceParam> resultList = new ArrayList<BaseDeviceParam>();
        for (BaseDeviceParam param : paramList) {
            if (!isNumericParam(param)) {
                continue;
            }
            if (param.getUpperLimit() == null && param.getLowerLimit() == null) {
                continue;
            }
            resultList.add(param);
        }
        return resultList;
    }

    private Map<String, List<BaseDeviceParam>> loadAlertParamsByDevice(String deviceCode) {
        List<BaseDeviceParam> alertParams = loadAlertParams(deviceCode, null);
        Map<String, List<BaseDeviceParam>> resultMap = new HashMap<String, List<BaseDeviceParam>>();
        for (BaseDeviceParam alertParam : alertParams) {
            if (StringUtils.isBlank(alertParam.getDeviceCode())) {
                continue;
            }
            if (!resultMap.containsKey(alertParam.getDeviceCode())) {
                resultMap.put(alertParam.getDeviceCode(), new ArrayList<BaseDeviceParam>());
            }
            resultMap.get(alertParam.getDeviceCode()).add(alertParam);
        }
        return resultMap;
    }

    private BaseDeviceParam loadSingleParam(String deviceCode, String paramCode) {
        BaseDeviceParam query = new BaseDeviceParam();
        query.setDeviceCode(trimToNull(deviceCode));
        query.setParamCode(trimToNull(paramCode));
        query.setIsFlag(1L);
        List<BaseDeviceParam> paramList = baseDeviceParamMapper.selectBaseDeviceParamList(query);
        if (paramList == null || paramList.isEmpty()) {
            return null;
        }
        return paramList.get(0);
    }

    private List<BaseDeviceParam> loadSwitchParams(String deviceCode, String switchType) {
        BaseDeviceParam query = new BaseDeviceParam();
        query.setDeviceCode(trimToNull(deviceCode));
        query.setIsFlag(1L);
        List<BaseDeviceParam> paramList = baseDeviceParamMapper.selectBaseDeviceParamList(query);
        if (paramList == null || paramList.isEmpty()) {
            return Collections.emptyList();
        }
        List<BaseDeviceParam> resultList = new ArrayList<BaseDeviceParam>();
        List<String> switchParamNames = resolveSwitchParamNames(switchType);
        for (BaseDeviceParam param : paramList) {
            if (switchParamNames.contains(param.getParamName())) {
                resultList.add(param);
            }
        }
        return resultList;
    }

    private List<DeviceParamSpcPointVo> buildSpcPoints(List<BaseDeviceParamVal> historyList, BaseDeviceParam config) {
        if (historyList == null || historyList.isEmpty()) {
            return Collections.emptyList();
        }
        historyList.sort(Comparator.comparing(this::resolveEventTime));
        List<DeviceParamSpcPointVo> pointList = new ArrayList<DeviceParamSpcPointVo>();
        for (BaseDeviceParamVal history : historyList) {
            Double numericValue = parseNumeric(history.getParamValue());
            if (numericValue == null) {
                continue;
            }
            DeviceParamSpcPointVo pointVo = new DeviceParamSpcPointVo();
            pointVo.setDeviceCode(history.getDeviceCode());
            pointVo.setDeviceName(config != null && StringUtils.isNotBlank(config.getDeviceName()) ? config.getDeviceName() : history.getDeviceCode());
            pointVo.setParamCode(history.getParamCode());
            pointVo.setParamName(config != null && StringUtils.isNotBlank(config.getParamName()) ? config.getParamName() : history.getParamName());
            pointVo.setUpperLimit(config != null ? config.getUpperLimit() : null);
            pointVo.setLowerLimit(config != null ? config.getLowerLimit() : null);
            pointVo.setCollectTime(resolveEventTime(history));
            pointVo.setParamValue(numericValue);
            pointList.add(pointVo);
        }
        return pointList;
    }

    private DeviceParamAnomalyVo buildAnomalyVo(BaseDeviceParam alertParam, List<NumericSample> sampleList) {
        if (sampleList == null || sampleList.isEmpty()) {
            return null;
        }
        sampleList.sort(Comparator.comparing(sample -> resolveEventTime(sample.history)));
        AlertEvent currentEvent = null;
        List<AlertEvent> eventList = new ArrayList<AlertEvent>();
        for (NumericSample sample : sampleList) {
            boolean upperAbnormal = alertParam.getUpperLimit() != null && sample.value > alertParam.getUpperLimit();
            boolean lowerAbnormal = alertParam.getLowerLimit() != null && sample.value < alertParam.getLowerLimit();
            if (!upperAbnormal && !lowerAbnormal) {
                if (currentEvent != null) {
                    eventList.add(currentEvent);
                    currentEvent = null;
                }
                continue;
            }
            if (currentEvent == null) {
                currentEvent = new AlertEvent();
                currentEvent.startTime = resolveEventTime(sample.history);
                currentEvent.maxValue = sample.value;
                currentEvent.minValue = sample.value;
            }
            currentEvent.endTime = resolveEventTime(sample.history);
            currentEvent.maxValue = Math.max(currentEvent.maxValue, sample.value);
            currentEvent.minValue = Math.min(currentEvent.minValue, sample.value);
            currentEvent.upperAbnormal = currentEvent.upperAbnormal || upperAbnormal;
            currentEvent.lowerAbnormal = currentEvent.lowerAbnormal || lowerAbnormal;
        }
        if (currentEvent != null) {
            eventList.add(currentEvent);
        }
        if (eventList.isEmpty()) {
            return null;
        }

        DeviceParamAnomalyVo anomalyVo = new DeviceParamAnomalyVo();
        anomalyVo.setDeviceCode(alertParam.getDeviceCode());
        anomalyVo.setDeviceName(alertParam.getDeviceName());
        anomalyVo.setParamCode(alertParam.getParamCode());
        anomalyVo.setParamName(alertParam.getParamName());
        anomalyVo.setAlertLevel(alertParam.getAlertLevel());
        anomalyVo.setAlertLevelName(resolveAlertLevelName(alertParam.getAlertLevel()));
        anomalyVo.setUpperLimit(alertParam.getUpperLimit());
        anomalyVo.setLowerLimit(alertParam.getLowerLimit());
        anomalyVo.setOverLimitCount(Long.valueOf(eventList.size()));

        long upperCount = 0L;
        long lowerCount = 0L;
        double durationMinutes = 0D;
        Double maxValue = null;
        Double minValue = null;
        Date firstTime = null;
        Date lastTime = null;
        for (AlertEvent event : eventList) {
            if (event.upperAbnormal) {
                upperCount++;
            }
            if (event.lowerAbnormal) {
                lowerCount++;
            }
            durationMinutes += calcDurationMinutes(event.startTime, event.endTime, alertParam.getReadFrequency());
            maxValue = maxValue == null ? event.maxValue : Math.max(maxValue, event.maxValue);
            minValue = minValue == null ? event.minValue : Math.min(minValue, event.minValue);
            if (firstTime == null || firstTime.after(event.startTime)) {
                firstTime = event.startTime;
            }
            if (lastTime == null || lastTime.before(event.endTime)) {
                lastTime = event.endTime;
            }
        }
        anomalyVo.setUpperAnomalyCount(upperCount);
        anomalyVo.setLowerAnomalyCount(lowerCount);
        anomalyVo.setAbnormalDurationMinutes(scale(durationMinutes));
        anomalyVo.setMaxValue(scale(maxValue));
        anomalyVo.setMinValue(scale(minValue));
        anomalyVo.setFirstAbnormalTime(firstTime);
        anomalyVo.setLastAbnormalTime(lastTime);
        return anomalyVo;
    }

    private void appendSwitchTraceRows(List<DeviceParamSwitchTraceVo> resultList, String deviceCode, List<BaseDeviceParam> switchParams,
                                       List<BaseDeviceParam> alertParams, Date beginTime, Date endTime, Date observeEndTime,
                                       int observeMinutes, String switchType) {
        List<String> switchParamNames = extractSwitchParamNames(switchType, switchParams);
        List<BaseDeviceParamVal> switchHistory = deviceParamReadFacade.listHistoryParamValuesByNames(
                deviceCode, switchParamNames, beginTime, endTime, null);
        if (switchHistory.isEmpty()) {
            return;
        }
        switchHistory.sort(Comparator.comparing(this::resolveEventTime));
        List<BaseDeviceParamVal> outputHistory = deviceParamReadFacade.listHistoryParamValues(
                deviceCode, null, OUTPUT_PARAM_NAME, beginTime, observeEndTime, null);
        outputHistory.sort(Comparator.comparing(this::resolveEventTime));
        List<BaseDeviceParamVal> statusHistory = deviceParamReadFacade.listHistoryParamValuesByNames(
                deviceCode, STATUS_PARAM_NAMES, beginTime, observeEndTime, null);
        statusHistory.sort(Comparator.comparing(this::resolveEventTime));
        List<BaseDeviceParamVal> alertHistory = deviceParamReadFacade.listHistoryParamValues(
                deviceCode, null, null, beginTime, observeEndTime, null);
        alertHistory.sort(Comparator.comparing(this::resolveEventTime));
        Map<String, BaseDeviceParam> alertConfigMap = buildAlertConfigMap(alertParams);
        Map<String, String> switchTypeMap = buildSwitchTypeMap(switchParams);

        Map<String, BaseDeviceParamVal> previousValueMap = new HashMap<String, BaseDeviceParamVal>();
        for (BaseDeviceParamVal history : switchHistory) {
            String paramKey = buildParamKey(history.getParamCode(), history.getParamName());
            BaseDeviceParamVal previous = previousValueMap.get(paramKey);
            if (previous != null && !Objects.equals(normalizeValue(previous.getParamValue()), normalizeValue(history.getParamValue()))) {
                DeviceParamSwitchTraceVo switchTraceVo = new DeviceParamSwitchTraceVo();
                switchTraceVo.setDeviceCode(deviceCode);
                switchTraceVo.setDeviceName(resolveDeviceName(switchParams, history));
                switchTraceVo.setSwitchType(switchTypeMap.getOrDefault(paramKey, resolveSwitchType(history.getParamName())));
                switchTraceVo.setParamCode(history.getParamCode());
                switchTraceVo.setParamName(history.getParamName());
                switchTraceVo.setChangeTime(resolveEventTime(history));
                switchTraceVo.setBeforeValue(previous.getParamValue());
                switchTraceVo.setAfterValue(history.getParamValue());
                switchTraceVo.setObserveMinutes(observeMinutes);
                Date windowEnd = addMinutes(resolveEventTime(history), observeMinutes);
                switchTraceVo.setOutputIncrement(calcOutputIncrement(outputHistory, resolveEventTime(history), windowEnd));
                switchTraceVo.setAbnormalCount(countAbnormalSamples(alertHistory, alertConfigMap, resolveEventTime(history), windowEnd));
                switchTraceVo.setLatestStatus(resolveLatestStatus(statusHistory, resolveEventTime(history), windowEnd));
                resultList.add(switchTraceVo);
            }
            previousValueMap.put(paramKey, history);
        }
    }

    private double calcDurationMinutes(Date startTime, Date endTime, Long readFrequency) {
        if (startTime == null) {
            return 0D;
        }
        if (endTime != null && endTime.after(startTime)) {
            return (endTime.getTime() - startTime.getTime()) / 60000D;
        }
        long frequency = readFrequency == null || readFrequency <= 0 ? 60000L : readFrequency.longValue();
        return frequency / 60000D;
    }

    private Map<String, BaseDeviceParam> buildAlertConfigMap(List<BaseDeviceParam> alertParams) {
        Map<String, BaseDeviceParam> resultMap = new HashMap<String, BaseDeviceParam>();
        if (alertParams == null) {
            return resultMap;
        }
        for (BaseDeviceParam alertParam : alertParams) {
            resultMap.put(buildParamKey(alertParam.getParamCode(), alertParam.getParamName()), alertParam);
        }
        return resultMap;
    }

    private Map<String, String> buildSwitchTypeMap(List<BaseDeviceParam> switchParams) {
        Map<String, String> resultMap = new HashMap<String, String>();
        for (BaseDeviceParam switchParam : switchParams) {
            resultMap.put(buildParamKey(switchParam.getParamCode(), switchParam.getParamName()), resolveSwitchType(switchParam.getParamName()));
        }
        return resultMap;
    }

    private List<String> extractSwitchParamNames(String switchType, List<BaseDeviceParam> switchParams) {
        List<String> resultList = new ArrayList<String>();
        for (BaseDeviceParam switchParam : switchParams) {
            if (!resultList.contains(switchParam.getParamName())) {
                resultList.add(switchParam.getParamName());
            }
        }
        if (!resultList.isEmpty()) {
            return resultList;
        }
        return resolveSwitchParamNames(switchType);
    }

    private List<String> resolveSwitchParamNames(String switchType) {
        if ("MOLD".equalsIgnoreCase(switchType)) {
            return Collections.singletonList("机台状态-模具数据名称");
        }
        if ("MATERIAL".equalsIgnoreCase(switchType)) {
            return Collections.singletonList("机台状态-物料描述");
        }
        if ("PRODUCT".equalsIgnoreCase(switchType)) {
            return Arrays.asList("机台状态-产品描述", "机台状态-产品描述1");
        }
        return SWITCH_PARAM_NAMES;
    }

    private Double calcOutputIncrement(List<BaseDeviceParamVal> outputHistory, Date beginTime, Date endTime) {
        Double minValue = null;
        Double maxValue = null;
        for (BaseDeviceParamVal history : outputHistory) {
            Date eventTime = resolveEventTime(history);
            if (!between(eventTime, beginTime, endTime)) {
                continue;
            }
            Double numericValue = parseNumeric(history.getParamValue());
            if (numericValue == null) {
                continue;
            }
            minValue = minValue == null ? numericValue : Math.min(minValue, numericValue);
            maxValue = maxValue == null ? numericValue : Math.max(maxValue, numericValue);
        }
        if (minValue == null || maxValue == null) {
            return 0D;
        }
        return scale(maxValue - minValue);
    }

    private Long countAbnormalSamples(List<BaseDeviceParamVal> alertHistory, Map<String, BaseDeviceParam> alertConfigMap,
                                      Date beginTime, Date endTime) {
        long count = 0L;
        for (BaseDeviceParamVal history : alertHistory) {
            Date eventTime = resolveEventTime(history);
            if (!between(eventTime, beginTime, endTime)) {
                continue;
            }
            BaseDeviceParam config = alertConfigMap.get(buildParamKey(history.getParamCode(), history.getParamName()));
            if (config == null) {
                continue;
            }
            Double numericValue = parseNumeric(history.getParamValue());
            if (numericValue == null) {
                continue;
            }
            if ((config.getUpperLimit() != null && numericValue > config.getUpperLimit())
                    || (config.getLowerLimit() != null && numericValue < config.getLowerLimit())) {
                count++;
            }
        }
        return Long.valueOf(count);
    }

    private String resolveLatestStatus(List<BaseDeviceParamVal> statusHistory, Date beginTime, Date endTime) {
        BaseDeviceParamVal latestStatus = null;
        for (BaseDeviceParamVal history : statusHistory) {
            Date eventTime = resolveEventTime(history);
            if (!between(eventTime, beginTime, endTime)) {
                continue;
            }
            if (!"TRUE".equalsIgnoreCase(normalizeValue(history.getParamValue()))) {
                continue;
            }
            if (latestStatus == null || resolveEventTime(latestStatus).before(eventTime)) {
                latestStatus = history;
            }
        }
        if (latestStatus == null) {
            return "未知";
        }
        return mapStatusName(latestStatus.getParamName());
    }

    private String mapStatusName(String paramName) {
        if ("机台状态-三色灯机器运行".equals(paramName)) {
            return "运行";
        }
        if ("机台状态-三色灯机器暂停".equals(paramName)) {
            return "停机";
        }
        if ("机台状态-三色灯机器待机".equals(paramName)) {
            return "待机";
        }
        if ("机台状态-三色灯机器报警".equals(paramName)) {
            return "报警";
        }
        return "未知";
    }

    private String resolveSwitchType(String paramName) {
        if ("机台状态-模具数据名称".equals(paramName)) {
            return "模具";
        }
        if ("机台状态-物料描述".equals(paramName)) {
            return "物料";
        }
        return "产品";
    }

    private String resolveDeviceName(List<BaseDeviceParam> switchParams, BaseDeviceParamVal history) {
        if (switchParams != null && !switchParams.isEmpty() && StringUtils.isNotBlank(switchParams.get(0).getDeviceName())) {
            return switchParams.get(0).getDeviceName();
        }
        return history.getDeviceCode();
    }

    private boolean isNumericParam(BaseDeviceParam param) {
        if (param == null || StringUtils.isBlank(param.getParamType())) {
            return false;
        }
        return Arrays.asList("1", "2", "3", "4", "5").contains(param.getParamType());
    }

    private String resolveAlertLevelName(String alertLevel) {
        if ("1".equals(alertLevel)) {
            return "一般";
        }
        if ("2".equals(alertLevel)) {
            return "重要";
        }
        if ("3".equals(alertLevel)) {
            return "紧急";
        }
        return "未配置";
    }

    private String buildParamKey(String paramCode, String paramName) {
        if (StringUtils.isNotBlank(paramCode)) {
            return "CODE:" + paramCode;
        }
        return "NAME:" + trimToNull(paramName);
    }

    private Double parseNumeric(String value) {
        if (StringUtils.isBlank(value)) {
            return null;
        }
        try {
            return new BigDecimal(value.trim()).doubleValue();
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    private Date parseDate(String dateTime) {
        if (StringUtils.isBlank(dateTime)) {
            return null;
        }
        LocalDateTime parsed = LocalDateTime.parse(dateTime.trim(), DATE_TIME_FORMATTER);
        return Date.from(parsed.atZone(ZoneId.systemDefault()).toInstant());
    }

    private String trimToNull(String text) {
        return StringUtils.isBlank(text) ? null : text.trim();
    }

    private Date resolveEventTime(BaseDeviceParamVal history) {
        if (history.getRecordTime() != null) {
            return history.getRecordTime();
        }
        if (history.getCollectTime() != null) {
            return history.getCollectTime();
        }
        return new Date(0L);
    }

    private Date addMinutes(Date baseTime, int minutes) {
        return new Date(baseTime.getTime() + minutes * 60L * 1000L);
    }

    private boolean between(Date currentTime, Date beginTime, Date endTime) {
        return currentTime != null
                && !currentTime.before(beginTime)
                && !currentTime.after(endTime);
    }

    private String normalizeValue(String value) {
        return value == null ? null : value.trim();
    }

    private static final class NumericSample {
        private final BaseDeviceParamVal history;
        private final double value;

        private NumericSample(BaseDeviceParamVal history, double value) {
            this.history = history;
            this.value = value;
        }
    }

    private static final class AlertEvent {
        private Date startTime;
        private Date endTime;
        private double maxValue;
        private double minValue;
        private boolean upperAbnormal;
        private boolean lowerAbnormal;
    }
}
