package org.dromara.safety.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.project.service.IBusProjectService;
import org.dromara.safety.constant.HseDocumentSafetyMeetingConstant;
import org.dromara.safety.domain.HseDocumentSafetyMeeting;
import org.dromara.safety.domain.enums.HseDocumentStatusEnum;
import org.dromara.safety.domain.enums.HseDocumentTypeEnum;
import org.dromara.safety.domain.dto.documentsafetymeeting.HseDocumentSafetyMeetingCreateFileReq;
import org.dromara.safety.domain.dto.documentsafetymeeting.HseDocumentSafetyMeetingCreateFolderReq;
import org.dromara.safety.domain.dto.documentsafetymeeting.HseDocumentSafetyMeetingQueryReq;
import org.dromara.safety.domain.vo.documentsafetymeeting.HseDocumentSafetyMeetingRecycleBinVo;
import org.dromara.safety.domain.vo.documentsafetymeeting.HseDocumentSafetyMeetingVo;
import org.dromara.safety.mapper.HseDocumentSafetyMeetingMapper;
import org.dromara.safety.service.IHseDocumentSafetyMeetingService;
import org.dromara.system.domain.vo.SysOssUploadVo;
import org.dromara.system.service.ISysOssService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 安全会议纪要Service业务层处理
 *
 * @author lcj
 * @date 2025-04-14
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class HseDocumentSafetyMeetingServiceImpl extends ServiceImpl<HseDocumentSafetyMeetingMapper, HseDocumentSafetyMeeting>
    implements IHseDocumentSafetyMeetingService {

    @Resource
    private IBusProjectService projectService;

    @Resource
    private ISysOssService ossService;

    /**
     * 查询安全会议纪要
     *
     * @param id 主键
     * @return 安全会议纪要
     */
    @Override
    public HseDocumentSafetyMeetingVo queryById(Long id) {
        HseDocumentSafetyMeeting documentSafetyMeeting = this.getById(id);
        if (documentSafetyMeeting == null) {
            throw new ServiceException("安全会议纪要信息不存在", HttpStatus.NOT_FOUND);
        }
        return this.getVo(documentSafetyMeeting);
    }

    /**
     * 分页查询安全会议纪要列表
     *
     * @param req 查询条件
     * @return 安全会议纪要分页列表
     */
    @Override
    public List<HseDocumentSafetyMeetingVo> queryList(HseDocumentSafetyMeetingQueryReq req) {
        LambdaQueryWrapper<HseDocumentSafetyMeeting> lqw = buildQueryWrapper(req);
        // 排除已经删除的文件
        lqw.eq(HseDocumentSafetyMeeting::getFileStatus, HseDocumentStatusEnum.NORMAL.getValue());
        List<HseDocumentSafetyMeeting> result = this.list(lqw);
        return result.stream().map(this::getVo).toList();
    }

    /**
     * 分页查询安全会议纪要回收站列表
     *
     * @param req       查询条件
     * @param pageQuery 分页参数
     * @return 安全会议纪要分页列表
     */
    @Override
    public TableDataInfo<HseDocumentSafetyMeetingRecycleBinVo> queryRecycleBinPageList(HseDocumentSafetyMeetingQueryReq req, PageQuery pageQuery) {
        LambdaQueryWrapper<HseDocumentSafetyMeeting> lqw = this.buildQueryWrapper(req);
        lqw.eq(HseDocumentSafetyMeeting::getFileStatus, HseDocumentStatusEnum.DELETE.getValue());
        Page<HseDocumentSafetyMeeting> result = this.page(pageQuery.build(), lqw);
        List<HseDocumentSafetyMeeting> documentSafetyMeetingList = result.getRecords();
        // 添加分页信息
        Page<HseDocumentSafetyMeetingRecycleBinVo> documentSafetyMeetingVoPage = new Page<>(
            result.getCurrent(),
            result.getSize(),
            result.getTotal());
        if (CollUtil.isEmpty(documentSafetyMeetingList)) {
            return TableDataInfo.build(documentSafetyMeetingVoPage);
        }
        // 对象列表 => 封装对象列表
        List<HseDocumentSafetyMeetingRecycleBinVo> list = documentSafetyMeetingList.stream().map(documentSafetyMeeting -> {
            HseDocumentSafetyMeetingRecycleBinVo resp = new HseDocumentSafetyMeetingRecycleBinVo();
            BeanUtils.copyProperties(documentSafetyMeeting, resp);
            return resp;
        }).toList();
        documentSafetyMeetingVoPage.setRecords(list);
        return TableDataInfo.build(documentSafetyMeetingVoPage);
    }

    /**
     * 新增安全会议纪要文件夹
     *
     * @param req 文件夹创建请求
     * @return 是否新增成功
     */
    @Override
    public Long insertByFolder(HseDocumentSafetyMeetingCreateFolderReq req) {
        if (req == null) {
            throw new ServiceException("文件夹创建请求不能为空", HttpStatus.BAD_REQUEST);
        }
        if (StringUtils.isEmpty(req.getFileName())) {
            throw new ServiceException("文件夹名称不能为空", HttpStatus.BAD_REQUEST);
        }
        HseDocumentSafetyMeeting documentSafetyMeeting = new HseDocumentSafetyMeeting();
        BeanUtils.copyProperties(req, documentSafetyMeeting);
        Long pid = req.getPid();
        String filePath;
        if (pid != null && pid != 0) {
            HseDocumentSafetyMeeting pDocumentSafetyMeeting = this.getById(pid);
            // 判断父级目录是否存在
            validParentFolder(pDocumentSafetyMeeting, req.getProjectId());
            filePath = pDocumentSafetyMeeting.getFilePath() + "/" + req.getFileName();
        } else {
            filePath = HseDocumentSafetyMeetingConstant.getTopFolderPrefix(req.getProjectId()) + req.getFileName();
        }
        // 填充默认值
        documentSafetyMeeting.setFilePath(filePath);
        documentSafetyMeeting.setFileType(HseDocumentTypeEnum.FOLDER.getValue());
        boolean save = this.save(documentSafetyMeeting);
        if (!save) {
            throw new ServiceException("新增目录失败，数据库异常", HttpStatus.ERROR);
        }
        return documentSafetyMeeting.getId();
    }

    /**
     * 新增安全会议纪要文件
     *
     * @param req 文件上传请求
     * @return 是否新增成功
     */
    @Override
    public Long insertByFile(MultipartFile file, HseDocumentSafetyMeetingCreateFileReq req) {
        // 数据校验
        if (ObjectUtils.isEmpty(file)) {
            throw new ServiceException("文件不能为空", HttpStatus.BAD_REQUEST);
        }
        Long projectId = req.getProjectId();
        if (projectService.getById(projectId) == null) {
            throw new ServiceException("项目不存在", HttpStatus.BAD_REQUEST);
        }
        // 拼接文件名
        String originalFilename = file.getOriginalFilename();
        String suffix = FileUtil.getSuffix(originalFilename);
        String uuid = IdUtil.fastSimpleUUID();
        String date = DateUtils.getDate();
        String fileName = String.format("%s_%s.%s", date, uuid, suffix);
        // 拼接文件路径
        Long pid = req.getPid();
        String filePath;
        if (pid != null && pid != 0) {
            HseDocumentSafetyMeeting pDocumentSafetyMeeting = this.getById(pid);
            // 判断父级目录是否存在
            validParentFolder(pDocumentSafetyMeeting, projectId);
            filePath = pDocumentSafetyMeeting.getFilePath() + "/" + fileName;
        } else {
            filePath = HseDocumentSafetyMeetingConstant.getTopFolderPrefix(projectId) + fileName;
        }
        // 上传文件
        SysOssUploadVo ossUploadVo = ossService.uploadWithNoSave(file, filePath);
        // 保存文件信息
        HseDocumentSafetyMeeting documentSafetyMeeting = new HseDocumentSafetyMeeting();
        documentSafetyMeeting.setFilePath(filePath);
        documentSafetyMeeting.setFileUrl(ossUploadVo.getOssId());
        documentSafetyMeeting.setFileSuffix(suffix);
        // 判断文件类型
        if (HseDocumentSafetyMeetingConstant.PICTURE_SUFFIX_LIST.contains(suffix)) {
            documentSafetyMeeting.setFileType(HseDocumentTypeEnum.PICTURE.getValue());
        } else {
            documentSafetyMeeting.setFileType(HseDocumentTypeEnum.FILE.getValue());
        }
        documentSafetyMeeting.setFileName(ossUploadVo.getFileName());
        documentSafetyMeeting.setOriginalName(originalFilename);
        documentSafetyMeeting.setProjectId(projectId);
        documentSafetyMeeting.setPid(pid);
        boolean save = this.save(documentSafetyMeeting);
        if (!save) {
            throw new ServiceException("新增文件失败，数据库异常", HttpStatus.ERROR);
        }
        // 返回主键
        return documentSafetyMeeting.getId();
    }

    /**
     * 批量恢复安全会议纪要信息
     *
     * @param ids 待删除的主键集合
     * @return 是否删除成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean recoveryBatchById(Collection<Long> ids) {
        List<Long> allParentIdsRecursively = getAllParentIdsRecursively(ids);
        // 需要更新状态的文件集合
        allParentIdsRecursively.addAll(ids);
        List<HseDocumentSafetyMeeting> updateList = allParentIdsRecursively.stream().map(id -> {
            HseDocumentSafetyMeeting documentSafetyMeeting = new HseDocumentSafetyMeeting();
            documentSafetyMeeting.setId(id);
            documentSafetyMeeting.setFileStatus(HseDocumentStatusEnum.NORMAL.getValue());
            return documentSafetyMeeting;
        }).toList();
        boolean result = this.updateBatchById(updateList);
        if (!result) {
            throw new ServiceException("恢复文件失败，数据库异常", HttpStatus.ERROR);
        }
        return true;
    }

    private List<Long> getAllParentIdsRecursively(Collection<Long> ids) {
        // 使用 list() 方法批量查询当前 id 列表对应的实体数据
        List<HseDocumentSafetyMeeting> fileList = this.lambdaQuery()
            .in(HseDocumentSafetyMeeting::getId, ids)
            .eq(HseDocumentSafetyMeeting::getFileStatus, HseDocumentStatusEnum.DELETE.getValue())
            .list();
        // 通过 stream 流过滤出非 0 的父 id，并去重
        List<Long> parentIdList = fileList.stream()
            .map(HseDocumentSafetyMeeting::getPid)
            .filter(pid -> pid != 0)
            .distinct()
            .collect(Collectors.toList());
        // 如果父 id 列表为空，说明递归终止，返回空列表
        if (parentIdList.isEmpty()) {
            return new ArrayList<>();
        }
        // 递归查询父 id 列表对应的上级父 id
        List<Long> higherParentIds = getAllParentIdsRecursively(parentIdList);
        // 将当前层的父 id 和上级递归得到的父 id 合并
        List<Long> allParentIds = new ArrayList<>();
        allParentIds.addAll(parentIdList);
        allParentIds.addAll(higherParentIds);
        // 返回合并后去重的结果
        return allParentIds.stream().distinct().collect(Collectors.toList());
    }


    /**
     * 校验父级目录是否存在
     *
     * @param pDocumentSafetyMeeting 父级目录
     * @param projectId              当前项目id
     */
    private void validParentFolder(HseDocumentSafetyMeeting pDocumentSafetyMeeting, Long projectId) {
        // 判断父级目录是否存在
        if (pDocumentSafetyMeeting == null) {
            throw new ServiceException("父级目录不存在", HttpStatus.NOT_FOUND);
        }
        // 判断父级目录是否是文件夹
        if (HseDocumentTypeEnum.FILE.getValue().equals(pDocumentSafetyMeeting.getFileType())) {
            throw new ServiceException("父级目录不是文件夹", HttpStatus.BAD_REQUEST);
        }
        // 判断是否为同一个项目
        if (!pDocumentSafetyMeeting.getProjectId().equals(projectId)) {
            throw new ServiceException("父级目录不属于当前项目", HttpStatus.BAD_REQUEST);
        }
    }

    /**
     * 批量删除安全会议纪要信息
     *
     * @param ids 待删除的主键集合
     * @return 是否删除成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWithRecycleBin(Collection<Long> ids) {
        // 获取所有需要删除的元素
        List<Long> allChildIdList = this.getAllChildIdList(ids);
        if (CollUtil.isEmpty(allChildIdList)) {
            return true;
        }
        // 批量修改状态为已删除
        List<HseDocumentSafetyMeeting> documentSafetyMeetingList = allChildIdList.stream().map(id -> {
            HseDocumentSafetyMeeting documentSafetyMeeting = new HseDocumentSafetyMeeting();
            documentSafetyMeeting.setId(id);
            documentSafetyMeeting.setDeletedAt(new Date());
            documentSafetyMeeting.setFileStatus(HseDocumentStatusEnum.DELETE.getValue());
            return documentSafetyMeeting;
        }).toList();
        // 批量修改
        boolean result = this.updateBatchById(documentSafetyMeetingList);
        if (!result) {
            throw new ServiceException("数据库操作失败", HttpStatus.ERROR);
        }
        return true;
    }

    /**
     * 彻底批量删除安全会议纪要
     *
     * @param ids 待删除的主键集合
     * @return 是否删除成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean completelyDelete(Collection<Long> ids) {
        // 获取所有需要删除的元素
        List<Long> allChildIdList = this.getAllChildIdList(ids);
        if (CollUtil.isEmpty(allChildIdList)) {
            return true;
        }
        boolean result = this.removeBatchByIds(allChildIdList);
        if (!result) {
            throw new ServiceException("数据库操作失败", HttpStatus.ERROR);
        }
        // todo 删除oss文件

        return true;
    }

    /**
     * 获取安全会议纪要视图对象
     *
     * @param documentSafetyMeeting 安全会议纪要对象
     * @return 安全会议纪要视图对象
     */
    @Override
    public HseDocumentSafetyMeetingVo getVo(HseDocumentSafetyMeeting documentSafetyMeeting) {
        // 对象转封装类
        HseDocumentSafetyMeetingVo documentSafetyMeetingVo = new HseDocumentSafetyMeetingVo();
        if (documentSafetyMeeting == null) {
            return documentSafetyMeetingVo;
        }
        BeanUtils.copyProperties(documentSafetyMeeting, documentSafetyMeetingVo);
        return documentSafetyMeetingVo;
    }

    /**
     * 获取安全会议纪要查询条件封装
     *
     * @param req 安全会议纪要查询条件
     * @return 安全会议纪要查询条件封装
     */
    @Override
    public LambdaQueryWrapper<HseDocumentSafetyMeeting> buildQueryWrapper(HseDocumentSafetyMeetingQueryReq req) {
        LambdaQueryWrapper<HseDocumentSafetyMeeting> lqw = new LambdaQueryWrapper<>();
        if (req == null) {
            return lqw;
        }
        Long projectId = req.getProjectId();
        Long pid = req.getPid();
        String fileType = req.getFileType();
        String notFileType = req.getNotFileType();
        // 精确查询
        lqw.eq(ObjectUtils.isNotEmpty(projectId), HseDocumentSafetyMeeting::getProjectId, projectId);
        lqw.eq(ObjectUtils.isNotEmpty(pid), HseDocumentSafetyMeeting::getPid, pid);
        lqw.eq(StringUtils.isNotBlank(fileType), HseDocumentSafetyMeeting::getFileType, fileType);
        // 不等于
        lqw.ne(StringUtils.isNotBlank(notFileType), HseDocumentSafetyMeeting::getFileType, notFileType);
        return lqw;
    }

    /**
     * 获取安全会议纪要分页对象视图
     *
     * @param documentSafetyMeetingPage 安全会议纪要分页对象
     * @return 安全会议纪要分页对象视图
     */
    @Override
    public Page<HseDocumentSafetyMeetingVo> getVoPage(Page<HseDocumentSafetyMeeting> documentSafetyMeetingPage) {
        // 获取安全会议纪要列表
        List<HseDocumentSafetyMeeting> documentSafetyMeetingList = documentSafetyMeetingPage.getRecords();
        // 添加分页信息
        Page<HseDocumentSafetyMeetingVo> documentSafetyMeetingVoPage = new Page<>(
            documentSafetyMeetingPage.getCurrent(),
            documentSafetyMeetingPage.getSize(),
            documentSafetyMeetingPage.getTotal());
        if (CollUtil.isEmpty(documentSafetyMeetingList)) {
            return documentSafetyMeetingVoPage;
        }
        // 对象列表 => 封装对象列表
        List<HseDocumentSafetyMeetingVo> documentSafetyMeetingVoList = documentSafetyMeetingList
            .stream().map(this::getVo).toList();
        documentSafetyMeetingVoPage.setRecords(documentSafetyMeetingVoList);
        return documentSafetyMeetingVoPage;
    }

    /**
     * 递归查询所有的文件和文件夹以及文件夹下的子文件夹的id
     *
     * @param ids 主键集合
     * @return 整合所有的id集合
     */
    @Override
    public List<Long> getAllChildIdList(Collection<Long> ids) {
        List<Long> idList = new ArrayList<>();
        for (Long id : ids) {
            // 将当前id加入集合
            idList.add(id);
            // 查询当前记录（判断是否为文件夹）
            HseDocumentSafetyMeeting record = this.getById(id);
            if (record != null && HseDocumentTypeEnum.FOLDER.getValue().equals(record.getFileType())) {
                // 如果是文件夹，递归查询子节点
                getChildIds(id, idList);
            }
        }
        return idList;
    }

    /**
     * 递归查询给定父级id下所有子文件和子文件夹的id
     *
     * @param parentId 父级文件夹id
     * @param ids      存放id的集合（递归过程中不断添加）
     */
    private void getChildIds(Long parentId, List<Long> ids) {
        // 条件查询，查询pid为parentId的记录
        List<HseDocumentSafetyMeeting> childList = this.lambdaQuery()
            .eq(HseDocumentSafetyMeeting::getPid, parentId)
            .eq(HseDocumentSafetyMeeting::getFileStatus, HseDocumentStatusEnum.NORMAL.getValue())
            .list();
        // 遍历所有子记录
        for (HseDocumentSafetyMeeting child : childList) {
            ids.add(child.getId());
            // 如果子节点是文件夹，继续递归
            if (HseDocumentTypeEnum.FOLDER.getValue().equals(child.getFileType())) {
                getChildIds(child.getId(), ids);
            }
        }
    }

}
