package org.dromara.project.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
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.core.utils.file.FileUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.project.constant.BusConstructionUserConstant;
import org.dromara.project.domain.BusConstructionBlacklist;
import org.dromara.project.domain.BusConstructionUser;
import org.dromara.project.domain.BusConstructionUserFile;
import org.dromara.project.domain.BusProject;
import org.dromara.project.domain.dto.constructionuserfile.BusConstructionUserFileQueryReq;
import org.dromara.project.domain.dto.constructionuserfile.BusConstructionUserFileReq;
import org.dromara.project.domain.dto.constructionuserfile.BusConstructionUserFileSaveReq;
import org.dromara.project.domain.dto.constructionuserfile.BusConstructionUserFileTemplateReq;
import org.dromara.project.domain.vo.constructionuserfile.BusConstructionUserFileVo;
import org.dromara.project.mapper.BusConstructionUserFileMapper;
import org.dromara.project.service.IBusConstructionBlacklistService;
import org.dromara.project.service.IBusConstructionUserFileService;
import org.dromara.project.service.IBusConstructionUserService;
import org.dromara.project.service.IBusProjectService;
import org.dromara.system.domain.vo.SysDictDataVo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.service.ISysDictTypeService;
import org.dromara.system.service.ISysOssService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 施工人员文件存储Service业务层处理
 *
 * @author lcj
 * @date 2025-04-01
 */
