package com.njcn.gather.event.eventlist.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.event.eventlist.mapper.EventListMapper;
import com.njcn.gather.event.eventlist.pojo.param.EventListQueryParam;
import com.njcn.gather.event.eventlist.pojo.param.EventWaveExportParam;
import com.njcn.gather.event.eventlist.pojo.po.MpEventDetailPO;
import com.njcn.gather.event.eventlist.pojo.vo.EventListVO;
import com.njcn.gather.event.eventlist.service.EventListService;
import com.njcn.gather.system.cfg.service.ISysConfigService;
import com.njcn.gather.system.dictionary.pojo.po.DictData;
import com.njcn.gather.system.dictionary.pojo.po.DictType;
import com.njcn.gather.system.dictionary.service.IDictDataService;
import com.njcn.gather.system.dictionary.service.IDictTypeService;
import com.njcn.gather.system.pojo.constant.DictConst;
import com.njcn.gather.system.util.ExportFileNameUtil;
import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLinePathQueryParam;
import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO;
import com.njcn.gather.tool.addledger.service.AddLedgerService;
import com.njcn.gather.tool.wave.pojo.param.WaveComtradeParseParam;
import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO;
import com.njcn.gather.tool.wave.service.WaveService;
import com.njcn.web.factory.PageFactory;
import com.njcn.web.utils.ExcelUtil;
import com.njcn.web.utils.HttpServletUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 事件列表服务实现。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class EventListServiceImpl implements EventListService {

    private static final int LEDGER_LINE_QUERY_LIMIT = 1000;
    private static final int EVENT_LINE_ID_QUERY_LIMIT = 1000;
    private static final int EXPORT_LIMIT = 5000;
    private static final int WAVE_EXPORT_LIMIT = 500;
    private static final String EMPTY_TEXT = "-";
    private static final String WAVE_EXPORT_SUCCESS = "成功";
    private static final String WAVE_EXPORT_FAIL = "失败";
    private static final BigDecimal PERCENT_BASE = new BigDecimal("100");
    private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
    private static final DateTimeFormatter[] INPUT_FORMATTERS = new DateTimeFormatter[]{
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
    };

    private final EventListMapper eventListMapper;
    private final AddLedgerService addLedgerService;
    private final ISysConfigService sysConfigService;
    private final IDictTypeService dictTypeService;
    private final IDictDataService dictDataService;
    private final WaveService waveService;

    @Override
    public Page<EventListVO> pageTransientEvents(EventListQueryParam param) {
        EventListQueryParam queryParam = normalizeQueryParam(param);
        if (!resolveLineFilter(queryParam)) {
            return emptyPage(queryParam);
        }
        Page<MpEventDetailPO> eventPage = eventListMapper.selectTransientPage(
                new Page<MpEventDetailPO>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)),
                queryParam);
        List<EventListVO> records = buildEventList(eventPage.getRecords());
        Page<EventListVO> resultPage = new Page<EventListVO>(eventPage.getCurrent(), eventPage.getSize(), eventPage.getTotal());
        resultPage.setRecords(records);
        return resultPage;
    }

    @Override
    public EventListVO getTransientEventDetail(String eventId) {
        String normalizedEventId = trimToNull(eventId);
        if (normalizedEventId == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "事件 ID 不能为空");
        }
        MpEventDetailPO eventDetail = eventListMapper.selectTransientDetail(normalizedEventId);
        if (eventDetail == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "暂态事件不存在");
        }
        List<MpEventDetailPO> eventDetails = new ArrayList<MpEventDetailPO>();
        eventDetails.add(eventDetail);
        return buildEventList(eventDetails).get(0);
    }

    @Override
    public void exportTransientEvents(EventListQueryParam param) {
        EventListQueryParam queryParam = normalizeQueryParam(param);
        List<EventListVO> exportRecords;
        if (resolveLineFilter(queryParam)) {
            List<MpEventDetailPO> eventDetails = eventListMapper.selectTransientExportList(queryParam, EXPORT_LIMIT + 1);
            if (eventDetails.size() > EXPORT_LIMIT) {
                throw new BusinessException(CommonResponseEnum.FAIL, "导出数据超过 5000 条，请缩小查询条件");
            }
            exportRecords = buildEventList(eventDetails);
            fillExportEventTypeNames(exportRecords);
        } else {
            exportRecords = Collections.emptyList();
        }
        ExcelUtil.exportExcel(ExportFileNameUtil.appendToday("暂态事件列表.xlsx"), "暂态事件列表", EventListVO.class, exportRecords);
    }

    @Override
    public WaveComtradeResultVO getTransientEventWave(String eventId) {
        MpEventDetailPO eventDetail = requireEventDetail(eventId);
        AddLedgerLinePathVO linePath = requireLinePath(eventDetail);
        EventWavePathResolver.WaveFilePath waveFilePath = resolveWaveFilePath(eventDetail, linePath);
        if (!Files.exists(waveFilePath.getCfgPath())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "cfg 波形文件不存在");
        }
        if (!Files.exists(waveFilePath.getDatPath())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "dat 波形文件不存在");
        }
        WaveComtradeParseParam parseParam = new WaveComtradeParseParam();
        parseParam.setMonitorName(defaultText(linePath.getLineName()));
        try (InputStream cfgInputStream = new FileInputStream(waveFilePath.getCfgPath().toFile());
             InputStream datInputStream = new FileInputStream(waveFilePath.getDatPath().toFile())) {
            return waveService.parseComtrade(cfgInputStream, datInputStream, parseParam);
        } catch (IOException ex) {
            log.error("读取暂态事件波形文件失败，eventId={}", eventDetail.getEventId(), ex);
            throw new BusinessException(CommonResponseEnum.FAIL, "读取波形文件失败");
        }
    }

    @Override
    public void exportTransientEventWaves(EventWaveExportParam param) {
        List<String> eventIds = normalizeExportEventIds(param);
        List<WaveExportItem> exportItems = buildWaveExportItems(eventIds);
        writeWaveExportZip(exportItems);
    }

    private MpEventDetailPO requireEventDetail(String eventId) {
        String normalizedEventId = trimToNull(eventId);
        if (normalizedEventId == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "事件 ID 不能为空");
        }
        MpEventDetailPO eventDetail = eventListMapper.selectTransientDetail(normalizedEventId);
        if (eventDetail == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "暂态事件不存在");
        }
        return eventDetail;
    }

    private AddLedgerLinePathVO requireLinePath(MpEventDetailPO eventDetail) {
        List<String> lineIds = new ArrayList<String>();
        lineIds.add(eventDetail.getMeasurementPointId());
        Map<String, AddLedgerLinePathVO> linePathMap = addLedgerService.listLinePathByLineIds(lineIds);
        AddLedgerLinePathVO linePath = linePathMap.get(eventDetail.getMeasurementPointId());
        if (linePath == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "监测点台账不存在或已删除");
        }
        return linePath;
    }

    private List<String> normalizeExportEventIds(EventWaveExportParam param) {
        List<String> eventIds = normalizeIds(param == null ? null : param.getEventIds());
        if (eventIds.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "请选择需要导出的事件");
        }
        if (eventIds.size() > WAVE_EXPORT_LIMIT) {
            throw new BusinessException(CommonResponseEnum.FAIL, "波形导出事件数量不能超过 500 条");
        }
        return eventIds;
    }

    private List<WaveExportItem> buildWaveExportItems(List<String> eventIds) {
        List<MpEventDetailPO> eventDetails = eventListMapper.selectTransientDetailsByIds(eventIds);
        Map<String, MpEventDetailPO> eventMap = new LinkedHashMap<String, MpEventDetailPO>();
        List<String> lineIds = new ArrayList<String>();
        for (MpEventDetailPO eventDetail : eventDetails) {
            eventMap.put(eventDetail.getEventId(), eventDetail);
            String lineId = trimToNull(eventDetail.getMeasurementPointId());
            if (lineId != null && !lineIds.contains(lineId)) {
                lineIds.add(lineId);
            }
        }
        Map<String, AddLedgerLinePathVO> linePathMap = addLedgerService.listLinePathByLineIds(lineIds);
        List<WaveExportItem> exportItems = new ArrayList<WaveExportItem>();
        for (String eventId : eventIds) {
            WaveExportItem item = new WaveExportItem();
            item.setEventId(eventId);
            MpEventDetailPO eventDetail = eventMap.get(eventId);
            item.setEventDetail(eventDetail);
            if (eventDetail == null) {
                item.fail("事件不存在");
                exportItems.add(item);
                continue;
            }
            AddLedgerLinePathVO linePath = linePathMap.get(eventDetail.getMeasurementPointId());
            item.setLinePath(linePath);
            item.setEventVO(buildEventVO(eventDetail, linePath));
            fillWaveExportFileState(item);
            exportItems.add(item);
        }
        return exportItems;
    }

    private void fillWaveExportFileState(WaveExportItem item) {
        MpEventDetailPO eventDetail = item.getEventDetail();
        if (item.getLinePath() == null) {
            item.fail("监测点台账不存在或已删除");
            return;
        }
        if (eventDetail.getFileFlag() == null || eventDetail.getFileFlag() != 1) {
            item.fail("波形文件未招");
            return;
        }
        try {
            EventWavePathResolver.WaveFilePath waveFilePath = resolveWaveFilePath(eventDetail, item.getLinePath());
            item.setWaveFilePath(waveFilePath);
            boolean cfgExists = Files.exists(waveFilePath.getCfgPath());
            boolean datExists = Files.exists(waveFilePath.getDatPath());
            if (cfgExists && datExists) {
                item.success();
                return;
            }
            if (!cfgExists && !datExists) {
                item.fail("cfg/dat 文件不存在");
            } else if (!cfgExists) {
                item.fail("cfg 文件不存在");
            } else {
                item.fail("dat 文件不存在");
            }
        } catch (BusinessException ex) {
            item.fail(ex.getMessage());
        }
    }

    private EventWavePathResolver.WaveFilePath resolveWaveFilePath(MpEventDetailPO eventDetail, AddLedgerLinePathVO linePath) {
        try {
            return EventWavePathResolver.resolve(sysConfigService.getWaveStoragePath(), linePath.getEquipmentMac(), eventDetail.getWavePath());
        } catch (IllegalArgumentException ex) {
            throw new BusinessException(CommonResponseEnum.FAIL, ex.getMessage());
        }
    }

    private void writeWaveExportZip(List<WaveExportItem> exportItems) {
        HttpServletResponse response = HttpServletUtil.getResponse();
        try {
            String fileName = URLEncoder.encode(ExportFileNameUtil.appendToday("选中事件波形导出.zip"), "UTF-8");
            response.reset();
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
            response.setContentType("application/zip;charset=UTF-8");
            try (ServletOutputStream outputStream = response.getOutputStream();
                 ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
                writeZipEntry(zipOutputStream, ExportFileNameUtil.appendToday("选中事件日志.xlsx"), buildWaveExportWorkbook(exportItems));
                for (int i = 0; i < exportItems.size(); i++) {
                    WaveExportItem item = exportItems.get(i);
                    EventWavePathResolver.WaveFilePath waveFilePath = item.getWaveFilePath();
                    if (waveFilePath == null) {
                        continue;
                    }
                    String folder = buildZipFolderName(i + 1, item.getEventId());
                    writeWaveFileIfExists(zipOutputStream, folder + "/" + waveFilePath.getCfgPath().getFileName(), waveFilePath.getCfgPath());
                    writeWaveFileIfExists(zipOutputStream, folder + "/" + waveFilePath.getDatPath().getFileName(), waveFilePath.getDatPath());
                }
                zipOutputStream.flush();
            }
        } catch (IOException ex) {
            log.error("导出暂态事件波形失败", ex);
            throw new BusinessException(CommonResponseEnum.FAIL, "导出波形文件失败");
        }
    }

    private byte[] buildWaveExportWorkbook(List<WaveExportItem> exportItems) throws IOException {
        XSSFWorkbook workbook = new XSSFWorkbook();
        try {
            writeEventLogSheet(workbook, exportItems);
            writeWaveFileSheet(workbook, exportItems);
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            workbook.write(outputStream);
            return outputStream.toByteArray();
        } finally {
            workbook.close();
        }
    }

    private void writeEventLogSheet(XSSFWorkbook workbook, List<WaveExportItem> exportItems) {
        XSSFSheet sheet = workbook.createSheet("选中事件列表");
        String[] headers = new String[]{"发生时刻", "工程名称", "项目名称", "设备名称", "监测点名称", "暂降/暂升幅值(%)",
                "持续时间(s)", "事件类型", "相别", "事件描述", "事件发生位置"};
        int[] columnWidths = new int[]{25, 25, 25, 25, 25, 20, 18, 18, 15, 25, 18};
        writeHeader(sheet, headers, createHeaderStyle(workbook));
        Map<String, String> eventTypeNameMap = buildEventTypeNameMap();
        int rowIndex = 1;
        for (WaveExportItem item : exportItems) {
            EventListVO vo = item.getEventVO();
            if (vo == null) {
                continue;
            }
            Row row = sheet.createRow(rowIndex++);
            writeCells(row, new Object[]{formatDateTime(vo.getStartTime()), vo.getEngineeringName(), vo.getProjectName(),
                    vo.getEquipmentName(), vo.getLineName(), vo.getFeatureAmplitude(), vo.getDuration(),
                    translateEventType(vo.getEventType(), eventTypeNameMap), vo.getPhase(), vo.getEventDescribe(),
                    vo.getSagsource()});
        }
        applyColumnWidth(sheet, columnWidths);
    }

    private void writeWaveFileSheet(XSSFWorkbook workbook, List<WaveExportItem> exportItems) {
        XSSFSheet sheet = workbook.createSheet("波形文件清单");
        String[] headers = new String[]{"事件ID", "设备MAC", "wavePath", "cfg文件路径", "dat文件路径", "导出状态", "失败原因"};
        writeHeader(sheet, headers, createHeaderStyle(workbook));
        int rowIndex = 1;
        for (WaveExportItem item : exportItems) {
            EventWavePathResolver.WaveFilePath waveFilePath = item.getWaveFilePath();
            Row row = sheet.createRow(rowIndex++);
            writeCells(row, new Object[]{item.getEventId(),
                    item.getLinePath() == null ? EMPTY_TEXT : item.getLinePath().getEquipmentMac(),
                    item.getEventDetail() == null ? EMPTY_TEXT : item.getEventDetail().getWavePath(),
                    waveFilePath == null ? EMPTY_TEXT : waveFilePath.getCfgPath().toString(),
                    waveFilePath == null ? EMPTY_TEXT : waveFilePath.getDatPath().toString(),
                    item.getStatus(), defaultText(item.getFailReason())});
        }
        applyColumnWidth(sheet, headers.length);
    }

    private CellStyle createHeaderStyle(XSSFWorkbook workbook) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(true);
        style.setFont(font);
        return style;
    }

    private void writeHeader(XSSFSheet sheet, String[] headers, CellStyle style) {
        Row row = sheet.createRow(0);
        for (int i = 0; i < headers.length; i++) {
            Cell cell = row.createCell(i);
            cell.setCellValue(headers[i]);
            cell.setCellStyle(style);
        }
    }

    private void writeCells(Row row, Object[] values) {
        for (int i = 0; i < values.length; i++) {
            Cell cell = row.createCell(i);
            Object value = values[i];
            cell.setCellValue(value == null ? "" : String.valueOf(value));
        }
    }

    private void applyColumnWidth(XSSFSheet sheet, int columnCount) {
        for (int i = 0; i < columnCount; i++) {
            sheet.setColumnWidth(i, 20 * 256);
        }
    }

    private void applyColumnWidth(XSSFSheet sheet, int[] columnWidths) {
        for (int i = 0; i < columnWidths.length; i++) {
            sheet.setColumnWidth(i, columnWidths[i] * 256);
        }
    }

    private void writeZipEntry(ZipOutputStream zipOutputStream, String entryName, byte[] content) throws IOException {
        ZipEntry zipEntry = new ZipEntry(entryName);
        zipOutputStream.putNextEntry(zipEntry);
        zipOutputStream.write(content);
        zipOutputStream.closeEntry();
    }

    private void writeWaveFileIfExists(ZipOutputStream zipOutputStream, String entryName, Path filePath) throws IOException {
        if (!Files.exists(filePath)) {
            return;
        }
        zipOutputStream.putNextEntry(new ZipEntry(entryName));
        try (InputStream inputStream = new FileInputStream(filePath.toFile())) {
            byte[] buffer = new byte[8192];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                zipOutputStream.write(buffer, 0, length);
            }
        }
        zipOutputStream.closeEntry();
    }

    private String buildZipFolderName(int index, String eventId) {
        return index + "_" + sanitizeZipSegment(eventId);
    }

    private String sanitizeZipSegment(String value) {
        String text = trimToNull(value);
        return text == null ? "unknown" : text.replaceAll("[\\\\/:*?\"<>|]", "_");
    }

    private String formatDateTime(LocalDateTime value) {
        return value == null ? EMPTY_TEXT : OUTPUT_FORMATTER.format(value);
    }

    private List<EventListVO> buildEventList(List<MpEventDetailPO> eventDetails) {
        if (eventDetails == null || eventDetails.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> lineIds = new ArrayList<String>();
        for (MpEventDetailPO eventDetail : eventDetails) {
            String lineId = trimToNull(eventDetail.getMeasurementPointId());
            if (lineId != null && !lineIds.contains(lineId)) {
                lineIds.add(lineId);
            }
        }
        Map<String, AddLedgerLinePathVO> linePathMap = addLedgerService.listLinePathByLineIds(lineIds);
        List<EventListVO> result = new ArrayList<EventListVO>();
        for (MpEventDetailPO eventDetail : eventDetails) {
            AddLedgerLinePathVO linePath = linePathMap.get(eventDetail.getMeasurementPointId());
            result.add(buildEventVO(eventDetail, linePath));
        }
        return result;
    }

    private EventListVO buildEventVO(MpEventDetailPO eventDetail, AddLedgerLinePathVO linePath) {
        EventListVO vo = new EventListVO();
        vo.setEventId(eventDetail.getEventId());
        vo.setMeasurementPointId(eventDetail.getMeasurementPointId());
        vo.setEventType(eventDetail.getEventType());
        vo.setEquipmentName(linePath == null ? EMPTY_TEXT : defaultText(linePath.getEquipmentName()));
        vo.setMac(linePath == null ? EMPTY_TEXT : defaultText(linePath.getEquipmentMac()));
        vo.setEngineeringName(linePath == null ? EMPTY_TEXT : defaultText(linePath.getEngineeringName()));
        vo.setProjectName(linePath == null ? EMPTY_TEXT : defaultText(linePath.getProjectName()));
        vo.setStartTime(eventDetail.getStartTime());
        vo.setLineName(linePath == null ? EMPTY_TEXT : defaultText(linePath.getLineName()));
        vo.setEventDescribe(trimToNull(eventDetail.getEventDescribe()));
        vo.setSagsource(defaultText(eventDetail.getSagsource()));
        vo.setPhase(defaultText(eventDetail.getPhase()));
        vo.setDuration(eventDetail.getDuration());
        vo.setFeatureAmplitude(toPercent(eventDetail.getFeatureAmplitude()));
        vo.setWavePath(eventDetail.getWavePath());
        vo.setFileFlag(eventDetail.getFileFlag());
        vo.setDealFlag(eventDetail.getDealFlag());
        vo.setCreateTime(eventDetail.getCreateTime());
        return vo;
    }

    private EventListQueryParam normalizeQueryParam(EventListQueryParam param) {
        EventListQueryParam queryParam = param == null ? new EventListQueryParam() : param;
        LocalDateTime startTime = parseDateTime(queryParam.getStartTimeStart());
        LocalDateTime endTime = parseDateTime(queryParam.getStartTimeEnd());
        if (startTime == null) {
            LocalDateTime now = LocalDateTime.now();
            startTime = LocalDateTime.of(now.getYear(), now.getMonth(), 1, 0, 0, 0);
        }
        if (endTime == null) {
            endTime = LocalDateTime.now();
        }
        if (startTime.isAfter(endTime)) {
            throw new BusinessException(CommonResponseEnum.FAIL, "开始时间不能大于结束时间");
        }
        validateRange(queryParam.getDurationMin(), queryParam.getDurationMax(), "持续时间下限不能大于上限");
        validateRange(queryParam.getFeatureAmplitudeMin(), queryParam.getFeatureAmplitudeMax(), "幅值下限不能大于上限");
        validateFlag(queryParam.getFileFlag(), "波形文件状态只能是 0 或 1");
        validateDealFlag(queryParam.getDealFlag());
        queryParam.setFeatureAmplitudeMin(fromPercent(queryParam.getFeatureAmplitudeMin()));
        queryParam.setFeatureAmplitudeMax(fromPercent(queryParam.getFeatureAmplitudeMax()));
        queryParam.setStartTimeStart(OUTPUT_FORMATTER.format(startTime));
        queryParam.setStartTimeEnd(OUTPUT_FORMATTER.format(endTime));
        queryParam.setEventType(trimToNull(queryParam.getEventType()));
        queryParam.setPhase(trimToNull(queryParam.getPhase()));
        queryParam.setEngineeringName(trimToNull(queryParam.getEngineeringName()));
        queryParam.setProjectName(trimToNull(queryParam.getProjectName()));
        queryParam.setEquipmentName(trimToNull(queryParam.getEquipmentName()));
        queryParam.setLineName(trimToNull(queryParam.getLineName()));
        List<String> lineIds = normalizeIds(queryParam.getLineIds());
        if (lineIds.size() > EVENT_LINE_ID_QUERY_LIMIT) {
            throw new BusinessException(CommonResponseEnum.FAIL, "监测点 ID 查询数量不能超过 1000 个");
        }
        queryParam.setLineIds(lineIds);
        return queryParam;
    }

    /**
     * 台账关键字先转成测点 ID，避免在事件模块直接联查台账表。
     */
    private boolean resolveLineFilter(EventListQueryParam queryParam) {
        if (!hasLedgerKeyword(queryParam)) {
            return true;
        }
        AddLedgerLinePathQueryParam linePathQueryParam = new AddLedgerLinePathQueryParam();
        linePathQueryParam.setEngineeringName(queryParam.getEngineeringName());
        linePathQueryParam.setProjectName(queryParam.getProjectName());
        linePathQueryParam.setEquipmentName(queryParam.getEquipmentName());
        linePathQueryParam.setLineName(queryParam.getLineName());
        linePathQueryParam.setLimit(LEDGER_LINE_QUERY_LIMIT + 1);
        List<String> ledgerLineIds = addLedgerService.listLineIdsByPathQuery(linePathQueryParam);
        if (ledgerLineIds.size() > LEDGER_LINE_QUERY_LIMIT) {
            throw new BusinessException(CommonResponseEnum.FAIL, "台账检索匹配监测点过多，请缩小查询条件");
        }
        if (ledgerLineIds.isEmpty()) {
            return false;
        }
        List<String> explicitLineIds = normalizeIds(queryParam.getLineIds());
        if (explicitLineIds.isEmpty()) {
            queryParam.setLineIds(ledgerLineIds);
            return true;
        }
        List<String> intersectLineIds = new ArrayList<String>();
        for (String lineId : explicitLineIds) {
            if (ledgerLineIds.contains(lineId)) {
                intersectLineIds.add(lineId);
            }
        }
        queryParam.setLineIds(intersectLineIds);
        return !intersectLineIds.isEmpty();
    }

    private Page<EventListVO> emptyPage(EventListQueryParam queryParam) {
        Page<EventListVO> page = new Page<EventListVO>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam), 0);
        page.setRecords(Collections.<EventListVO>emptyList());
        return page;
    }

    private LocalDateTime parseDateTime(String value) {
        String text = trimToNull(value);
        if (text == null) {
            return null;
        }
        for (DateTimeFormatter formatter : INPUT_FORMATTERS) {
            try {
                return LocalDateTime.parse(text, formatter);
            } catch (DateTimeParseException ignored) {
                // 尝试下一种前端可能传入的时间格式。
            }
        }
        throw new BusinessException(CommonResponseEnum.FAIL, "时间格式不正确，仅支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm:ss.SSS");
    }

    private List<String> normalizeIds(List<String> ids) {
        if (ids == null || ids.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> normalizedIds = new ArrayList<String>();
        for (String id : ids) {
            String normalizedId = trimToNull(id);
            if (normalizedId != null && !normalizedIds.contains(normalizedId)) {
                normalizedIds.add(normalizedId);
            }
        }
        return normalizedIds;
    }

    private boolean hasLedgerKeyword(EventListQueryParam queryParam) {
        return trimToNull(queryParam.getEngineeringName()) != null
                || trimToNull(queryParam.getProjectName()) != null
                || trimToNull(queryParam.getEquipmentName()) != null
                || trimToNull(queryParam.getLineName()) != null;
    }

    private void validateRange(BigDecimal min, BigDecimal max, String message) {
        if (min != null && max != null && min.compareTo(max) > 0) {
            throw new BusinessException(CommonResponseEnum.FAIL, message);
        }
    }

    private BigDecimal toPercent(BigDecimal value) {
        return value == null ? null : value.multiply(PERCENT_BASE);
    }

    private BigDecimal fromPercent(BigDecimal value) {
        return value == null ? null : value.divide(PERCENT_BASE);
    }

    private void fillExportEventTypeNames(List<EventListVO> exportRecords) {
        Map<String, String> eventTypeNameMap = buildEventTypeNameMap();
        for (EventListVO record : exportRecords) {
            record.setEventType(translateEventType(record.getEventType(), eventTypeNameMap));
        }
    }

    private Map<String, String> buildEventTypeNameMap() {
        DictType dictType = dictTypeService.getByCode(DictConst.EVENT_TYPE_DICT);
        if (dictType == null) {
            dictType = dictTypeService.lambdaQuery()
                    .eq(DictType::getName, DictConst.EVENT_TYPE_DICT)
                    .eq(DictType::getState, DataStateEnum.ENABLE.getCode())
                    .orderByAsc(DictType::getSort)
                    .orderByDesc(DictType::getId)
                    .last("LIMIT 1")
                    .one();
        }
        if (dictType == null) {
            return Collections.emptyMap();
        }
        List<DictData> dictDataList = dictDataService.listDictDataByTypeId(dictType.getId());
        Map<String, String> eventTypeNameMap = new LinkedHashMap<String, String>();
        for (DictData dictData : dictDataList) {
            String id = trimToNull(dictData.getId());
            String code = trimToNull(dictData.getCode());
            if (id != null) {
                eventTypeNameMap.put(id, defaultText(dictData.getName(), id));
            }
            if (code != null) {
                eventTypeNameMap.put(code, defaultText(dictData.getName(), code));
            }
        }
        return eventTypeNameMap;
    }

    private String translateEventType(String eventType, Map<String, String> eventTypeNameMap) {
        String normalizedEventType = trimToNull(eventType);
        if (normalizedEventType == null) {
            return EMPTY_TEXT;
        }
        String eventTypeName = eventTypeNameMap.get(normalizedEventType);
        return eventTypeName == null ? normalizedEventType : eventTypeName;
    }

    private void validateFlag(Integer flag, String message) {
        if (flag != null && flag != 0 && flag != 1) {
            throw new BusinessException(CommonResponseEnum.FAIL, message);
        }
    }

    private void validateDealFlag(Integer dealFlag) {
        if (dealFlag != null && (dealFlag < 0 || dealFlag > 3)) {
            throw new BusinessException(CommonResponseEnum.FAIL, "处理状态只能是 0、1、2、3");
        }
    }

    private String defaultText(String value) {
        return defaultText(value, EMPTY_TEXT);
    }

    private String defaultText(String value, String defaultValue) {
        String text = trimToNull(value);
        return text == null ? defaultValue : text;
    }

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

    private static class WaveExportItem {

        private String eventId;

        private MpEventDetailPO eventDetail;

        private AddLedgerLinePathVO linePath;

        private EventListVO eventVO;

        private EventWavePathResolver.WaveFilePath waveFilePath;

        private String status = WAVE_EXPORT_FAIL;

        private String failReason;

        private String getEventId() {
            return eventId;
        }

        private void setEventId(String eventId) {
            this.eventId = eventId;
        }

        private MpEventDetailPO getEventDetail() {
            return eventDetail;
        }

        private void setEventDetail(MpEventDetailPO eventDetail) {
            this.eventDetail = eventDetail;
        }

        private AddLedgerLinePathVO getLinePath() {
            return linePath;
        }

        private void setLinePath(AddLedgerLinePathVO linePath) {
            this.linePath = linePath;
        }

        private EventListVO getEventVO() {
            return eventVO;
        }

        private void setEventVO(EventListVO eventVO) {
            this.eventVO = eventVO;
        }

        private EventWavePathResolver.WaveFilePath getWaveFilePath() {
            return waveFilePath;
        }

        private void setWaveFilePath(EventWavePathResolver.WaveFilePath waveFilePath) {
            this.waveFilePath = waveFilePath;
        }

        private String getStatus() {
            return status;
        }

        private String getFailReason() {
            return failReason;
        }

        private void success() {
            this.status = WAVE_EXPORT_SUCCESS;
            this.failReason = null;
        }

        private void fail(String failReason) {
            this.status = WAVE_EXPORT_FAIL;
            this.failReason = failReason;
        }
    }
}
