package org.dromara.quality.service.impl;

import cn.hutool.core.collection.CollUtil;
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 jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.*;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.ObjectUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.project.domain.BusConstructionUser;
import org.dromara.project.domain.BusProject;
import org.dromara.project.service.IBusConstructionUserService;
import org.dromara.project.service.IBusProjectService;
import org.dromara.quality.constant.QltQualityConstant;
import org.dromara.quality.domain.QltQualityInspection;
import org.dromara.quality.domain.enums.QltQualityInspectionStatusEnum;
import org.dromara.quality.domain.enums.QltQualityInspectionVerificationTypeEnum;
import org.dromara.quality.domain.dto.qualityinspection.QltQualityInspectionCreateReq;
import org.dromara.quality.domain.dto.qualityinspection.QltQualityInspectionGisReq;
import org.dromara.quality.domain.dto.qualityinspection.QltQualityInspectionQueryReq;
import org.dromara.quality.domain.dto.qualityinspection.QltQualityInspectionUpdateReq;
import org.dromara.quality.domain.vo.qualityinspection.QltQualityInspectionGis;
import org.dromara.quality.domain.vo.qualityinspection.QltQualityInspectionListGisVo;
import org.dromara.quality.domain.vo.qualityinspection.QltQualityInspectionVo;
import org.dromara.quality.mapper.QltQualityInspectionMapper;
import org.dromara.quality.service.IQltQualityInspectionService;
import org.dromara.system.domain.vo.SysDictDataVo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysDictDataService;
import org.dromara.system.service.ISysDictTypeService;
import org.dromara.system.service.ISysOssService;
import org.dromara.system.service.ISysUserService;
import org.dromara.utils.DocumentUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipOutputStream;

/**
 * 质量-检查工单Service业务层处理
 *
 * @author lcj
 * @date 2025-04-16
 */