@Slf4j
@Service
public class BusConstructionUserFileServiceImpl extends ServiceImpl<BusConstructionUserFileMapper, BusConstructionUserFile>
    implements IBusConstructionUserFileService {

    @Resource
    private IBusProjectService projectService;

    @Resource
    private IBusConstructionUserService constructionUserService;

    @Resource
    private ISysDictTypeService dictTypeService;

    @Resource
    private IBusConstructionBlacklistService constructionBlacklistService;

    @Resource
    private ISysOssService ossService;

    /**
     * 查询施工人员文件存储
     *
     * @param id 主键
     * @return 施工人员文件存储
     */
    @Override
    public BusConstructionUserFileVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 查询符合条件的施工人员文件存储列表
     *
     * @param req 查询条件
     * @return 施工人员文件存储列表
     */
    @Override
    public List<BusConstructionUserFileVo> queryList(BusConstructionUserFileQueryReq req) {
        LambdaQueryWrapper<BusConstructionUserFile> lqw = buildQueryWrapper(req);
        return baseMapper.selectVoList(lqw);
    }

    /**
     * 导出施工人员文件模版zip
     *
     * @param req      项目id
     * @param response 响应对象
     */
    @Override
    public void downloadFileTemplate(BusConstructionUserFileTemplateReq req, HttpServletResponse response) {
        // 1. 校验项目是否存在
        Long projectId = req.getProjectId();
        BusProject project = projectService.getById(projectId);
        if (project == null) {
            throw new ServiceException("项目不存在", HttpStatus.BAD_REQUEST);
        }
        // 2. 查询施工人员列表
        List<BusConstructionUser> constructionUserList = constructionUserService.list(this.buildTemplateQueryWrapper(req));
        // 3. 根目录名称
        String randomString = project.getId() + "_" + RandomUtil.randomString(8);
        String rootFolder = project.getShortName() + "_" + randomString + "/";
        // 4. 设置响应头
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment;filename=\"" + randomString + ".zip\"");
        // 5. 获取输出流，并封装为 ZipOutputStream
        try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
            // 6. 根目录下创建一个文件夹
            zipOut.putNextEntry(new ZipEntry(rootFolder));
            zipOut.closeEntry();
            // 7. 对每个人，创建其文件夹，再在其中创建子文件夹（如“三级安全教育”、“体检报告”...）及文件
            for (BusConstructionUser constructionUser : constructionUserList) {
                String personFolder = rootFolder + constructionUser.getUserName() + "-" + constructionUser.getId() + "/";
                // 7.1. 写入个人文件夹条目
                zipOut.putNextEntry(new ZipEntry(personFolder));
                zipOut.closeEntry();
                // 7.2. 在个人文件夹下写几个子文件夹
                List<SysDictDataVo> dictDataList = dictTypeService.selectDictDataByType(BusConstructionUserConstant.USER_FILE_TYPE);
                for (SysDictDataVo dataVo : dictDataList) {
                    String subFolderPath = personFolder + dataVo.getDictValue() + "_" + dataVo.getDictLabel() + "/";
                    zipOut.putNextEntry(new ZipEntry(subFolderPath));
                    zipOut.closeEntry();
                }
            }
            zipOut.flush();
            response.flushBuffer();  // 确保数据全部写入响应流
        } catch (IOException e) {
            throw new ServiceException("文件下载失败", HttpStatus.ERROR);
        }
    }

    /**
     * 通过zip文件批量上传施工人员文件
     *
     * @param multipartFile zip文件
     * @return 是否上传成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean batchUploadFileByZip(MultipartFile multipartFile) {
        // 获取文件原始名字
        String originalFilename = multipartFile.getOriginalFilename();
        // 校验
        if (originalFilename == null) {
            throw new ServiceException("文件不存在", HttpStatus.BAD_REQUEST);
        }
        // 校验是否为压缩包zip格式
        String suffix = FileUtil.getSuffix(originalFilename);
        if (!suffix.equals("zip")) {
            throw new ServiceException("请上传zip格式的文件", HttpStatus.BAD_REQUEST);
        }
        // 1. 获取项目id
        String[] parts = originalFilename.split("_");
        long projectId = Long.parseLong(parts[1]);
        // 压缩包临时文件路径
        String randomStr = RandomUtil.randomString(16);
        File tempZipFile = null;
        String tempZipFilePath = randomStr + "-" + originalFilename;
        // 解压后的文件夹路径
        File destDir = null;
        String basePath = "unzip_path";
        String destDirPath = basePath + "/" + DateUtils.getDate() + "/" + projectId + "/" + randomStr;
        // 构建需要修改的对象
        List<BusConstructionUserFile> constructionUserFileList = new ArrayList<>();
        try {
            // 2. 创建临时文件
            tempZipFile = File.createTempFile(tempZipFilePath, null);
            multipartFile.transferTo(tempZipFile);
            // 3. 解压 zip
            destDir = new File(destDirPath);
            ZipUtil.unzip(tempZipFile, destDir);
            // 4. 遍历最外层文件夹
            File[] userFolders = destDir.listFiles();
            if (userFolders != null) {
                for (File userFolder : userFolders) {
                    if (userFolder.isDirectory()) {
                        String userFolderName = userFolder.getName(); // 李四-1905161272755195006
                        // 5. 解析 userId
                        long userId = Long.parseLong(userFolderName.substring(userFolderName.lastIndexOf("-") + 1));
                        // 6. 继续遍历每个用户子文件夹里的 1_合同, 2_体检报告, ...
                        File[] docFolders = userFolder.listFiles();
                        if (docFolders != null) {
                            for (File docFolder : docFolders) {
                                if (docFolder.isDirectory()) {
                                    String docFolderName = docFolder.getName(); // 1_合同
                                    String[] docParts = docFolderName.split("_");
                                    String fileType = docParts[0];
                                    // 7. 获取该文件夹下所有文件
                                    File[] filesInDocFolder = docFolder.listFiles();
                                    String fileIdStr = null;
                                    if (filesInDocFolder != null) {
                                        // 遍历文件
                                        List<Long> fileIds = new ArrayList<>();
                                        for (File file : filesInDocFolder) {
                                            if (file.isFile()) {
                                                SysOssVo upload = ossService.upload(file);
                                                if (upload != null) {
                                                    fileIds.add(upload.getOssId());
                                                }
                                            }
                                        }
                                        // 跳过空文件
                                        if (fileIds.isEmpty()) {
                                            continue;
                                        }
                                        fileIdStr = fileIds.stream()
                                            .map(String::valueOf)
                                            .collect(Collectors.joining(","));
                                    }
                                    // 8. 创建 BusConstructionUserFile 对象
                                    BusConstructionUserFile constructionUserFile = new BusConstructionUserFile();
                                    constructionUserFile.setUserId(userId);
                                    constructionUserFile.setFileType(fileType);
                                    constructionUserFile.setPath(fileIdStr);
                                    constructionUserFileList.add(constructionUserFile);
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new ServiceException("文件上传失败", HttpStatus.ERROR);
        } finally {
            if (tempZipFile != null) {
                // 删除临时文件
                boolean delete = tempZipFile.delete();
                if (!delete) {
                    log.error("临时文件删除失败，路径：{}", tempZipFilePath);
                }
            }
            if (destDir != null) {
                Path dirPath = Paths.get(basePath);
                try {
                    FileUtils.deleteDirectory(dirPath);
                } catch (IOException e) {
                    log.error("解压文件删除失败，路径：{}", destDirPath, e);
                }
            }
        }
        if (CollUtil.isEmpty(constructionUserFileList)) {
            return true;
        }
        // 9. 判断是否已经存在
        List<Long> userIdList = constructionUserFileList.stream().map(BusConstructionUserFile::getUserId).toList();
        List<BusConstructionUserFile> oldConstructionUserFileList = this.lambdaQuery()
            .in(BusConstructionUserFile::getUserId, userIdList).list();
        // 10. 如果存在，则删除数据库对应记录和文件
        if (CollUtil.isNotEmpty(oldConstructionUserFileList)) {
            // 待删除id
            List<Long> deleteIds = oldConstructionUserFileList.stream().map(BusConstructionUserFile::getId).toList();
            // 待删除对象存储id
            List<Long> deleteOssIds = new ArrayList<>();
            for (BusConstructionUserFile constructionUserFile : oldConstructionUserFileList) {
                String path = constructionUserFile.getPath();
                if (StrUtil.isNotBlank(path)) {
                    String[] ossIds = path.split(",");
                    deleteOssIds.addAll(Arrays.stream(ossIds).map(Long::parseLong).toList());
                }
            }
            // 删除数据库记录
            boolean result = this.removeByIds(deleteIds);
            if (!result) {
                throw new ServiceException("数据库操作失败", HttpStatus.ERROR);
            }
            // 删除文件
            Boolean ossResult = ossService.deleteWithValidByIds(deleteOssIds, true);
            if (!ossResult) {
                throw new ServiceException("文件存储修改失败", HttpStatus.ERROR);
            }
        }
        // 11. 保存
        boolean result = this.saveBatch(constructionUserFileList);
        if (!result) {
            throw new ServiceException("数据库操作失败", HttpStatus.ERROR);
        }
        return true;
    }

    /**
     * 保存施工人员文件存储
     *
     * @param req 保存参数
     * @return 是否保存成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveFileList(BusConstructionUserFileSaveReq req) {
        // 1. 参数校验
        // 判断参数是否为空
        Long userId = req.getUserId();
        List<BusConstructionUserFileReq> fileList = req.getFileList();
        if (userId == null || CollUtil.isEmpty(fileList)) {
            throw new ServiceException("施工人员文件存储参数错误", HttpStatus.BAD_REQUEST);
        }
        // 校验修改用户是否存在
        BusConstructionUser constructionUser = constructionUserService.getById(userId);
        if (constructionUser == null) {
            throw new ServiceException("施工人员不存在", HttpStatus.NOT_FOUND);
        }
        // 判断登录用户是否有用户所在项目的操作权限
        Long projectId = constructionUser.getProjectId();
        Long loginUser = LoginHelper.getUserId();
        projectService.validAuth(projectId, loginUser);
        // 2. 查询当前用户的所有文件记录（一次性查询，避免多次访问数据库）
        LambdaQueryWrapper<BusConstructionUserFile> lqw = Wrappers.lambdaQuery(BusConstructionUserFile.class)
            .eq(BusConstructionUserFile::getUserId, userId);
        List<BusConstructionUserFile> constructionUserFileList = this.list(lqw);
        // 3. 构建 Map<fileType, BusConstructionUserFile> 方便查找
        Map<String, BusConstructionUserFile> existingFileMap = constructionUserFileList.stream()
            .collect(Collectors.toMap(BusConstructionUserFile::getFileType, Function.identity()));
        // 4. 处理新增或更新的数据
        List<BusConstructionUserFile> saveOrUpdateList = new ArrayList<>();
        // 处理删除的数据
        List<Long> deleteList = new ArrayList<>();
        for (BusConstructionUserFileReq fileReq : fileList) {
            String fileType = fileReq.getFileType();
            String fileId = fileReq.getFileId();
            if (existingFileMap.containsKey(fileType)) {
                // 如果存在，则更新
                BusConstructionUserFile existingFile = existingFileMap.get(fileType);
                // 如果文件id相同，则不更新
                if (StringUtils.equals(existingFile.getPath(), fileId)) {
                    continue;
                }
                // 如果文件id为空，则删除
                if (StringUtils.isBlank(fileId)) {
                    deleteList.add(existingFile.getId());
                    continue;
                }
                existingFile.setPath(fileId);
                saveOrUpdateList.add(existingFile);
            } else {
                // 如果不存在，且存在文件id则更新
                if (StringUtils.isNotBlank(fileId)) {
                    BusConstructionUserFile newFile = new BusConstructionUserFile();
                    newFile.setUserId(userId);
                    newFile.setFileType(fileType);
                    newFile.setPath(fileId);
                    saveOrUpdateList.add(newFile);
                }
            }
        }
        // 5. 批量保存或更新
        if (CollUtil.isNotEmpty(saveOrUpdateList)) {
            boolean result = this.saveOrUpdateBatch(saveOrUpdateList);
            if (!result) {
                throw new ServiceException("保存或更新施工人员文件存储失败", HttpStatus.ERROR);
            }
        }
        // 6. 批量删除
        if (CollUtil.isNotEmpty(deleteList)) {
            boolean result = this.removeBatchByIds(deleteList);
            if (!result) {
                throw new ServiceException("删除施工人员文件存储失败", HttpStatus.ERROR);
            }
        }
        return true;
    }

    /**
     * 获取施工人员文件存储查询条件封装
     *
     * @param req 查询条件
     * @return 查询条件封装
     */
    @Override
    public LambdaQueryWrapper<BusConstructionUserFile> buildQueryWrapper(BusConstructionUserFileQueryReq req) {
        LambdaQueryWrapper<BusConstructionUserFile> lqw = new LambdaQueryWrapper<>();
        if (req == null) {
            return lqw;
        }
        Long id = req.getId();
        Long userId = req.getUserId();
        String fileType = req.getFileType();
        String remark = req.getRemark();
        // 模糊查询
        lqw.like(StringUtils.isNotBlank(remark), BusConstructionUserFile::getRemark, remark);
        // 精确查询
        lqw.eq(ObjectUtils.isNotEmpty(id), BusConstructionUserFile::getId, id);
        lqw.eq(ObjectUtils.isNotEmpty(userId), BusConstructionUserFile::getUserId, userId);
        lqw.eq(StringUtils.isNotBlank(fileType), BusConstructionUserFile::getFileType, fileType);
        return lqw;
    }

    /**
     * 获取施工人员查询条件封装
     *
     * @param req 查询条件
     * @return 查询条件封装
     */
    @Override
    public LambdaQueryWrapper<BusConstructionUser> buildTemplateQueryWrapper(BusConstructionUserFileTemplateReq req) {
        LambdaQueryWrapper<BusConstructionUser> lqw = new LambdaQueryWrapper<>();
        if (req == null) {
            return lqw;
        }
        Long projectId = req.getProjectId();
        String userName = req.getUserName();
        Long contractorId = req.getContractorId();
        Long teamId = req.getTeamId();
        String typeOfWork = req.getTypeOfWork();
        String clock = req.getClock();
        List<Long> userIdList = req.getUserIdList();
        // 模糊查询
        lqw.like(StringUtils.isNotBlank(userName), BusConstructionUser::getUserName, userName);
        // 精确查询
        lqw.eq(ObjectUtils.isNotEmpty(projectId), BusConstructionUser::getProjectId, projectId);
        lqw.eq(ObjectUtils.isNotEmpty(contractorId), BusConstructionUser::getContractorId, contractorId);
        lqw.eq(ObjectUtils.isNotEmpty(teamId), BusConstructionUser::getTeamId, teamId);
        lqw.eq(StringUtils.isNotBlank(typeOfWork), BusConstructionUser::getTypeOfWork, typeOfWork);
        lqw.eq(StringUtils.isNotBlank(clock), BusConstructionUser::getClock, clock);
        if (CollUtil.isNotEmpty(userIdList)) {
            lqw.in(BusConstructionUser::getId, userIdList);
        }
        // 根据项目id获取黑名单施工人员
        List<Long> blacklistUserIdList = constructionBlacklistService.lambdaQuery()
            .eq(BusConstructionBlacklist::getProjectId, projectId)
            .list().stream().map(BusConstructionBlacklist::getUserId).toList();
        if (CollUtil.isNotEmpty(blacklistUserIdList)) {
            lqw.notIn(BusConstructionUser::getId, blacklistUserIdList);
        }
        return lqw;
    }

}
