package com.njcn.gather.aireport.testdevice.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.aireport.testdevice.component.TestDeviceReportStorageService;
import com.njcn.gather.aireport.testdevice.mapper.TestDeviceMapper;
import com.njcn.gather.aireport.testdevice.pojo.constant.TestDeviceConst;
import com.njcn.gather.aireport.testdevice.pojo.param.TestDeviceParam;
import com.njcn.gather.aireport.testdevice.pojo.po.TestDevicePO;
import com.njcn.gather.aireport.testdevice.pojo.vo.TestDeviceExcelVO;
import com.njcn.gather.aireport.testdevice.pojo.vo.TestDeviceImportResultVO;
import com.njcn.gather.aireport.testdevice.pojo.vo.TestDeviceTypeNameVO;
import com.njcn.gather.aireport.testdevice.pojo.vo.TestDeviceVO;
import com.njcn.gather.aireport.testdevice.service.TestDeviceService;
import com.njcn.gather.system.util.ExportFileNameUtil;
import com.njcn.web.factory.PageFactory;
import com.njcn.web.utils.ExcelUtil;
import com.njcn.web.utils.HttpServletUtil;
import com.njcn.web.utils.RequestUtil;
import lombok.Data;
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.DataFormatter;
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 org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
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.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Slf4j
@Service
@RequiredArgsConstructor
public class TestDeviceServiceImpl extends ServiceImpl<TestDeviceMapper, TestDevicePO> implements TestDeviceService {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final String[] TEMPLATE_HEADERS = new String[]{"设备型号", "设备编号", "校准有效期", "设备状态", "校准报告文件名"};
    private static final long MAX_IMPORT_REPORT_SIZE = 10L * 1024L * 1024L;

    private final TestDeviceReportStorageService reportStorageService;

