package com.njcn.gather.tool.wave.service.impl;

import cn.hutool.core.util.StrUtil;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.tool.wave.component.WaveFileComponent;
import com.njcn.gather.tool.wave.component.WaveVectorComponent;
import com.njcn.gather.tool.wave.pojo.bo.WaveDataDetail;
import com.njcn.gather.tool.wave.pojo.dto.EigenvalueDTO;
import com.njcn.gather.tool.wave.pojo.dto.WaveDataDTO;
import com.njcn.gather.tool.wave.pojo.dto.WaveVectorGroupDTO;
import com.njcn.gather.tool.wave.pojo.param.WaveComtradeParseParam;
import com.njcn.gather.tool.wave.pojo.param.WaveParseParam;
import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO;
import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeVectorResultVO;
import com.njcn.gather.tool.wave.pojo.vo.WaveParseResultVO;
import com.njcn.gather.tool.wave.pojo.vo.WavePointVO;
import com.njcn.gather.tool.wave.service.WaveService;
import com.njcn.gather.tool.wave.utils.WaveUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 波形解析服务实现。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WaveServiceImpl implements WaveService {

    /** 文本波形默认最大返回点数。 */
    private static final int DEFAULT_MAX_POINT_COUNT = 2000;
    /** 自动识别分隔符标记。 */
    private static final String AUTO_SEPARATOR = "AUTO";
    /** COMTRADE 默认解析类型。 */
    private static final int DEFAULT_PARSE_TYPE = 1;
    /** 向量计算固定使用原始波形解析类型。 */
    private static final int RAW_WAVE_PARSE_TYPE = 3;
    /** PT/CT 默认变比。 */
    private static final double DEFAULT_RATIO = 1D;
    /** PT 默认接线方式，0 表示星形。 */
    private static final int DEFAULT_PT_TYPE = 0;

    /** 波形文件解析组件。 */
    private final WaveFileComponent waveFileComponent;
    /** 波形向量计算组件。 */
    private final WaveVectorComponent waveVectorComponent;

    @Override
    public WaveParseResultVO parse(WaveParseParam param) {
        if (param == null || StrUtil.isBlank(param.getWaveformText())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "波形文本不能为空");
        }

        boolean containsXAxis = Boolean.TRUE.equals(param.getContainsXAxis());
        int skipHeaderLines = sanitizeSkipHeaderLines(param.getSkipHeaderLines());
        int maxPointCount = sanitizeMaxPointCount(param.getMaxPointCount());
        BigDecimal samplingInterval = sanitizeSamplingInterval(param.getSamplingInterval());
        int xColumnIndex = sanitizeColumnIndex(param.getXColumnIndex(), 0);
        int yColumnIndex = sanitizeColumnIndex(param.getYColumnIndex(), containsXAxis ? 1 : 0);

        List<WavePointVO> sourcePoints = new ArrayList<>();
        int ignoredLineCount = 0;
        int nonBlankLineIndex = 0;
        String[] lines = param.getWaveformText().split("\\r?\\n");
        for (String line : lines) {
            if (StrUtil.isBlank(line)) {
                continue;
            }
            if (nonBlankLineIndex++ < skipHeaderLines) {
                continue;
            }
            String[] columns = splitColumns(line, param.getSeparator());
            if (columns.length == 0) {
                ignoredLineCount++;
                continue;
            }
            try {
                if (containsXAxis) {
                    sourcePoints.add(buildPoint(columns, xColumnIndex, yColumnIndex));
                } else {
                    sourcePoints.addAll(buildSingleColumnPoints(columns, samplingInterval, sourcePoints.size()));
                }
            } catch (Exception ex) {
                ignoredLineCount++;
                log.debug("波形行解析失败，line={}, reason={}", line, ex.getMessage());
            }
        }

        if (sourcePoints.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "未解析到有效波形点位");
        }

        List<WavePointVO> displayPoints = downSample(sourcePoints, maxPointCount);
        return buildTextResult(sourcePoints, displayPoints, ignoredLineCount, containsXAxis);
    }

    @Override
    public WaveComtradeResultVO parseComtrade(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param) {
        if (cfgStream == null || datStream == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "cfg 或 dat 文件不能为空");
        }

        WaveComtradeParseParam resolvedParam = param == null ? new WaveComtradeParseParam() : param;
        try (InputStream cfgInputStream = cfgStream; InputStream datInputStream = datStream) {
            WaveDataDTO waveDataDTO = waveFileComponent.getComtrade(cfgInputStream, datInputStream, sanitizeParseType(resolvedParam.getParseType()));
            applyWaveMetadata(waveDataDTO, resolvedParam);

            boolean needRms = shouldCalculateRms(resolvedParam);
            boolean buildDetails = shouldBuildDetails(resolvedParam);
            boolean calculateEigenvalue = Boolean.TRUE.equals(resolvedParam.getCalculateEigenvalue());

            if (needRms || buildDetails || calculateEigenvalue) {
                waveDataDTO = waveFileComponent.getValidData(waveDataDTO);
            }

            WaveComtradeResultVO result = new WaveComtradeResultVO();
            result.setWaveData(waveDataDTO);
            if (buildDetails) {
                List<WaveDataDetail> waveDataDetails = WaveUtil.filterWaveData(waveDataDTO);
                result.setWaveDataDetails(waveDataDetails);
            }
            if (calculateEigenvalue) {
                List<EigenvalueDTO> eigenvalues = waveFileComponent.getEigenvalue(
                        waveDataDTO,
                        resolvedParam.getDynamicThreshold() == null || resolvedParam.getDynamicThreshold()
                );
                result.setEigenvalues(eigenvalues);
            }
            return result;
        } catch (BusinessException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("COMTRADE 波形解析失败", ex);
            throw new BusinessException(CommonResponseEnum.FAIL, "COMTRADE 波形解析失败");
        }
    }

    @Override
    public WaveComtradeVectorResultVO parseComtradeVector(InputStream cfgStream, InputStream datStream, WaveComtradeParseParam param) {
        if (cfgStream == null || datStream == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "cfg 或 dat 文件不能为空");
        }

        WaveComtradeParseParam resolvedParam = param == null ? new WaveComtradeParseParam() : param;
        try (InputStream cfgInputStream = cfgStream; InputStream datInputStream = datStream) {
            WaveDataDTO waveDataDTO = waveFileComponent.getComtrade(cfgInputStream, datInputStream, RAW_WAVE_PARSE_TYPE);
            applyWaveMetadata(waveDataDTO, resolvedParam);

            List<WaveVectorGroupDTO> vectorGroups = waveVectorComponent.calculateVectors(waveDataDTO);
            WaveComtradeVectorResultVO result = new WaveComtradeVectorResultVO();
            result.setMonitorName(waveDataDTO.getMonitorName());
            result.setTime(waveDataDTO.getTime());
            result.setSamplePerCycle(waveDataDTO.getComtradeCfgDTO().getFinalSampleRate());
            result.setCycleCount(resolveCycleCount(vectorGroups));
            result.setVectorGroups(vectorGroups);
            return result;
        } catch (BusinessException ex) {
            throw ex;
        } catch (Exception ex) {
            log.error("COMTRADE 向量计算失败", ex);
            throw new BusinessException(CommonResponseEnum.FAIL, "COMTRADE 向量计算失败");
        }
    }

    private Integer resolveCycleCount(List<WaveVectorGroupDTO> vectorGroups) {
        if (vectorGroups == null || vectorGroups.isEmpty() || vectorGroups.get(0).getVectorSeries() == null) {
            return 0;
        }
        return vectorGroups.get(0).getVectorSeries().size();
    }

    private void applyWaveMetadata(WaveDataDTO waveDataDTO, WaveComtradeParseParam param) {
        waveDataDTO.setPt(defaultIfNull(param.getPt(), DEFAULT_RATIO));
        waveDataDTO.setCt(defaultIfNull(param.getCt(), DEFAULT_RATIO));
        waveDataDTO.setPtType(param.getPtType() == null ? DEFAULT_PT_TYPE : param.getPtType());
        waveDataDTO.setMonitorName(StrUtil.blankToDefault(param.getMonitorName(), "未命名测点"));
    }

    private boolean shouldCalculateRms(WaveComtradeParseParam param) {
        return param.getCalculateRms() == null || param.getCalculateRms();
    }

    private boolean shouldBuildDetails(WaveComtradeParseParam param) {
        return param.getBuildDetails() == null || param.getBuildDetails();
    }

    private int sanitizeParseType(Integer parseType) {
        if (parseType == null || parseType < 0 || parseType > 3) {
            return DEFAULT_PARSE_TYPE;
        }
        return parseType;
    }

    private double defaultIfNull(Double value, double defaultValue) {
        if (value == null || value <= 0) {
            return defaultValue;
        }
        return value;
    }

    private WavePointVO buildPoint(String[] columns, int xColumnIndex, int yColumnIndex) {
        BigDecimal yValue = parseNumber(readColumn(columns, yColumnIndex));
        BigDecimal xValue = parseNumber(readColumn(columns, xColumnIndex));
        return new WavePointVO(xValue, yValue);
    }

    private List<WavePointVO> buildSingleColumnPoints(String[] columns, BigDecimal samplingInterval, int startIndex) {
        List<WavePointVO> points = new ArrayList<>();
        for (int i = 0; i < columns.length; i++) {
            BigDecimal yValue = parseNumber(columns[i]);
            BigDecimal xValue = samplingInterval.multiply(BigDecimal.valueOf(startIndex + i));
            points.add(new WavePointVO(xValue, yValue));
        }
        return points;
    }

    private String readColumn(String[] columns, int columnIndex) {
        if (columnIndex < 0 || columnIndex >= columns.length) {
            throw new IllegalArgumentException("列下标超出范围");
        }
        return columns[columnIndex];
    }

    private BigDecimal parseNumber(String value) {
        if (StrUtil.isBlank(value)) {
            throw new IllegalArgumentException("数值不能为空");
        }
        return new BigDecimal(value.trim());
    }

    private String[] splitColumns(String line, String separator) {
        String trimmedLine = line.trim();
        if (StrUtil.isBlank(trimmedLine)) {
            return new String[0];
        }
        String[] parts;
        if (StrUtil.isBlank(separator) || AUTO_SEPARATOR.equalsIgnoreCase(separator)) {
            parts = trimmedLine.split("[,;\\s]+");
        } else if ("TAB".equalsIgnoreCase(separator)) {
            parts = trimmedLine.split("\\t+");
        } else if ("SPACE".equalsIgnoreCase(separator)) {
            parts = trimmedLine.split("\\s+");
        } else {
            parts = trimmedLine.split(Pattern.quote(separator));
        }
        return Arrays.stream(parts)
                .map(String::trim)
                .filter(StrUtil::isNotBlank)
                .toArray(String[]::new);
    }

    private List<WavePointVO> downSample(List<WavePointVO> sourcePoints, int maxPointCount) {
        if (sourcePoints.size() <= maxPointCount) {
            return sourcePoints;
        }

        List<WavePointVO> result = new ArrayList<>();
        int step = (int) Math.ceil((double) sourcePoints.size() / maxPointCount);
        for (int i = 0; i < sourcePoints.size(); i += step) {
            result.add(sourcePoints.get(i));
        }
        WavePointVO lastPoint = sourcePoints.get(sourcePoints.size() - 1);
        if (!result.contains(lastPoint) && result.size() < maxPointCount) {
            result.add(lastPoint);
        } else if (!result.isEmpty()) {
            result.set(result.size() - 1, lastPoint);
        }
        return result;
    }

    private WaveParseResultVO buildTextResult(List<WavePointVO> sourcePoints, List<WavePointVO> displayPoints,
                                              int ignoredLineCount, boolean containsXAxis) {
        BigDecimal minX = sourcePoints.get(0).getX();
        BigDecimal maxX = sourcePoints.get(0).getX();
        BigDecimal minY = sourcePoints.get(0).getY();
        BigDecimal maxY = sourcePoints.get(0).getY();
        BigDecimal sumY = BigDecimal.ZERO;

        for (WavePointVO point : sourcePoints) {
            if (point.getX().compareTo(minX) < 0) {
                minX = point.getX();
            }
            if (point.getX().compareTo(maxX) > 0) {
                maxX = point.getX();
            }
            if (point.getY().compareTo(minY) < 0) {
                minY = point.getY();
            }
            if (point.getY().compareTo(maxY) > 0) {
                maxY = point.getY();
            }
            sumY = sumY.add(point.getY());
        }

        WaveParseResultVO result = new WaveParseResultVO();
        result.setContainsXAxis(containsXAxis);
        result.setSourcePointCount(sourcePoints.size());
        result.setDisplayPointCount(displayPoints.size());
        result.setIgnoredLineCount(ignoredLineCount);
        result.setSampled(sourcePoints.size() != displayPoints.size());
        result.setMinX(minX);
        result.setMaxX(maxX);
        result.setMinY(minY);
        result.setMaxY(maxY);
        result.setAverageY(sumY.divide(BigDecimal.valueOf(sourcePoints.size()), 6, RoundingMode.HALF_UP));
        result.setPoints(displayPoints);
        return result;
    }

    private int sanitizeSkipHeaderLines(Integer skipHeaderLines) {
        if (skipHeaderLines == null || skipHeaderLines < 0) {
            return 0;
        }
        return skipHeaderLines;
    }

    private int sanitizeMaxPointCount(Integer maxPointCount) {
        if (maxPointCount == null || maxPointCount <= 0) {
            return DEFAULT_MAX_POINT_COUNT;
        }
        return maxPointCount;
    }

    private int sanitizeColumnIndex(Integer columnIndex, int defaultValue) {
        if (columnIndex == null || columnIndex < 0) {
            return defaultValue;
        }
        return columnIndex;
    }

    private BigDecimal sanitizeSamplingInterval(BigDecimal samplingInterval) {
        if (samplingInterval == null || samplingInterval.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ONE;
        }
        return samplingInterval;
    }
}