@Slf4j
@Service
public class QltQualityInspectionServiceImpl extends ServiceImpl<QltQualityInspectionMapper, QltQualityInspection>
    implements IQltQualityInspectionService {

    @Resource
    private IBusProjectService projectService;

    @Resource
    private ISysUserService userService;

    @Resource
    private ISysOssService ossService;

    @Resource
    private IBusConstructionUserService constructionUserService;

    @Resource
    private ISysDictDataService dictDataService;

    @Resource
    private ISysDictTypeService dictTypeService;

    /**
     * 查询质量-检查工单
     *
     * @param id 主键
     * @return 质量-检查工单
     */
    @Override
    public QltQualityInspectionVo queryById(Long id) {
        QltQualityInspection qualityInspection = this.getById(id);
        if (qualityInspection == null) {
            throw new ServiceException("检查工单信息不存在", HttpStatus.NOT_FOUND);
        }
        return this.getVo(qualityInspection);
    }

    /**
     * 分页查询质量-检查工单列表
     *
     * @param req       查询条件
     * @param pageQuery 分页参数
     * @return 质量-检查工单分页列表
     */
    @Override
    public TableDataInfo<QltQualityInspectionVo> queryPageList(QltQualityInspectionQueryReq req, PageQuery pageQuery) {
        Page<QltQualityInspection> result = this.page(pageQuery.build(), buildQueryWrapper(req));
        return TableDataInfo.build(this.getVoPage(result));
    }

    /**
     * 查询符合条件的质量-检查工单列表
     *
     * @param req 查询条件
     * @return 质量-检查工单列表
     */
    @Override
    public List<QltQualityInspectionVo> queryList(QltQualityInspectionQueryReq req) {
        LambdaQueryWrapper<QltQualityInspection> lqw = buildQueryWrapper(req);
        return this.list(lqw).stream().map(this::getVo).toList();
    }

    /**
     * 查询大屏质量-检查工单列表
     *
     * @param req 查询条件
     * @return 质量-检查工单列表
     */
    @Override
    public QltQualityInspectionListGisVo queryGisList(QltQualityInspectionGisReq req) {
        Long projectId = req.getProjectId();
        if (projectId == null || projectService.getById(projectId) == null) {
            throw new ServiceException("项目信息不存在", HttpStatus.NOT_FOUND);
        }
        QltQualityInspectionListGisVo gisVo = new QltQualityInspectionListGisVo();
        List<QltQualityInspection> qualityInspectionList = this.lambdaQuery()
            .eq(QltQualityInspection::getProjectId, projectId)
            .list();
        if (CollUtil.isEmpty(qualityInspectionList)) {
            return gisVo;
        }
        // 获取字典值
        List<SysDictDataVo> dictDataVoList = dictTypeService.selectDictDataByType(QltQualityConstant.QUALITY_INSPECTION_CHECK_TYPE);
        Map<String, String> dictDataMap = dictDataVoList.stream().collect(Collectors.toMap(SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel));
        // 获取最新的检查工单
        List<QltQualityInspection> topList = qualityInspectionList.stream()
            .sorted(Comparator.comparing(QltQualityInspection::getCreateTime).reversed())
            .limit(Optional.ofNullable(req.getPageSize()).orElse(20))
            .toList();
        // 转换为 GIS 对象，并设置标签
        List<QltQualityInspectionGis> gisList = topList.stream().map(qualityInspection -> {
            QltQualityInspectionGis gis = new QltQualityInspectionGis();
            BeanUtils.copyProperties(qualityInspection, gis);
            String label = dictDataMap.get(qualityInspection.getInspectionType());
            if (label != null) {
                gis.setInspectionTypeLabel(label);
            }
            return gis;
        }).toList();
        // 获取整改情况
        long passCount = 0L;
        for (QltQualityInspection qualityInspection : qualityInspectionList) {
            if (QltQualityInspectionStatusEnum.VERIFICATION.getValue().equals(qualityInspection.getInspectionStatus())
                && QltQualityInspectionVerificationTypeEnum.PASS.getValue().equals(qualityInspection.getVerificationType())) {
                passCount++;
            }
        }
        gisVo.setList(gisList);
        gisVo.setCount((long) qualityInspectionList.size());
        gisVo.setCorrectSituation(String.format("%.2f", passCount * 100.0 / qualityInspectionList.size()));
        return gisVo;
    }

    /**
     * 新增质量-检查工单
     *
     * @param req 质量-检查工单
     * @return 新增检查工单id
     */
    @Override
    public Long insertByBo(QltQualityInspectionCreateReq req) {
        // 将实体类和 DTO 进行转换
        QltQualityInspection qualityInspection = new QltQualityInspection();
        BeanUtils.copyProperties(req, qualityInspection);
        // 数据校验
        validEntityBeforeSave(qualityInspection, true);
        // 填充默认值
        qualityInspection.setInspectionStatus(QltQualityInspectionStatusEnum.INFORM.getValue());
        // 写入数据库
        boolean save = this.save(qualityInspection);
        if (!save) {
            throw new ServiceException("新增检查工单信息失败，数据库异常", HttpStatus.ERROR);
        }
        // 返回新写入的数据
        return qualityInspection.getId();
    }

    /**
     * 修改质量-检查工单
     *
     * @param req 质量-检查工单
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(QltQualityInspectionUpdateReq req) {
        // 将实体类和 DTO 进行转换
        QltQualityInspection qualityInspection = new QltQualityInspection();
        BeanUtils.copyProperties(req, qualityInspection);
        // 数据校验
        validEntityBeforeSave(qualityInspection, false);
        // 判断是否存在
        QltQualityInspection oldQualityInspection = this.getById(qualityInspection.getId());
        if (oldQualityInspection == null) {
            throw new ServiceException("修改检查工单信息失败，数据不存在", HttpStatus.NOT_FOUND);
        }
        // 操作数据库
        return this.updateById(qualityInspection);
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(QltQualityInspection entity, Boolean create) {
        // TODO 做一些数据校验,如唯一约束
        Long projectId = entity.getProjectId();
        if (create) {
            if (projectId == null) {
                throw new ServiceException("项目id不能为空", HttpStatus.BAD_REQUEST);
            }
        }
        if (projectId != null && projectService.getById(projectId) == null) {
            throw new ServiceException("对应项目不存在", HttpStatus.NOT_FOUND);
        }
    }

    /**
     * 校验并批量删除质量-检查工单信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        Long userId = LoginHelper.getUserId();
        List<QltQualityInspection> qualityInspectionList = this.listByIds(ids);
        if (isValid) {
            // TODO 做一些业务上的校验,判断是否需要校验
            List<Long> projectId = qualityInspectionList.stream().map(QltQualityInspection::getProjectId).toList();
            projectService.validAuth(projectId, userId);
        }
        return this.removeBatchByIds(ids);
    }

    /**
     * 获取质量-检查工单视图对象
     *
     * @param qualityInspection 质量-检查工单对象
     * @return 质量-检查工单视图对象
     */
    @Override
    public QltQualityInspectionVo getVo(QltQualityInspection qualityInspection) {
        // 对象转封装类
        QltQualityInspectionVo qualityInspectionVo = new QltQualityInspectionVo();
        if (qualityInspection == null) {
            return qualityInspectionVo;
        }
        BeanUtils.copyProperties(qualityInspection, qualityInspectionVo);
        // 关联项目信息
        Long projectId = qualityInspection.getProjectId();
        if (projectId != null) {
            qualityInspectionVo.setProjectName(projectService.getById(projectId).getProjectName());
        }
        // 关联创建用户信息
        Long createBy = qualityInspection.getCreateBy();
        if (createBy != null) {
            SysUserVo sysUserVo = userService.selectUserById(createBy);
            qualityInspectionVo.setCreateBy(sysUserVo.getNickName());
        }
        // 关联整改人信息
        Long corrector = qualityInspection.getCorrector();
        if (corrector != null) {
            String userName = constructionUserService.getById(corrector).getUserName();
            qualityInspectionVo.setCorrectorName(userName);
        }
        // 关联附件信息
        String inspectionFile = qualityInspection.getInspectionFile();
        if (StringUtils.isNotBlank(inspectionFile)) {
            List<Long> ossIdList = Arrays.stream(inspectionFile.split(",")).map(Long::parseLong).toList();
            List<SysOssVo> ossVoList = ossService.listByIds(ossIdList);
            qualityInspectionVo.setInspectionFileList(ossVoList);
        }
        String rectificationFile = qualityInspection.getRectificationFile();
        if (StringUtils.isNotBlank(rectificationFile)) {
            List<Long> ossIdList = Arrays.stream(rectificationFile.split(",")).map(Long::parseLong).toList();
            List<SysOssVo> ossVoList = ossService.listByIds(ossIdList);
            qualityInspectionVo.setRectificationFileList(ossVoList);
        }
        return qualityInspectionVo;
    }

    /**
     * 获取质量-检查工单查询条件封装
     *
     * @param req 质量-检查工单查询条件
     * @return 质量-检查工单查询条件封装
     */
    @Override
    public LambdaQueryWrapper<QltQualityInspection> buildQueryWrapper(QltQualityInspectionQueryReq req) {
        LambdaQueryWrapper<QltQualityInspection> lqw = new LambdaQueryWrapper<>();
        if (req == null) {
            return lqw;
        }
        Long projectId = req.getProjectId();
        String inspectionType = req.getInspectionType();
        String inspectionStatus = req.getInspectionStatus();
        Long teamId = req.getTeamId();
        // 精确查询
        lqw.eq(ObjectUtils.isNotEmpty(projectId), QltQualityInspection::getProjectId, projectId);
        lqw.eq(ObjectUtils.isNotEmpty(inspectionType), QltQualityInspection::getInspectionType, inspectionType);
        lqw.eq(ObjectUtils.isNotEmpty(inspectionStatus), QltQualityInspection::getInspectionStatus, inspectionStatus);
        lqw.eq(ObjectUtils.isNotEmpty(teamId), QltQualityInspection::getTeamId, teamId);
        return lqw;
    }

    /**
     * 获取质量-检查工单分页对象视图
     *
     * @param qualityInspectionPage 质量-检查工单分页对象
     * @return 质量-检查工单分页对象视图
     */
    @Override
    public Page<QltQualityInspectionVo> getVoPage(Page<QltQualityInspection> qualityInspectionPage) {
        // 获取质量-检查工单列表
        List<QltQualityInspection> qualityInspectionList = qualityInspectionPage.getRecords();
        // 添加分页信息
        Page<QltQualityInspectionVo> qualityInspectionVoPage = new Page<>(
            qualityInspectionPage.getCurrent(),
            qualityInspectionPage.getSize(),
            qualityInspectionPage.getTotal());
        if (CollUtil.isEmpty(qualityInspectionList)) {
            return qualityInspectionVoPage;
        }
        // 获取项目名称
        List<Long> projectIdList = qualityInspectionList.stream().map(QltQualityInspection::getProjectId).distinct().toList();
        if (projectIdList.size() != 1) {
            throw new ServiceException("仅能查询单个项目下的施工日志", HttpStatus.BAD_REQUEST);
        }
        BusProject project = projectService.getById(projectIdList.get(0));
        if (project == null) {
            throw new ServiceException("项目不存在", HttpStatus.NOT_FOUND);
        }
        // 获取整改人信息
        List<Long> correctorList = qualityInspectionList.stream().map(QltQualityInspection::getCorrector).distinct().toList();
        Map<Long, String> correctorMap = constructionUserService.listByIds(correctorList)
            .stream().collect(Collectors.toMap(BusConstructionUser::getId, BusConstructionUser::getUserName));
        // 获取创建用户信息
        List<Long> createByList = qualityInspectionList.stream().map(QltQualityInspection::getCreateBy).distinct().toList();
        List<SysUserVo> userVoList = userService.selectUserByIds(createByList, null);
        Map<Long, String> userMap = userVoList.stream().collect(Collectors.toMap(SysUserVo::getUserId, SysUserVo::getNickName));
        // 获取附件信息
        List<Long> ossInspectionFileIdList = qualityInspectionList.stream().map(QltQualityInspection::getInspectionFile).filter(StringUtils::isNotBlank)
            .flatMap(fileId -> Arrays.stream(fileId.split(",")).map(Long::parseLong)).distinct().toList();
        List<Long> ossIdList = new ArrayList<>(ossInspectionFileIdList);
        List<Long> ossRectificationFileIdList = qualityInspectionList.stream().map(QltQualityInspection::getRectificationFile).filter(StringUtils::isNotBlank)
            .flatMap(fileId -> Arrays.stream(fileId.split(",")).map(Long::parseLong)).distinct().toList();
        ossIdList.addAll(ossRectificationFileIdList);
        Map<Long, List<SysOssVo>> ossMap = ossService.listByIds(ossIdList)
            .stream().collect(Collectors.groupingBy(SysOssVo::getOssId));
        // 对象列表 => 封装对象列表
        List<QltQualityInspectionVo> qualityInspectionVoList = qualityInspectionList.stream().map(qualityInspection -> {
            QltQualityInspectionVo qualityInspectionVo = new QltQualityInspectionVo();
            BeanUtils.copyProperties(qualityInspection, qualityInspectionVo);
            qualityInspectionVo.setProjectName(project.getProjectName());
            // 关联整改人信息
            Long corrector = qualityInspection.getCorrector();
            String correctorName = null;
            if (correctorMap.containsKey(corrector)) {
                correctorName = correctorMap.get(corrector);
            }
            qualityInspectionVo.setCorrectorName(correctorName);
            // 关联创建用户信息
            Long createBy = qualityInspection.getCreateBy();
            String createByName = null;
            if (userMap.containsKey(createBy)) {
                createByName = userMap.get(createBy);
            }
            qualityInspectionVo.setCreateBy(createByName);
            // 关联附件信息
            String inspectionFile = qualityInspection.getInspectionFile();
            List<SysOssVo> inspectionFileList = new ArrayList<>();
            if (StringUtils.isNotBlank(inspectionFile)) {
                List<Long> inspectionFileIdList = Arrays.stream(inspectionFile.split(",")).map(Long::parseLong).toList();
                for (Long id : inspectionFileIdList) {
                    if (ossMap.containsKey(id)) {
                        inspectionFileList.add(ossMap.get(id).get(0));
                    }
                }
            }
            qualityInspectionVo.setInspectionFileList(inspectionFileList);
            String rectificationFile = qualityInspection.getRectificationFile();
            List<SysOssVo> rectificationFileList = new ArrayList<>();
            if (StringUtils.isNotBlank(rectificationFile)) {
                List<Long> rectificationFileIdList = Arrays.stream(rectificationFile.split(",")).map(Long::parseLong).toList();
                for (Long id : rectificationFileIdList) {
                    if (ossMap.containsKey(id)) {
                        rectificationFileList.add(ossMap.get(id).get(0));
                    }
                }
            }
            qualityInspectionVo.setRectificationFileList(rectificationFileList);
            return qualityInspectionVo;
        }).toList();
        qualityInspectionVoPage.setRecords(qualityInspectionVoList);
        return qualityInspectionVoPage;
    }

    /**
     * 导出质量-检查工单
     *
     * @param id       质量-检查工单id
     * @param response HttpServletResponse
     */
    @Override
    public void exportWordById(Long id, HttpServletResponse response) {
        QltQualityInspection qualityInspection = this.getById(id);
        if (qualityInspection == null) {
            throw new ServiceException("质量-检查工单不存在");
        }
        Map<String, String> replacementMap = getReplacementMap(qualityInspection);
        Path targetDir = Paths.get(QltQualityConstant.getQualityInspectionFileUrl(qualityInspection));
        // 如果存在目录则直接返回，不存在则生成文件并返回
        if (!Files.exists(targetDir)) {
            // 清理旧文件
            String baseUrl = QltQualityConstant.QUALITY_INSPECTION_FILE_URL + qualityInspection.getId();
            try {
                Path dirPath = Paths.get(baseUrl);
                if (Files.exists(dirPath)) {
                    FileUtils.deleteDirectory(dirPath);
                }
            } catch (IOException e) {
                log.error("文件目录：{}，清理失败", baseUrl, e);
            }
            // 生成文件
            try (InputStream is = getClass().getClassLoader().getResourceAsStream(QltQualityConstant.QUALITY_INSPECTION_TEMPLATE_PATH)) {
                if (is == null) {
                    throw new ServiceException("模板文件不存在");
                }
                try (XWPFDocument document = new XWPFDocument(is)) {
                    // 替换段落中的文本
                    for (XWPFParagraph paragraph : document.getParagraphs()) {
                        replaceInParagraph(paragraph, replacementMap, document, qualityInspection);
                    }
                    // 替换表格中的文本（如果模板中有表格）
                    for (XWPFTable table : document.getTables()) {
                        for (XWPFTableRow row : table.getRows()) {
                            for (XWPFTableCell cell : row.getTableCells()) {
                                for (XWPFParagraph paragraph : cell.getParagraphs()) {
                                    replaceInParagraph(paragraph, replacementMap, document, qualityInspection);
                                }
                            }
                        }
                    }
                    // 创建目标目录
                    if (!Files.exists(targetDir)) {
                        Files.createDirectories(targetDir);
                    }
                    // 组合目标文件名
                    String fileName = QltQualityConstant.getQualityInspectionFileName(qualityInspection);
                    // 保存修改后的文件
                    try (FileOutputStream fos = new FileOutputStream(targetDir.resolve(fileName).toFile())) {
                        document.write(fos);
                    }
                }
            } catch (IOException | InvalidFormatException e) {
                throw new OssException("生成Word文件失败，错误信息: " + e.getMessage());
            }
        }
        // 设置响应头，返回ZIP文件
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            DocumentUtils.zipDirectory(targetDir, targetDir, zos);
            zos.flush();
        } catch (Exception e) {
            throw new OssException("生成ZIP文件失败，错误信息: " + e.getMessage());
        }
    }

    /**
     * 根据实体获取Word的文本Map
     *
     * @param qualityInspection 质量-检查工单对象
     * @return Map
     */
    public Map<String, String> getReplacementMap(QltQualityInspection qualityInspection) {
        Map<String, String> replacementMap = new HashMap<>();
        String createName = userService.selectUserById(qualityInspection.getCreateBy()).getNickName();
        replacementMap.put("${createName}", createName);
        Date createTime = qualityInspection.getCreateTime();
        replacementMap.put("${createTime}", createTime != null ? DateUtils.formatDateTime(createTime) : "");
        String projectName = projectService.getById(qualityInspection.getProjectId()).getProjectName();
        replacementMap.put("${projectName}", projectName);
        replacementMap.put("${inspectionHeadline}", qualityInspection.getInspectionHeadline());
        String correctorName = constructionUserService.getById(qualityInspection.getCorrector()).getUserName();
        replacementMap.put("${correctorName}", correctorName);
        Date rectificationTime = qualityInspection.getRectificationTime();
        replacementMap.put("${rectificationTime}", rectificationTime != null ? DateUtils.formatDateTime(rectificationTime) : "");
        String inspectionType = dictDataService.selectDictLabel(QltQualityConstant.QUALITY_INSPECTION_CHECK_TYPE, qualityInspection.getInspectionType());
        replacementMap.put("${inspectionType}", inspectionType);
        Date replyPeriodDate = qualityInspection.getReplyPeriodDate();
        replacementMap.put("${replyPeriodDate}", replyPeriodDate != null ? DateUtils.formatDate(replyPeriodDate) : "");
        replacementMap.put("${inspectionResult}", qualityInspection.getInspectionResult());
        replacementMap.put("${inspectionFile}", qualityInspection.getInspectionFile());
        String inspectionStatus = dictDataService.selectDictLabel(QltQualityConstant.QUALITY_INSPECTION_STATUS_TYPE, qualityInspection.getInspectionStatus());
        replacementMap.put("${inspectionStatus}", inspectionStatus);
        replacementMap.put("${rectificationResult}", qualityInspection.getRectificationResult());
        replacementMap.put("${rectificationFile}", qualityInspection.getRectificationFile());
        replacementMap.put("${verificationResult}", qualityInspection.getVerificationResult());
        return replacementMap;
    }

    /**
     * 替换段落中所有文本运行的占位符内容，对于 checkFile 和 rectificationFile
     * 插入图片或超链接（附件）展示。
     *
     * @param paragraph    当前段落
     * @param replacements 占位符与替换内容的映射
     * @param document     当前文档，用于插入图片或创建超链接
     */
    public void replaceInParagraph(XWPFParagraph paragraph,
                                   Map<String, String> replacements,
                                   XWPFDocument document,
                                   QltQualityInspection qualityInspection)
        throws InvalidFormatException, IOException {
        // 先拷贝 paragraph 里所有的 run
        List<XWPFRun> runs = new ArrayList<>(paragraph.getRuns());
        // 在拷贝上遍历，修改原 paragraph（增删 run）都不会抛 CME
        for (XWPFRun run : runs) {
            String text = run.getText(0);
            if (text != null) {
                // 针对每个占位符进行检查
                for (Map.Entry<String, String> entry : replacements.entrySet()) {
                    String placeholder = entry.getKey();
                    String value = entry.getValue();
                    if (text.contains(placeholder)) {
                        // 针对 file 进行特殊处理
                        if (placeholder.equals("${inspectionFile}") || placeholder.equals("${rectificationFile}")) {
                            // 判断该 run 中的文本是否仅包含该占位符（建议模板中独占一行）
                            if (text.trim().equals(placeholder)) {
                                // 清空原有文本
                                run.setText("", 0);
                                // 根据附件的后缀决定以图片或超链接展示
                                if (StringUtils.isBlank(value)) {
                                    continue;
                                }
                                // 获取附件的ossId
                                List<Long> ossIdList = Arrays.stream(value.split(",")).map(Long::parseLong).toList();
                                List<SysOssVo> ossVoList = ossService.listByIds(ossIdList);
                                String baseFile = QltQualityConstant.getQualityInspectionFileUrl(qualityInspection) + "/file";
                                for (SysOssVo ossVo : ossVoList) {
                                    String lowerVal = ossVo.getUrl().toLowerCase();
                                    OssClient storage = OssFactory.instance(ossVo.getService());
                                    String fileDownload = storage.fileDownload(ossVo.getUrl(), ossVo.getOriginalName(), baseFile);
                                    if (lowerVal.endsWith(".png") || lowerVal.endsWith(".jpg") || lowerVal.endsWith(".jpeg") || lowerVal.endsWith(".gif")) {
                                        try {
                                            DocumentUtils.insertImageDynamic(run, fileDownload, document, 300);
                                        } catch (Exception e) {
                                            throw new ServiceException("插入图片失败");
                                        }
                                    } else {
                                        // —— 非图片：插入超链接 ——
                                        XWPFHyperlinkRun link = paragraph.createHyperlinkRun(ossVo.getUrl());
                                        link.setText(ossVo.getOriginalName());
                                        link.setColor("0000FF");
                                        link.setUnderline(UnderlinePatterns.SINGLE);
                                    }
                                }
                            } else {
                                // 如果占位符与其它文本混合，可进行文本替换，提示附件展示请单独使用占位符
                                text = text.replace(placeholder, "[附件]");
                                run.setText(text, 0);
                            }
                        } else {
                            // 普通文本占位符直接替换
                            if (StringUtils.isBlank(value)) {
                                // 如果填入值为空，清空原有文本
                                run.setText("", 0);
                                continue;
                            }
                            text = text.replace(placeholder, value);
                            run.setText(text, 0);
                        }
                    }
                }
            }
        }
    }

}