    @Override
    public Page<TestDeviceVO> listTestDevices(TestDeviceParam.QueryParam param) {
        TestDeviceParam.QueryParam query = normalizeQueryParam(param);
        Page<TestDeviceVO> page = this.baseMapper.selectDevicePage(
                new Page<TestDeviceVO>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), query);
        page.getRecords().forEach(this::fillReportDisplayFields);
        return page;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addTestDevice(TestDeviceParam.AddParam param, MultipartFile reportFile) {
        NormalizedDevice data = normalizeParam(param);
        checkDeviceType(data.getType());
        checkNoUnique(data.getNo(), null);
        String storedPath = reportStorageService.save(reportFile);
        try {
            LocalDateTime now = LocalDateTime.now();
            String userId = resolveCurrentUserId();
            TestDevicePO device = new TestDevicePO();
            device.setId(UUID.randomUUID().toString());
            device.setType(data.getType());
            device.setNo(data.getNo());
            device.setValidityPeriod(data.getValidityPeriod());
            device.setValidityReport(storedPath);
            device.setState(data.getState());
            device.setStatus(TestDeviceConst.STATUS_NORMAL);
            device.setCreateBy(userId);
            device.setCreateTime(now);
            device.setUpdateBy(userId);
            device.setUpdateTime(now);
            boolean saved = this.save(device);
            if (!saved) {
                reportStorageService.deleteQuietly(storedPath);
            }
            return saved;
        } catch (RuntimeException exception) {
            reportStorageService.deleteQuietly(storedPath);
            throw exception;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateTestDevice(TestDeviceParam.UpdateParam param, MultipartFile reportFile) {
        if (param == null || StrUtil.isBlank(param.getId())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "检测设备ID不能为空");
        }
        TestDevicePO device = requireNormal(param.getId());
        NormalizedDevice data = normalizeParam(param);
        checkDeviceType(data.getType());
        checkNoUnique(data.getNo(), device.getId());
        String oldReport = device.getValidityReport();
        String newReport = null;
        if (reportFile != null && !reportFile.isEmpty()) {
            newReport = reportStorageService.save(reportFile);
            device.setValidityReport(newReport);
        } else if (StrUtil.isBlank(oldReport)) {
            throw new BusinessException(CommonResponseEnum.FAIL, "校准报告不能为空");
        }
        try {
            device.setType(data.getType());
            device.setNo(data.getNo());
            device.setValidityPeriod(data.getValidityPeriod());
            device.setState(data.getState());
            device.setUpdateBy(resolveCurrentUserId());
            device.setUpdateTime(LocalDateTime.now());
            boolean updated = this.updateById(device);
            if (!updated) {
                reportStorageService.deleteQuietly(newReport);
                return false;
            }
            if (StrUtil.isNotBlank(newReport)) {
                reportStorageService.deleteQuietly(oldReport);
            }
            return true;
        } catch (RuntimeException exception) {
            reportStorageService.deleteQuietly(newReport);
            throw exception;
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteTestDevices(List<String> ids) {
        List<String> validIds = normalizeIds(ids);
        if (validIds.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "检测设备ID不能为空");
        }
        List<TestDevicePO> devices = this.lambdaQuery()
                .in(TestDevicePO::getId, validIds)
                .eq(TestDevicePO::getStatus, TestDeviceConst.STATUS_NORMAL)
                .list();
        boolean deleted = this.lambdaUpdate()
                .set(TestDevicePO::getStatus, TestDeviceConst.STATUS_DELETED)
                .set(TestDevicePO::getUpdateBy, resolveCurrentUserId())
                .set(TestDevicePO::getUpdateTime, LocalDateTime.now())
                .in(TestDevicePO::getId, validIds)
                .eq(TestDevicePO::getStatus, TestDeviceConst.STATUS_NORMAL)
                .update();
        if (deleted) {
            for (TestDevicePO device : devices) {
                reportStorageService.deleteQuietly(device.getValidityReport());
            }
        }
        return deleted;
    }

    @Override
    public TestDeviceVO getTestDevice(String id) {
        TestDeviceVO vo = this.baseMapper.selectDeviceDetail(requireText(id, "检测设备ID不能为空"));
        if (vo == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "检测设备不存在或已删除");
        }
        fillReportDisplayFields(vo);
        return vo;
    }

    @Override
    public TestDevicePO requireNormal(String id) {
        String normalizedId = requireText(id, "检测设备ID不能为空");
        TestDevicePO device = this.lambdaQuery()
                .eq(TestDevicePO::getId, normalizedId)
                .eq(TestDevicePO::getStatus, TestDeviceConst.STATUS_NORMAL)
                .one();
        if (device == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "检测设备不存在或已删除");
        }
        return device;
    }

