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.BusProject;
import org.dromara.project.service.IBusProjectService;
import org.dromara.quality.constant.QltQualityConstant;
import org.dromara.quality.domain.QltQualityConstructionLog;
import org.dromara.quality.domain.dto.qualityconstructionlog.QltQualityConstructionLogCreateReq;
import org.dromara.quality.domain.dto.qualityconstructionlog.QltQualityConstructionLogQueryReq;
import org.dromara.quality.domain.dto.qualityconstructionlog.QltQualityConstructionLogUpdateReq;
import org.dromara.quality.domain.vo.qualityconstructionlog.QltQualityConstructionLogVo;
import org.dromara.quality.mapper.QltQualityConstructionLogMapper;
import org.dromara.quality.service.IQltQualityConstructionLogService;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysUserVo;
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 QltQualityConstructionLogServiceImpl extends ServiceImpl<QltQualityConstructionLogMapper, QltQualityConstructionLog>
    implements IQltQualityConstructionLogService {

    @Resource
    private IBusProjectService projectService;

    @Resource
    private ISysUserService userService;

    @Resource
    private ISysOssService ossService;

    /**
     * 查询质量-施工日志
     *
     * @param id 主键
     * @return 质量-施工日志
     */
    @Override
    public QltQualityConstructionLogVo queryById(Long id) {
        QltQualityConstructionLog qualityConstructionLog = this.getById(id);
        if (qualityConstructionLog == null) {
            throw new ServiceException("施工日志信息不存在", HttpStatus.NOT_FOUND);
        }
        return this.getVo(qualityConstructionLog);
    }

    /**
     * 分页查询质量-施工日志列表
     *
     * @param req       查询条件
     * @param pageQuery 分页参数
     * @return 质量-施工日志分页列表
     */
    @Override
    public TableDataInfo<QltQualityConstructionLogVo> queryPageList(QltQualityConstructionLogQueryReq req, PageQuery pageQuery) {
        Page<QltQualityConstructionLog> result = this.page(pageQuery.build(), buildQueryWrapper(req));
        return TableDataInfo.build(this.getVoPage(result));
    }

    /**
     * 查询符合条件的质量-施工日志列表
     *
     * @param req 查询条件
     * @return 质量-施工日志列表
     */
    @Override
    public List<QltQualityConstructionLogVo> queryList(QltQualityConstructionLogQueryReq req) {
        LambdaQueryWrapper<QltQualityConstructionLog> lqw = buildQueryWrapper(req);
        return this.list(lqw).stream().map(this::getVo).toList();
    }

    /**
     * 新增质量-施工日志
     *
     * @param req 质量-施工日志
     * @return 新增日志主键
     */
    @Override
    public Long insertByBo(QltQualityConstructionLogCreateReq req) {
        // 将实体类和 DTO 进行转换
        QltQualityConstructionLog qualityConstructionLog = new QltQualityConstructionLog();
        BeanUtils.copyProperties(req, qualityConstructionLog);
        // 数据校验
        validEntityBeforeSave(qualityConstructionLog, true);
        // 写入数据库
        boolean save = this.save(qualityConstructionLog);
        if (!save) {
            throw new ServiceException("新增施工日志信息失败，数据库异常", HttpStatus.ERROR);
        }
        // 返回新写入的数据
        return qualityConstructionLog.getId();
    }

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

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(QltQualityConstructionLog 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<QltQualityConstructionLog> qualityConstructionLogList = this.listByIds(ids);
        if (isValid) {
            // TODO 做一些业务上的校验,判断是否需要校验
            List<Long> projectId = qualityConstructionLogList.stream().map(QltQualityConstructionLog::getProjectId).toList();
            projectService.validAuth(projectId, userId);
        }
        return this.removeBatchByIds(ids);
    }

    /**
     * 获取质量-施工日志视图对象
     *
     * @param qualityConstructionLog 质量-施工日志对象
     * @return 质量-施工日志视图对象
     */
    @Override
    public QltQualityConstructionLogVo getVo(QltQualityConstructionLog qualityConstructionLog) {
        // 对象转封装类
        QltQualityConstructionLogVo qualityConstructionLogVo = new QltQualityConstructionLogVo();
        if (qualityConstructionLog == null) {
            return qualityConstructionLogVo;
        }
        BeanUtils.copyProperties(qualityConstructionLog, qualityConstructionLogVo);
        // 关联项目信息
        Long projectId = qualityConstructionLog.getProjectId();
        if (projectId != null) {
            qualityConstructionLogVo.setProjectName(projectService.getById(projectId).getProjectName());
        }
        // 关联创建用户信息
        Long createBy = qualityConstructionLog.getCreateBy();
        if (createBy != null) {
            SysUserVo sysUserVo = userService.selectUserById(createBy);
            qualityConstructionLogVo.setCreateBy(sysUserVo.getNickName());
        }
        // 关联附件信息
        String file = qualityConstructionLog.getFile();
        if (StringUtils.isNotBlank(file)) {
            List<Long> ossIdList = Arrays.stream(file.split(",")).map(Long::parseLong).toList();
            List<SysOssVo> ossVoList = ossService.listByIds(ossIdList);
            qualityConstructionLogVo.setFileList(ossVoList);
        }
        return qualityConstructionLogVo;
    }

    /**
     * 获取质量-施工日志查询条件封装
     *
     * @param req 质量-施工日志查询条件
     * @return 质量-施工日志查询条件封装
     */
    @Override
    public LambdaQueryWrapper<QltQualityConstructionLog> buildQueryWrapper(QltQualityConstructionLogQueryReq req) {
        LambdaQueryWrapper<QltQualityConstructionLog> lqw = new LambdaQueryWrapper<>();
        if (req == null) {
            return lqw;
        }
        Long projectId = req.getProjectId();
        Date happenDate = req.getHappenDate();
        // 精准查询
        lqw.eq(ObjectUtils.isNotEmpty(projectId), QltQualityConstructionLog::getProjectId, projectId);
        lqw.eq(ObjectUtils.isNotEmpty(happenDate), QltQualityConstructionLog::getHappenDate, happenDate);
        return lqw;
    }

    /**
     * 获取质量-施工日志分页对象视图
     *
     * @param qualityConstructionLogPage 质量-施工日志分页对象
     * @return 质量-施工日志分页对象视图
     */
    @Override
    public Page<QltQualityConstructionLogVo> getVoPage(Page<QltQualityConstructionLog> qualityConstructionLogPage) {
        // 获取质量-施工日志列表
        List<QltQualityConstructionLog> qualityConstructionLogList = qualityConstructionLogPage.getRecords();
        // 添加分页信息
        Page<QltQualityConstructionLogVo> qualityConstructionLogVoPage = new Page<>(
            qualityConstructionLogPage.getCurrent(),
            qualityConstructionLogPage.getSize(),
            qualityConstructionLogPage.getTotal());
        if (CollUtil.isEmpty(qualityConstructionLogList)) {
            return qualityConstructionLogVoPage;
        }
        // 获取项目名称
        List<Long> projectIdList = qualityConstructionLogList.stream().map(QltQualityConstructionLog::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> createByList = qualityConstructionLogList.stream().map(QltQualityConstructionLog::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> ossIdList = qualityConstructionLogList.stream().map(QltQualityConstructionLog::getFile).filter(StringUtils::isNotBlank)
            .flatMap(fileId -> Arrays.stream(fileId.split(",")).map(Long::parseLong)).distinct().toList();
        Map<Long, List<SysOssVo>> ossMap = ossService.listByIds(ossIdList)
            .stream().collect(Collectors.groupingBy(SysOssVo::getOssId));
        // 对象列表 => 封装对象列表
        List<QltQualityConstructionLogVo> qualityConstructionLogVoList = qualityConstructionLogList.stream().map(qualityConstructionLog -> {
            QltQualityConstructionLogVo qualityConstructionLogVo = new QltQualityConstructionLogVo();
            // 对象转封装类
            BeanUtils.copyProperties(qualityConstructionLog, qualityConstructionLogVo);
            // 项目名称
            qualityConstructionLogVo.setProjectName(project.getProjectName());
            // 创建用户姓名
            Long createBy = qualityConstructionLog.getCreateBy();
            String createByName = null;
            if (userMap.containsKey(createBy)) {
                createByName = userMap.get(createBy);
            }
            qualityConstructionLogVo.setCreateBy(createByName);
            // 附件信息
            String file = qualityConstructionLog.getFile();
            List<SysOssVo> fileList = new ArrayList<>();
            if (StringUtils.isNotBlank(file)) {
                List<Long> fileIdList = Arrays.stream(file.split(",")).map(Long::parseLong).toList();
                for (Long fileId : fileIdList) {
                    if (ossMap.containsKey(fileId)) {
                        fileList.add(ossMap.get(fileId).get(0));
                    }
                }
            }
            qualityConstructionLogVo.setFileList(fileList);
            return qualityConstructionLogVo;
        }).toList();
        qualityConstructionLogVoPage.setRecords(qualityConstructionLogVoList);
        return qualityConstructionLogVoPage;
    }

    /**
     * 导出质量-施工日志
     *
     * @param id       主键
     * @param response 响应
     */
    @Override
    public void exportWordById(Long id, HttpServletResponse response) {
        QltQualityConstructionLog qualityConstructionLog = this.getById(id);
        if (qualityConstructionLog == null) {
            throw new ServiceException("质量-施工日志不存在");
        }
        Map<String, String> replacementMap = getReplacementMap(qualityConstructionLog);
        Path targetDir = Paths.get(QltQualityConstant.getQualityConstructionLogFileUrl(qualityConstructionLog));
        // 如果存在目录则直接返回，不存在则生成文件并返回
        if (!Files.exists(targetDir)) {
            // 清理旧文件
            String baseUrl = QltQualityConstant.QUALITY_CONSTRUCTION_LOG_FILE_URL + qualityConstructionLog.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_CONSTRUCTION_LOG_TEMPLATE_PATH)) {
                if (is == null) {
                    throw new ServiceException("模板文件不存在");
                }
                try (XWPFDocument document = new XWPFDocument(is)) {
                    // 替换段落中的文本
                    for (XWPFParagraph paragraph : document.getParagraphs()) {
                        replaceInParagraph(paragraph, replacementMap, document, qualityConstructionLog);
                    }
                    // 替换表格中的文本（如果模板中有表格）
                    for (XWPFTable table : document.getTables()) {
                        for (XWPFTableRow row : table.getRows()) {
                            for (XWPFTableCell cell : row.getTableCells()) {
                                for (XWPFParagraph paragraph : cell.getParagraphs()) {
                                    replaceInParagraph(paragraph, replacementMap, document, qualityConstructionLog);
                                }
                            }
                        }
                    }
                    // 创建目标目录
                    if (!Files.exists(targetDir)) {
                        Files.createDirectories(targetDir);
                    }
                    // 组合目标文件名
                    String fileName = QltQualityConstant.getQualityConstructionLogFileName(qualityConstructionLog);
                    // 保存修改后的文件
                    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 qualityConstructionLog 施工日志对象
     * @return Map
     */
    public Map<String, String> getReplacementMap(QltQualityConstructionLog qualityConstructionLog) {
        Map<String, String> replacementMap = new HashMap<>();
        String createName = userService.selectUserById(qualityConstructionLog.getCreateBy()).getNickName();
        replacementMap.put("${createName}", createName);
        Date createTime = qualityConstructionLog.getCreateTime();
        replacementMap.put("${createTime}", createTime != null ? DateUtils.formatDateTime(createTime) : "");
        String projectName = projectService.getById(qualityConstructionLog.getProjectId()).getProjectName();
        replacementMap.put("${projectName}", projectName);
        Date happenDate = qualityConstructionLog.getHappenDate();
        replacementMap.put("${happenDate}", happenDate != null ? DateUtils.formatDate(happenDate) : "");
        replacementMap.put("${productionStatus}", qualityConstructionLog.getProductionStatus());
        replacementMap.put("${technologyQuality}", qualityConstructionLog.getTechnologyQuality());
        replacementMap.put("${remark}", qualityConstructionLog.getRemark());
        replacementMap.put("${file}", qualityConstructionLog.getFile());
        return replacementMap;
    }

    /**
     * 替换段落中所有文本运行的占位符内容，对于 checkFile 和 rectificationFile
     * 插入图片或超链接（附件）展示。
     *
     * @param paragraph    当前段落
     * @param replacements 占位符与替换内容的映射
     * @param document     当前文档，用于插入图片或创建超链接
     */
    public void replaceInParagraph(XWPFParagraph paragraph,
                                   Map<String, String> replacements,
                                   XWPFDocument document,
                                   QltQualityConstructionLog qualityConstructionLog) 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("${file}")) {
                            // 判断该 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.getQualityConstructionLogFileUrl(qualityConstructionLog) + "/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);
                        }
                    }
                }
            }
        }
    }

}