    @Override
    public void downloadTemplate() {
        HttpServletResponse response = HttpServletUtil.getResponse();
        try (XSSFWorkbook workbook = new XSSFWorkbook()) {
            XSSFSheet sheet = workbook.createSheet("检测设备导入模板");
            writeTemplateHeader(workbook, sheet);
            String fileName = URLEncoder.encode(ExportFileNameUtil.appendToday("检测设备导入模板.xlsx"), "UTF-8");
            response.reset();
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
            try (ServletOutputStream outputStream = response.getOutputStream()) {
                workbook.write(outputStream);
                outputStream.flush();
            }
        } catch (IOException exception) {
            log.error("下载检测设备导入模板失败", exception);
            throw new BusinessException(CommonResponseEnum.FAIL, "下载检测设备导入模板失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TestDeviceImportResultVO importTestDevices(MultipartFile file, MultipartFile reportZip) {
        if (file == null || file.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "导入文件不能为空");
        }
        if (reportZip == null || reportZip.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "校准报告压缩包不能为空");
        }
        List<TestDeviceExcelVO> rows = readImportRows(file);
        TestDeviceImportResultVO result = new TestDeviceImportResultVO();
        if (rows.isEmpty()) {
            return result;
        }

        ImportArchive archive = readReportZip(reportZip);
        try {
            Map<String, String> typeNameIdMap = loadTypeNameIdMap();
            List<NormalizedImportDevice> devices = validateImportRows(rows, archive, typeNameIdMap, result);
            if (result.getFailCount() > 0) {
                return result;
            }

            List<String> savedReports = new ArrayList<String>();
            try {
                LocalDateTime now = LocalDateTime.now();
                String userId = resolveCurrentUserId();
                List<TestDevicePO> saveRows = new ArrayList<TestDevicePO>();
                for (NormalizedImportDevice data : devices) {
                    String storedPath;
                    try (InputStream inputStream = Files.newInputStream(data.getReportPath())) {
                        storedPath = reportStorageService.savePdf(data.getReportName(), inputStream, Files.size(data.getReportPath()));
                    } catch (IOException exception) {
                        throw new BusinessException(CommonResponseEnum.FAIL, "读取校准报告压缩包失败");
                    }
                    savedReports.add(storedPath);

                    TestDevicePO device = new TestDevicePO();
                    device.setId(UUID.randomUUID().toString());
                    device.setType(data.getType());
                    device.setNo(data.getNo());
                    device.setValidityPeriod(data.getValidityPeriod());
                    device.setValidityReport(storedPath);
                    device.setState(data.getState());
                    device.setStatus(TestDeviceConst.STATUS_NORMAL);
                    device.setCreateBy(userId);
                    device.setCreateTime(now);
                    device.setUpdateBy(userId);
                    device.setUpdateTime(now);
                    saveRows.add(device);
                }

                boolean saved = saveRows.isEmpty() || this.saveBatch(saveRows);
                if (!saved) {
                    savedReports.forEach(reportStorageService::deleteQuietly);
                    return result;
                }
                result.setSuccessCount(saveRows.size());
                return result;
            } catch (RuntimeException exception) {
                savedReports.forEach(reportStorageService::deleteQuietly);
                throw exception;
            }
        } finally {
            archive.cleanup();
        }
    }

    @Override
    public void exportTestDevices(TestDeviceParam.QueryParam param) {
        TestDeviceParam.QueryParam query = normalizeQueryParam(param);
        Page<TestDeviceVO> page = this.baseMapper.selectDevicePage(new Page<TestDeviceVO>(1, Integer.MAX_VALUE), query);
        List<TestDeviceExcelVO> exportRows = page.getRecords().stream()
                .peek(this::fillReportDisplayFields)
                .map(this::toExcelVO)
                .collect(Collectors.toList());
        ExcelUtil.exportExcel(ExportFileNameUtil.appendToday("检测设备列表.xlsx"), "检测设备列表",
                TestDeviceExcelVO.class, exportRows);
    }

    private TestDeviceParam.QueryParam normalizeQueryParam(TestDeviceParam.QueryParam param) {
        TestDeviceParam.QueryParam query = param == null ? new TestDeviceParam.QueryParam() : param;
        query.setKeyword(trimToNull(query.getKeyword()));
        if (StrUtil.isNotBlank(query.getSearchBeginTime())) {
            query.setSearchBeginTime(formatDateTime(parseDateTime(query.getSearchBeginTime(), "开始时间格式不正确")));
        }
        if (StrUtil.isNotBlank(query.getSearchEndTime())) {
            query.setSearchEndTime(formatDateTime(parseDateTime(query.getSearchEndTime(), "结束时间格式不正确")));
        }
        return query;
    }

    private NormalizedDevice normalizeParam(TestDeviceParam.AddParam param) {
        if (param == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "检测设备参数不能为空");
        }
        NormalizedDevice data = new NormalizedDevice();
        data.setType(requireText(param.getType(), "设备型号不能为空"));
        data.setNo(requireText(param.getNo(), "设备编号不能为空", 64, "设备编号不能超过64个字符"));
        data.setValidityPeriod(parseDate(requireText(param.getValidityPeriod(), "校准有效期不能为空")));
        data.setState(normalizeState(param.getState()));
        return data;
    }

    private List<NormalizedImportDevice> validateImportRows(List<TestDeviceExcelVO> rows, ImportArchive archive,
                                                            Map<String, String> typeNameIdMap, TestDeviceImportResultVO result) {
        Set<String> fileNos = new HashSet<String>();
        List<NormalizedImportDevice> devices = new ArrayList<NormalizedImportDevice>();
        for (int i = 0; i < rows.size(); i++) {
            int rowNumber = i + 2;
            try {
                TestDeviceExcelVO row = rows.get(i);
                NormalizedImportDevice device = new NormalizedImportDevice();
                String typeName = requireText(row.getTypeName(), "设备型号不能为空");
                String typeId = typeNameIdMap.get(typeName);
                if (StrUtil.isBlank(typeId)) {
                    throw new BusinessException(CommonResponseEnum.FAIL, "设备型号不存在或已删除");
                }
                device.setType(typeId);
                device.setNo(requireText(row.getNo(), "设备编号不能为空", 64, "设备编号不能超过64个字符"));
                if (!fileNos.add(device.getNo())) {
                    throw new BusinessException(CommonResponseEnum.FAIL, "文件内存在重复设备编号");
                }
                checkNoUnique(device.getNo(), null);
                device.setValidityPeriod(parseDate(requireText(row.getValidityPeriod(), "校准有效期不能为空")));
                device.setState(normalizeState(row.getStateName()));
                device.setReportName(requireText(row.getValidityReportName(), "校准报告文件名不能为空"));
                Path reportPath = archive.getReportPath(device.getReportName());
                if (reportPath == null) {
                    throw new BusinessException(CommonResponseEnum.FAIL, "校准报告压缩包中未找到对应PDF");
                }
                device.setReportPath(reportPath);
                devices.add(device);
            } catch (BusinessException exception) {
                result.setFailCount(result.getFailCount() + 1);
                result.getFailReasons().add("第" + rowNumber + "行：" + exception.getMessage());
            }
        }
        return devices;
    }

    private List<TestDeviceExcelVO> readImportRows(MultipartFile file) {
        try (InputStream inputStream = file.getInputStream();
             XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
            XSSFSheet sheet = workbook.getNumberOfSheets() == 0 ? null : workbook.getSheetAt(0);
            if (sheet == null || sheet.getLastRowNum() < 1) {
                return Collections.emptyList();
            }
            List<TestDeviceExcelVO> rows = new ArrayList<TestDeviceExcelVO>();
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (isEmptyRow(row)) {
                    continue;
                }
                TestDeviceExcelVO excelVO = new TestDeviceExcelVO();
                excelVO.setTypeName(readCell(row, 0));
                excelVO.setNo(readCell(row, 1));
                excelVO.setValidityPeriod(readCell(row, 2));
                excelVO.setStateName(readCell(row, 3));
                excelVO.setValidityReportName(readCell(row, 4));
                rows.add(excelVO);
            }
            return rows;
        } catch (IOException exception) {
            log.error("读取检测设备导入文件失败", exception);
            throw new BusinessException(CommonResponseEnum.FAIL, "读取导入文件失败");
        }
    }

    ImportArchive readReportZip(MultipartFile reportZip) {
        Path tempDir;
        try {
            tempDir = Files.createTempDirectory("test-device-import-");
        } catch (IOException exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, "读取校准报告压缩包失败");
        }

        Map<String, Path> files = new HashMap<String, Path>();
        try (ZipInputStream zipInputStream = new ZipInputStream(reportZip.getInputStream())) {
            ZipEntry entry;
            while ((entry = zipInputStream.getNextEntry()) != null) {
                if (entry.isDirectory()) {
                    continue;
                }
                String fileName = Paths.get(entry.getName()).getFileName().toString();
                if (!fileName.toLowerCase().endsWith(".pdf")) {
                    throw new BusinessException(CommonResponseEnum.FAIL, "校准报告压缩包仅允许包含PDF文件");
                }
                if (files.containsKey(fileName)) {
                    throw new BusinessException(CommonResponseEnum.FAIL, "校准报告压缩包中存在重复文件名");
                }
                files.put(fileName, readZipEntryToFile(tempDir, fileName, zipInputStream));
            }
            return new ImportArchive(tempDir, files);
        } catch (BusinessException exception) {
            deleteDirectoryQuietly(tempDir);
            throw exception;
        } catch (IOException exception) {
            deleteDirectoryQuietly(tempDir);
            log.error("读取校准报告压缩包失败", exception);
            throw new BusinessException(CommonResponseEnum.FAIL, "读取校准报告压缩包失败");
        }
    }

    private Path readZipEntryToFile(Path tempDir, String fileName, ZipInputStream zipInputStream) throws IOException {
        Path tempFile = tempDir.resolve(UUID.randomUUID().toString().replace("-", "") + "-" + fileName).normalize();
        long bytes = 0L;
        byte[] buffer = new byte[8192];
        try (OutputStream outputStream = Files.newOutputStream(tempFile)) {
            int length;
            while ((length = zipInputStream.read(buffer)) != -1) {
                bytes += length;
                if (bytes > MAX_IMPORT_REPORT_SIZE) {
                    throw new BusinessException(CommonResponseEnum.FAIL, "校准报告不能超过10MB");
                }
                outputStream.write(buffer, 0, length);
            }
        } catch (BusinessException exception) {
            Files.deleteIfExists(tempFile);
            throw exception;
        } catch (IOException exception) {
            Files.deleteIfExists(tempFile);
            throw exception;
        }
        return tempFile;
    }

    private void deleteDirectoryQuietly(Path directory) {
        if (directory == null || !Files.exists(directory)) {
            return;
        }
        try (Stream<Path> pathStream = Files.walk(directory)) {
            pathStream.sorted(Comparator.reverseOrder()).forEach(path -> {
                try {
                    Files.deleteIfExists(path);
                } catch (IOException ignored) {
                    // 导入临时目录清理失败不影响主流程返回。
                }
            });
        } catch (IOException ignored) {
            // 导入临时目录清理失败不影响主流程返回。
        }
    }

    private void writeTemplateHeader(XSSFWorkbook workbook, XSSFSheet sheet) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(true);
        style.setFont(font);
        Row row = sheet.createRow(0);
        for (int i = 0; i < TEMPLATE_HEADERS.length; i++) {
            Cell cell = row.createCell(i);
            cell.setCellValue(TEMPLATE_HEADERS[i]);
            cell.setCellStyle(style);
            sheet.setColumnWidth(i, 25 * 256);
        }
    }

    private boolean isEmptyRow(Row row) {
        if (row == null) {
            return true;
        }
        for (int i = 0; i < TEMPLATE_HEADERS.length; i++) {
            if (StrUtil.isNotBlank(readCell(row, i))) {
                return false;
            }
        }
        return true;
    }

    private String readCell(Row row, int index) {
        Cell cell = row.getCell(index);
        return cell == null ? null : trimToNull(new DataFormatter().formatCellValue(cell));
    }

    private void fillReportDisplayFields(TestDeviceVO vo) {
        if (vo == null || StrUtil.isBlank(vo.getValidityReport())) {
            return;
        }
        vo.setValidityReportName(reportStorageService.resolveDownloadFileName(vo.getValidityReport()));
        vo.setValidityReportUrl("/api/test-device/" + vo.getId() + "/report");
    }

    private TestDeviceExcelVO toExcelVO(TestDeviceVO vo) {
        TestDeviceExcelVO excelVO = new TestDeviceExcelVO();
        excelVO.setTypeName(vo.getTypeName());
        excelVO.setNo(vo.getNo());
        excelVO.setValidityPeriod(vo.getValidityPeriod() == null ? null : DATE_FORMATTER.format(vo.getValidityPeriod()));
        excelVO.setStateName(resolveStateName(vo.getState()));
        excelVO.setValidityReportName(vo.getValidityReportName());
        excelVO.setCreateByName(StrUtil.isNotBlank(vo.getCreateByName()) ? vo.getCreateByName() : vo.getCreateBy());
        excelVO.setCreateTime(vo.getCreateTime() == null ? null : DATE_TIME_FORMATTER.format(vo.getCreateTime()));
        return excelVO;
    }

    private Map<String, String> loadTypeNameIdMap() {
        List<TestDeviceTypeNameVO> types = this.baseMapper.selectNormalDeviceTypes();
        if (types == null || types.isEmpty()) {
            return Collections.emptyMap();
        }
        return types.stream()
                .filter(type -> type != null && StrUtil.isNotBlank(type.getName()) && StrUtil.isNotBlank(type.getId()))
                .collect(Collectors.toMap(TestDeviceTypeNameVO::getName, TestDeviceTypeNameVO::getId, (first, second) -> first));
    }

    private void checkDeviceType(String type) {
        if (this.baseMapper.countNormalDeviceType(type) <= 0) {
            throw new BusinessException(CommonResponseEnum.FAIL, "设备型号不存在或已删除");
        }
    }

    private void checkNoUnique(String no, String excludeId) {
        LambdaQueryWrapper<TestDevicePO> wrapper = new LambdaQueryWrapper<TestDevicePO>();
        wrapper.eq(TestDevicePO::getNo, no)
                .ne(StrUtil.isNotBlank(excludeId), TestDevicePO::getId, excludeId);
        if (this.count(wrapper) > 0) {
            throw new BusinessException(CommonResponseEnum.FAIL, "设备编号已存在");
        }
    }

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

    private LocalDate parseDate(String value) {
        try {
            return LocalDate.parse(value.trim(), DATE_FORMATTER);
        } catch (DateTimeParseException exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, "校准有效期格式不正确");
        }
    }

    private LocalDateTime parseDateTime(String value, String errorMessage) {
        try {
            return LocalDateTime.parse(value.trim(), DATE_TIME_FORMATTER);
        } catch (DateTimeParseException exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, errorMessage);
        }
    }

    private String formatDateTime(LocalDateTime value) {
        return DATE_TIME_FORMATTER.format(value);
    }

    private String normalizeState(String state) {
        String text = trimToNull(state);
        if (text == null || "在运".equals(text) || TestDeviceConst.STATE_RUNNING.equals(text)) {
            return TestDeviceConst.STATE_RUNNING;
        }
        if ("退运".equals(text) || TestDeviceConst.STATE_RETIRED.equals(text)) {
            return TestDeviceConst.STATE_RETIRED;
        }
        throw new BusinessException(CommonResponseEnum.FAIL, "设备状态不正确");
    }

    private String resolveStateName(String state) {
        if (TestDeviceConst.STATE_RUNNING.equals(state)) {
            return "在运";
        }
        if (TestDeviceConst.STATE_RETIRED.equals(state)) {
            return "退运";
        }
        return state;
    }

    private String requireText(String value, String blankMessage) {
        String text = trimToNull(value);
        if (text == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, blankMessage);
        }
        return text;
    }

    private String requireText(String value, String blankMessage, int maxLength, String lengthMessage) {
        String text = requireText(value, blankMessage);
        if (text.length() > maxLength) {
            throw new BusinessException(CommonResponseEnum.FAIL, lengthMessage);
        }
        return text;
    }

    private String resolveCurrentUserId() {
        String userId = RequestUtil.getUserId();
        return StrUtil.isBlank(userId) ? null : userId;
    }

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

    @Data
    private static class NormalizedDevice {
        private String type;
        private String no;
        private LocalDate validityPeriod;
        private String state;
    }

    @Data
    private static class NormalizedImportDevice extends NormalizedDevice {
        private String reportName;
        private Path reportPath;
    }

    static class ImportArchive {
        private final Path tempDir;
        private final Map<String, Path> reportFiles;

        ImportArchive(Path tempDir, Map<String, Path> reportFiles) {
            this.tempDir = tempDir;
            this.reportFiles = reportFiles;
        }

        Path getReportPath(String fileName) {
            return reportFiles.get(fileName);
        }

        void cleanup() {
            if (tempDir == null || !Files.exists(tempDir)) {
                return;
            }
            try (Stream<Path> pathStream = Files.walk(tempDir)) {
                pathStream.sorted(Comparator.reverseOrder()).forEach(path -> {
                    try {
                        Files.deleteIfExists(path);
                    } catch (IOException ignored) {
                        // 导入临时文件清理失败不影响主流程返回。
                    }
                });
            } catch (IOException ignored) {
                // 导入临时目录清理失败不影响主流程返回。
            }
        }
    }
}
