package com.yifu.cloud.plus.v1.batch.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yifu.cloud.plus.v1.batch.constants.ArchiveConstants;
import com.yifu.cloud.plus.v1.batch.entity.TAttaInfo;
import com.yifu.cloud.plus.v1.batch.entity.TElectronicArchiveDetail;
import com.yifu.cloud.plus.v1.batch.entity.TElectronicArchiveTask;
import com.yifu.cloud.plus.v1.batch.entity.TTaskArchiveRelation;
import com.yifu.cloud.plus.v1.batch.mapper.TElectronicArchiveDetailMapper;
import com.yifu.cloud.plus.v1.batch.service.TAttaInfoService;
import com.yifu.cloud.plus.v1.batch.service.TElectronicArchiveDetailService;
import com.yifu.cloud.plus.v1.batch.service.TElectronicArchiveTaskService;
import com.yifu.cloud.plus.v1.batch.service.TTaskArchiveRelationService;
import com.yifu.cloud.plus.v1.batch.vo.*;
import com.yifu.cloud.plus.v1.yifu.archives.vo.TSettleDomainListVo;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.ClientNameConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.util.Common;
import com.yifu.cloud.plus.v1.yifu.common.core.util.OSSUtil;
import com.yifu.cloud.plus.v1.yifu.common.core.util.R;
import com.yifu.cloud.plus.v1.yifu.common.core.util.bean.OSSObjectBean;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
import com.yifu.cloud.plus.v1.yifu.common.dapr.util.ArchivesDaprUtil;
import com.yifu.cloud.plus.v1.yifu.common.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 电子档案明细表
 *
 * @author huych
 * @date 2025-12-17 11:03:37
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class TElectronicArchiveDetailServiceImpl extends ServiceImpl<TElectronicArchiveDetailMapper, TElectronicArchiveDetail> implements TElectronicArchiveDetailService {

	private final TAttaInfoService attaInfoService;
	private final TTaskArchiveRelationService relationService;
	private final TElectronicArchiveTaskService taskService;
	private final ArchivesDaprUtil archivesDaprUtil;

	@Autowired
	private OSSUtil ossUtil;

    /**
     * 电子档案明细表简单分页查询
     * @param tElectronicArchiveDetail 电子档案明细表
     * @return
     */
    @Override
    public IPage<TElectronicArchiveDetail> getTElectronicArchiveDetailPage(Page<TElectronicArchiveDetail> page, TElectronicArchiveDetailSearchVo tElectronicArchiveDetail){
		//权限赋值
		initSearchVo(tElectronicArchiveDetail);
		return baseMapper.getTElectronicArchiveDetailPage(page,tElectronicArchiveDetail);
    }

	/**
	 * 通过id查询电子档案明细表详情
	 * 附件信息按任务分组展示，每个任务的附件按资料类型分类
	 * 
	 * @param id 明细ID
	 * @return 明细详情
	 */
	@Override
	public R<TElectronicArchiveDetail> getDetailInfoById(String id) {
		TElectronicArchiveDetail detail = baseMapper.selectById(id);
		if (Common.isEmpty(detail)) {
			return R.failed(CommonConstants.NO_DATA_TO_HANDLE);
		}
		
		// 查询该明细关联的所有任务
		List<TTaskArchiveRelation> relations = relationService.list(Wrappers.<TTaskArchiveRelation>lambdaQuery()
				.eq(TTaskArchiveRelation::getArchiveDetailId, id));
		
		if (CollUtil.isEmpty(relations)) {
			return R.ok(detail);
		}
		
		// 按任务分组组织附件信息
		List<TaskAttachmentGroupVO> taskGroups = new ArrayList<>();
		
		for (TTaskArchiveRelation relation : relations) {
			// 查询任务信息
			TElectronicArchiveTask task = taskService.getById(relation.getTaskId());
			if (task == null || !ArchiveConstants.TASK_STATUS_GENERATED.equals(task.getStatus())) {
				// 任务不存在或未完成，跳过
				continue;
			}
			
			// 查询该任务上传的附件（domainId为明细ID，taskId为任务ID，用于区分同一个domainId下不同任务的附件）
			List<TAttaInfo> taskAttachments = attaInfoService.list(Wrappers.<TAttaInfo>lambdaQuery()
					.eq(TAttaInfo::getDomainId, id)
					.eq(TAttaInfo::getTaskId, relation.getTaskId())
					.eq(TAttaInfo::getRelationType, ArchiveConstants.ATTA_RELATION_TYPE_PER)
					.eq(TAttaInfo::getStatus, ArchiveConstants.ATTA_STATUS_ENABLED));
			
			if (CollUtil.isEmpty(taskAttachments)) {
				continue;
			}
			
			// 按资料类型分组附件
			Map<String, List<TAttaInfo>> materialTypeMap = new LinkedHashMap<>();
			
			for (TAttaInfo atta : taskAttachments) {
				// 从附件路径中提取资料类型
				// 路径格式：项目编码-姓名-身份证号/资料类型/文件名
				String materialType = extractMaterialTypeFromPath(atta.getAttaSrc());
				if (StrUtil.isBlank(materialType)) {
					materialType = "其他";
				}
				
				// 生成附件访问URL
				URL url = ossUtil.getObjectUrl("yf-hr-efile-source", atta.getAttaSrc());
				atta.setAttaUrl(url.toString());
				
				// 按资料类型分组
				materialTypeMap.computeIfAbsent(materialType, k -> new ArrayList<>()).add(atta);
			}
			
			// 创建任务附件分组VO
			TaskAttachmentGroupVO groupVO = new TaskAttachmentGroupVO();
			groupVO.setTaskId(task.getId());
			groupVO.setTaskCode(task.getTaskCode());
			// 设置创建人和创建时间
			String createUser = task.getCreateName();
			if (StrUtil.isBlank(createUser)) {
				createUser = task.getCreateBy();
			}
			groupVO.setCreateUser(createUser);
			groupVO.setCreateTime(task.getCreateTime());
			groupVO.setMaterialTypeAttachments(materialTypeMap);
			
			taskGroups.add(groupVO);
		}
		
		// 按上传时间倒序排序（最新的在前面）
		taskGroups.sort((g1, g2) -> g2.getCreateTime().compareTo(g1.getCreateTime()));
		
		detail.setTaskAttachmentGroups(taskGroups);
		
		return R.ok(detail);
	}
	
	/**
	 * 从附件路径中提取资料类型
	 * 路径格式：项目编码-姓名-身份证号/资料类型/文件名
	 * 
	 * @param attaSrc 附件路径
	 * @return 资料类型
	 */
	private String extractMaterialTypeFromPath(String attaSrc) {
		if (StrUtil.isBlank(attaSrc)) {
			return "";
		}
		
		try {
			// 按/分割路径
			String[] parts = attaSrc.split("/");
			if (parts.length >= 2) {
				// 第二部分应该是资料类型
				String materialType = parts[parts.length - 2];
				
				// 验证是否是有效的资料类型
				if (ArchiveConstants.MATERIAL_TYPE_STOCK.equals(materialType) ||
					ArchiveConstants.MATERIAL_TYPE_NEW.equals(materialType) ||
					ArchiveConstants.MATERIAL_TYPE_RENEW.equals(materialType) ||
					ArchiveConstants.MATERIAL_TYPE_LEAVE.equals(materialType)) {
					return materialType;
				}
			}
		} catch (Exception e) {
			log.error("从附件路径提取资料类型失败，路径：{}", attaSrc, e);
		}
		
		return "";
	}

	/**
	 * 批量下载电子档案
	 * 
	 * @param requestList 下载请求列表
	 * @param response HTTP响应
	 * @author huych
	 * @date 2025-12-17
	 */
	@Override
	public void batchDownloadArchives(List<ArchiveBatchDownloadRequestVO> requestList, HttpServletResponse response) {

		// 反馈列表
		List<ArchiveDownloadFeedbackVO> feedbackList = new ArrayList<>();

		// 用于存储需要下载的档案及其附件
		Map<String, ArchiveDownloadInfo> downloadMap = new LinkedHashMap<>();

		List<String> deptNoList = new ArrayList<>();
		YifuUser user = SecurityUtils.getUser();
		boolean isCheck = true;
		List<Long> roleList = user.getClientRoleMap().get(ClientNameConstants.CLIENT_MVP);
		if (CommonConstants.ZERO_STRING.equals(user.getSystemFlag()) || (Common.isNotNull(roleList)
				&& (roleList.stream().anyMatch(role -> role == 1569585030423158786L)
				|| roleList.stream().anyMatch(role -> role == 2003347621962379266L)))) {
			isCheck = false;
		}

		if (Common.isNotNull(user) && isCheck) {
			deptNoList = getDeptNoList(user.getId());
		}

		try {
			//重复数据检测
			Map<String,String> map = new HashMap<>();

			// 1. 校验请求并查询档案信息
			for (ArchiveBatchDownloadRequestVO request : requestList) {
				try {
					validateAndPrepareDownload(request, downloadMap, feedbackList, isCheck, deptNoList, map);
				} catch (Exception e) {
					log.error("处理下载请求失败，姓名：{}，身份证：{}，项目编码：{}",
							request.getEmpName(), request.getEmpIdcard(), request.getDeptNo(), e);
					feedbackList.add(createFeedbackVO(request, "系统异常：" + e.getMessage(),null));
				}
			}

			// 2. 校验文件总大小是否超过500MB
			long totalSize = calculateTotalFileSize(downloadMap);
			long maxSize = 500 * 1024 * 1024L; // 500MB
			if (totalSize > maxSize) {
				log.warn("批量下载文件总大小超过限制，总大小：{}MB，限制：500MB", totalSize / 1024 / 1024);
				response.setContentType("application/json;charset=UTF-8");
				response.setCharacterEncoding("UTF-8");
				String errorMsg = String.format("{\"code\":1,\"msg\":\"下载文件总大小超过限制，当前总大小：%.2fMB，最大允许：500MB\"}",
						totalSize / 1024.0 / 1024.0);
				response.getWriter().write(errorMsg);
				return;
			}

			// 3. 设置响应头
			String fileName = "电子档案批量下载_" +
					LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".zip";
			response.setContentType("application/zip");
			response.setCharacterEncoding("UTF-8");
			response.setHeader(CommonConstants.CONTENT_DISPOSITION,
					"attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));

			// 4. 创建ZIP输出流
			try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {

				// 5. 下载并打包档案文件
				for (Map.Entry<String, ArchiveDownloadInfo> entry : downloadMap.entrySet()) {
					String folderName = entry.getKey();
					ArchiveDownloadInfo downloadInfo = entry.getValue();

					try {
						downloadArchiveFiles(zipOut, folderName, downloadInfo);

						// 更新反馈状态为成功
						// 使用materialTypeAttaMap的所有key来匹配，确保所有资料类型的反馈都被更新
						Set<String> materialTypes = downloadInfo.getMaterialTypeAttaMap().keySet();
						for (ArchiveDownloadFeedbackVO feedback : feedbackList) {
							if (feedback.getEmpName().equals(downloadInfo.getDetail().getEmpName()) &&
									feedback.getEmpIdcard().equals(downloadInfo.getDetail().getEmpIdcard()) &&
									feedback.getDeptNo().equals(downloadInfo.getDetail().getDeptNo()) &&
									(materialTypes.contains(feedback.getMaterialType()) || "全部资料类型".equals(feedback.getMaterialType()))) {
								feedback.setDownloadStatus("成功");
								feedback.setFailReason("");
							}
						}
					} catch (Exception e) {
						log.error("下载档案文件失败，文件夹：{}", folderName, e);
						// 更新反馈状态为失败
						Set<String> materialTypes = downloadInfo.getMaterialTypeAttaMap().keySet();
						for (ArchiveDownloadFeedbackVO feedback : feedbackList) {
							if (feedback.getEmpName().equals(downloadInfo.getDetail().getEmpName()) &&
									feedback.getEmpIdcard().equals(downloadInfo.getDetail().getEmpIdcard()) &&
									feedback.getDeptNo().equals(downloadInfo.getDetail().getDeptNo()) &&
									(materialTypes.contains(feedback.getMaterialType()) || "全部资料类型".equals(feedback.getMaterialType()))) {
								feedback.setDownloadStatus("失败");
								feedback.setFailReason("下载文件失败：" + e.getMessage());
							}
						}
					}
				}

				// 6. 生成并添加反馈Excel表
				addFeedbackExcel(zipOut, feedbackList);

				zipOut.finish();
			}

		} catch (Exception e) {
			log.error("批量下载电子档案失败", e);
		}
	}

	/**
	 * 获取项目权限
	 *
	 * @author huych
	 * @param userId
	 * @return {@link List< String>}
	 */
	public List<String> getDeptNoList(String userId){
		List<String> deptList = new ArrayList<>(16);
		try{
			R<TSettleDomainListVo> settleDomainList = archivesDaprUtil.getSettleDomainIdsByUserId(userId);
			if(Common.isNotNull(settleDomainList) && settleDomainList.getCode() == CommonConstants.SUCCESS){
				TSettleDomainListVo data = settleDomainList.getData();
				if (Optional.ofNullable(data).isPresent()){
					deptList = data.getDeptNos();
				}
			}
		}catch (Exception e){
			log.error("查询项目信息出错："+e);
		}
		return deptList;
	}

	/**
	 * 校验请求并准备下载信息
	 * 
	 * @param request 下载请求
	 * @param downloadMap 下载信息Map
	 * @param feedbackList 反馈列表
	 */
	private void validateAndPrepareDownload(ArchiveBatchDownloadRequestVO request, 
											Map<String, ArchiveDownloadInfo> downloadMap,
											List<ArchiveDownloadFeedbackVO> feedbackList,
											boolean isCheck,
											List<String> deptNoList,
											Map<String,String> map) {

		// 1. 根据姓名、身份证、项目编码、项目名称查询电子档案明细
		LambdaQueryWrapper<TElectronicArchiveDetail> wrapper = Wrappers.<TElectronicArchiveDetail>lambdaQuery()
				.eq(TElectronicArchiveDetail::getEmpName, request.getEmpName())
				.eq(TElectronicArchiveDetail::getEmpIdcard, request.getEmpIdcard())
				.eq(TElectronicArchiveDetail::getDeptNo, request.getDeptNo())
				.eq(TElectronicArchiveDetail::getStatus, ArchiveConstants.DETAIL_STATUS_ENABLED);
		
		// 如果项目名称不为空，添加项目名称条件
		if (StrUtil.isNotBlank(request.getDeptName())) {
			wrapper.eq(TElectronicArchiveDetail::getDeptName, request.getDeptName());
		}
		
		// 查询档案明细
		TElectronicArchiveDetail detail = baseMapper.selectOne(wrapper);

		StringBuilder s = new StringBuilder();
		s.append(request.getEmpIdcard());
		s.append("_");
		s.append(request.getDeptNo());
		s.append("_");
		s.append(request.getDeptName());
		s.append("_");
		s.append(request.getEmpName());
		s.append("_");
		s.append(request.getMaterialType());
		if (null != map.get(s.toString())) {
			feedbackList.add(createFeedbackVO(request, "存在重复的导出人员信息",detail));
			return;
		}else {
			map.put(s.toString(), "1");
		}


		//项目权限校验
		if (isCheck && !deptNoList.contains(request.getDeptNo())) {
			feedbackList.add(createFeedbackVO(request, "没有该项目的权限",detail));
			return;
		}

		// 2. 如果未找到档案，添加失败反馈
		if (Common.isEmpty(detail)) {
			feedbackList.add(createFeedbackVO(request, "没找到这个人和项目对应的信息",detail));
			return;
		}
		
		// 3. 判断是否下载所有资料类型
		boolean downloadAllTypes = StrUtil.isBlank(request.getMaterialType());
		
		if (downloadAllTypes) {
			// 资料类型为空，下载该人员该项目下所有资料类型
			// 查询该明细下所有可用附件
			List<TAttaInfo> allAttaList = attaInfoService.list(Wrappers.<TAttaInfo>lambdaQuery()
					.eq(TAttaInfo::getDomainId, detail.getId())
					.eq(TAttaInfo::getRelationType, ArchiveConstants.ATTA_RELATION_TYPE_PER)
					.eq(TAttaInfo::getStatus, ArchiveConstants.ATTA_STATUS_ENABLED));
			
			if (CollUtil.isEmpty(allAttaList)) {
				feedbackList.add(createFeedbackVO(request, "该人员在此项目下没有可用的附件", detail));
				return;
			}
			
			// 构建文件夹名称：项目编码-姓名-身份证
			String folderName = detail.getDeptNo() + "-" + detail.getEmpName() + "-" + detail.getEmpIdcard();
			
			// 按资料类型分组附件
			Map<String, List<TAttaInfo>> materialTypeAttaMap = new LinkedHashMap<>();
			for (TAttaInfo atta : allAttaList) {
				String materialType = extractMaterialTypeFromPath(atta.getAttaSrc());
				if (StrUtil.isBlank(materialType)) {
					materialType = "其他";
				}
				materialTypeAttaMap.computeIfAbsent(materialType, k -> new ArrayList<>()).add(atta);
			}
			
			// 添加到下载Map
			ArchiveDownloadInfo downloadInfo = downloadMap.get(folderName);
			if (downloadInfo == null) {
				downloadInfo = new ArchiveDownloadInfo();
				downloadInfo.setDetail(detail);
				downloadInfo.setMaterialTypeAttaMap(materialTypeAttaMap);
				downloadMap.put(folderName, downloadInfo);
			} else {
				// 合并资料类型附件
				downloadInfo.getMaterialTypeAttaMap().putAll(materialTypeAttaMap);
			}
			downloadInfo.setMaterialType("全部资料类型");
			
			// 添加成功的反馈
			ArchiveBatchDownloadRequestVO feedbackRequest = new ArchiveBatchDownloadRequestVO();
			feedbackRequest.setEmpName(request.getEmpName());
			feedbackRequest.setEmpIdcard(request.getEmpIdcard());
			feedbackRequest.setDeptNo(request.getDeptNo());
			feedbackRequest.setDeptName(request.getDeptName());
			feedbackRequest.setMaterialType("全部资料类型");
			feedbackList.add(createSuccessFeedbackVO(feedbackRequest,detail));
			
		} else {
			// 下载指定资料类型
			// 检查资料类型是否匹配（支持多个资料类型，用逗号分隔）
			if (StrUtil.isBlank(detail.getMaterialType()) || 
				!detail.getMaterialType().contains(request.getMaterialType())) {
				feedbackList.add(createFeedbackVO(request, "该人员在此项目下没有【" + request.getMaterialType() + "】类型的资料",detail));
				return;
			}
			
			// 查询该明细下指定资料类型的附件（状态为可用）
			List<TAttaInfo> attaList = attaInfoService.list(Wrappers.<TAttaInfo>lambdaQuery()
					.eq(TAttaInfo::getDomainId, detail.getId())
					.eq(TAttaInfo::getRelationType, ArchiveConstants.ATTA_RELATION_TYPE_PER)
					.eq(TAttaInfo::getStatus, ArchiveConstants.ATTA_STATUS_ENABLED)
					.like(TAttaInfo::getAttaSrc, request.getMaterialType())); // 附件路径中包含资料类型
			
			// 如果没有附件，添加失败反馈
			if (CollUtil.isEmpty(attaList)) {
				feedbackList.add(createFeedbackVO(request, "该资料类型下没有可用的附件",detail));
				return;
			}
			
			// 构建文件夹名称：项目编码-姓名-身份证
			String folderName = detail.getDeptNo() + "-" + detail.getEmpName() + "-" + detail.getEmpIdcard();
			
			// 添加到下载Map
			ArchiveDownloadInfo downloadInfo = downloadMap.get(folderName);
			if (downloadInfo == null) {
				downloadInfo = new ArchiveDownloadInfo();
				downloadInfo.setDetail(detail);
				downloadInfo.setMaterialTypeAttaMap(new HashMap<>());
				downloadMap.put(folderName, downloadInfo);
			}
			
			// 按资料类型分组附件
			if (!downloadInfo.getMaterialTypeAttaMap().containsKey(request.getMaterialType())) {
				downloadInfo.getMaterialTypeAttaMap().put(request.getMaterialType(), attaList);
			}
			// 不再使用单个materialType字段，而是使用materialTypeAttaMap的所有key
			
			// 添加成功的反馈（状态暂时为"处理中"，实际下载完成后更新）
			feedbackList.add(createSuccessFeedbackVO(request,detail));
		}
	}
	
	/**
	 * 下载档案文件到ZIP
	 * 
	 * @param zipOut ZIP输出流
	 * @param folderName 文件夹名称
	 * @param downloadInfo 下载信息
	 */
	private void downloadArchiveFiles(ZipOutputStream zipOut, String folderName, 
									  ArchiveDownloadInfo downloadInfo) throws Exception {
		
		Map<String, List<TAttaInfo>> materialTypeAttaMap = downloadInfo.getMaterialTypeAttaMap();
		
		// 遍历每个资料类型
		for (Map.Entry<String, List<TAttaInfo>> entry : materialTypeAttaMap.entrySet()) {
			String materialType = entry.getKey();
			List<TAttaInfo> attaList = entry.getValue();
			
			// 创建二级文件夹：资料类型
			String subFolderPath = folderName + "/" + materialType + "/";
			
			// 准备OSS对象Bean列表
			List<OSSObjectBean> ossObjectList = new ArrayList<>();
			for (TAttaInfo atta : attaList) {
				OSSObjectBean bean = new OSSObjectBean();
				bean.setKey(atta.getAttaSrc()); // OSS文件路径
				bean.setName(subFolderPath + atta.getAttaName()); // ZIP中的完整路径
				ossObjectList.add(bean);
			}
			
			// 批量下载文件到ZIP
			if (CollUtil.isNotEmpty(ossObjectList)) {
				ossUtil.getObjectZip("yf-hr-efile-source", ossObjectList, zipOut);
			}
		}
	}
	
	/**
	 * 添加反馈Excel到ZIP
	 * 
	 * @param zipOut ZIP输出流
	 * @param feedbackList 反馈列表
	 */
	private void addFeedbackExcel(ZipOutputStream zipOut, List<ArchiveDownloadFeedbackVO> feedbackList) 
			throws IOException {
		
		try {
			// 创建Excel内存流
			ByteArrayOutputStream excelOut = new ByteArrayOutputStream();
			
			// 使用EasyExcel写入Excel
			EasyExcel.write(excelOut, ArchiveDownloadFeedbackVO.class)
					.sheet("下载结果反馈")
					.doWrite(feedbackList);
			
			// 添加Excel到ZIP
			ZipEntry excelEntry = new ZipEntry("下载结果反馈表.xlsx");
			zipOut.putNextEntry(excelEntry);
			zipOut.write(excelOut.toByteArray());
			zipOut.closeEntry();
			excelOut.close();
			
		} catch (Exception e) {
			log.error("生成反馈Excel失败", e);
		}
	}
	
	/**
	 * 创建失败反馈VO
	 * 
	 * @param request 请求VO
	 * @param failReason 失败原因
	 * @return 反馈VO
	 */
	private ArchiveDownloadFeedbackVO createFeedbackVO(ArchiveBatchDownloadRequestVO request, String failReason
			,TElectronicArchiveDetail detail) {
		ArchiveDownloadFeedbackVO feedback = new ArchiveDownloadFeedbackVO();
		feedback.setEmpName(request.getEmpName());
		feedback.setEmpIdcard(request.getEmpIdcard());
		feedback.setDeptNo(request.getDeptNo());
		feedback.setDeptName(request.getDeptName());
		feedback.setMaterialType(request.getMaterialType());
		if (Common.isNotNull(detail)) {
			feedback.setCustomerCode(detail.getCustomerCode());
			feedback.setCustomerName(detail.getCustomerName());
			feedback.setFileAddress(detail.getFileAddress());
		}
		feedback.setDownloadStatus("失败");
		feedback.setFailReason(failReason);
		return feedback;
	}
	
	/**
	 * 创建成功反馈VO
	 * 
	 * @param request 请求VO
	 * @return 反馈VO
	 */
	private ArchiveDownloadFeedbackVO createSuccessFeedbackVO(ArchiveBatchDownloadRequestVO request,TElectronicArchiveDetail detail) {
		ArchiveDownloadFeedbackVO feedback = new ArchiveDownloadFeedbackVO();
		feedback.setEmpName(request.getEmpName());
		feedback.setEmpIdcard(request.getEmpIdcard());
		feedback.setDeptNo(request.getDeptNo());
		feedback.setDeptName(request.getDeptName());
		feedback.setMaterialType(request.getMaterialType());
		if (Common.isNotNull(detail)) {
			feedback.setCustomerCode(detail.getCustomerCode());
			feedback.setCustomerName(detail.getCustomerName());
			feedback.setFileAddress(detail.getFileAddress());
		}
		feedback.setDownloadStatus("处理中");
		feedback.setFailReason("");
		return feedback;
	}
	
	/**
	 * 下载信息内部类
	 */
	private static class ArchiveDownloadInfo {
		private TElectronicArchiveDetail detail;
		private Map<String, List<TAttaInfo>> materialTypeAttaMap;
		private String materialType;
		
		public TElectronicArchiveDetail getDetail() {
			return detail;
		}
		
		public void setDetail(TElectronicArchiveDetail detail) {
			this.detail = detail;
		}
		
		public Map<String, List<TAttaInfo>> getMaterialTypeAttaMap() {
			return materialTypeAttaMap;
		}
		
		public void setMaterialTypeAttaMap(Map<String, List<TAttaInfo>> materialTypeAttaMap) {
			this.materialTypeAttaMap = materialTypeAttaMap;
		}
		
		public String getMaterialType() {
			return materialType;
		}
		
		public void setMaterialType(String materialType) {
			this.materialType = materialType;
		}
	}
	
	/**
	 * 删除电子档案附件
	 * <p>
	 * 功能说明：
	 * 1. 校验请求参数（明细ID、任务ID、资料类型、附件ID列表）
	 * 2. 校验附件是否属于指定的明细、任务和资料类型
	 * 3. 将附件状态更新为不可用（逻辑删除）
	 * 4. 同时删除OSS上的文件
	 * 5. 如果该资料类型下的所有附件都被删除，则从明细的资料类型字段中移除该类型
	 * </p>
	 *
	 * @param request 删除请求
	 * @return 删除结果
	 * @author huych
	 * @date 2025-12-17
	 */
	@Override
	public R<String> deleteArchiveAttachments(ArchiveAttachmentDeleteRequestVO request) {

		try {
			// 1. 校验明细是否存在
			TElectronicArchiveDetail detail = baseMapper.selectById(request.getDetailId());
			if (Common.isEmpty(detail)) {
				return R.failed("电子档案明细不存在");
			}
			
			// 2. 校验资料类型是否有效
			if (isValidMaterialType(request.getMaterialType())) {
				return R.failed("资料类型不正确，必须为：存量、新签、续签、减员");
			}
			
			// 3. 查询要删除的附件
			List<TAttaInfo> attachments = attaInfoService.listByIds(request.getAttachmentIds());
			if (CollUtil.isEmpty(attachments)) {
				return R.failed("附件不存在");
			}
			
			// 4. 校验附件是否都属于该明细
			for (TAttaInfo atta : attachments) {
				if (!request.getDetailId().equals(atta.getDomainId())) {
					return R.failed("附件【" + atta.getAttaName() + "】不属于该档案明细");
				}
				
				// 校验附件关系类型是否为ZIP附件
				if (!ArchiveConstants.ATTA_RELATION_TYPE_PER.equals(atta.getRelationType())) {
					return R.failed("附件【" + atta.getAttaName() + "】不是电子档案附件");
				}
				
				// 校验附件是否属于指定的资料类型（从路径中提取）
				String materialType = extractMaterialTypeFromPath(atta.getAttaSrc());
				if (!request.getMaterialType().equals(materialType)) {
					return R.failed("附件【" + atta.getAttaName() + "】不属于资料类型【" + request.getMaterialType() + "】");
				}
			}
			
			// 5. 逻辑删除附件（将状态更新为不可用）
			int successCount = 0;
			int failCount = 0;
			StringBuilder failReasons = new StringBuilder();
			
			for (TAttaInfo atta : attachments) {
				try {
					boolean updated = attaInfoService.removeById(atta);
					
					if (updated) {
						// 删除OSS文件
						try {
							ossUtil.deleteObject("yf-hr-efile-source", atta.getAttaSrc());
							successCount++;
						} catch (Exception e) {
							log.error("删除OSS文件失败，附件ID：{}，路径：{}", atta.getId(), atta.getAttaSrc(), e);
							// OSS删除失败不影响整体流程，已经逻辑删除了
							successCount++;
						}
					} else {
						failCount++;
						failReasons.append(atta.getAttaName()).append("；");
					}
				} catch (Exception e) {
					log.error("删除附件失败，附件ID：{}", atta.getId(), e);
					failCount++;
					failReasons.append(atta.getAttaName()).append("；");
				}
			}
			
			// 6. 检查该资料类型下是否还有其他可用附件
			LambdaQueryWrapper<TAttaInfo> attaWrapper = Wrappers.<TAttaInfo>lambdaQuery()
					.eq(TAttaInfo::getDomainId, request.getDetailId())
					.eq(TAttaInfo::getRelationType, ArchiveConstants.ATTA_RELATION_TYPE_PER)
					.eq(TAttaInfo::getStatus, ArchiveConstants.ATTA_STATUS_ENABLED)
					.like(TAttaInfo::getAttaSrc, "/" + request.getMaterialType() + "/");
			long remainingCount = attaInfoService.count(attaWrapper);
			
			// 7. 如果该资料类型下没有附件了，从明细的资料类型字段中移除
			if (remainingCount == 0) {
				updateDetailMaterialTypeAfterDelete(detail, request.getMaterialType());
			}
			
			// 8. 返回结果
			String resultMsg = String.format("删除完成，成功：%d个，失败：%d个", successCount, failCount);
			if (failCount > 0) {
				resultMsg += "。失败的附件：" + failReasons.toString();
			}

			return R.ok(null, resultMsg);
			
		} catch (Exception e) {
			log.error("删除电子档案附件失败，明细ID：{}", request.getDetailId(), e);
			return R.failed("删除失败：" + e.getMessage());
		}
	}
	
	/**
	 * 判断是否是有效的资料类型
	 */
	private boolean isValidMaterialType(String typeName) {
		return !ArchiveConstants.MATERIAL_TYPE_STOCK.equals(typeName) &&
				!ArchiveConstants.MATERIAL_TYPE_NEW.equals(typeName) &&
				!ArchiveConstants.MATERIAL_TYPE_RENEW.equals(typeName) &&
				!ArchiveConstants.MATERIAL_TYPE_LEAVE.equals(typeName);
	}
	
	/**
	 * 删除附件后更新明细表的资料类型
	 * 从资料类型字段中移除指定的资料类型
	 *
	 * @param detail 档案明细
	 * @param removedMaterialType 要移除的资料类型
	 */
	private void updateDetailMaterialTypeAfterDelete(TElectronicArchiveDetail detail, String removedMaterialType) {
		try {
			if (StrUtil.isBlank(detail.getMaterialType())) {
				return;
			}
			
			// 解析资料类型列表
			List<String> materialTypes = new ArrayList<>(Arrays.asList(detail.getMaterialType().split(",")));
			
			// 移除指定的资料类型
			materialTypes.remove(removedMaterialType);
			
			// 更新明细
			if (materialTypes.isEmpty()) {
				// 如果所有资料类型都被删除了，将状态改为不可用
				detail.setMaterialType("");
				detail.setStatus(ArchiveConstants.DETAIL_STATUS_DISABLED);
			} else {
				detail.setMaterialType(String.join(",", materialTypes));
			}
			
			baseMapper.updateById(detail);
			
		} catch (Exception e) {
			log.error("更新档案明细资料类型失败，明细ID：{}", detail.getId(), e);
		}
	}
	
	/**
	 * 勾选导出电子档案
	 * <p>
	 * 功能说明：
	 * 1. 根据明细ID、任务ID、资料类型列表导出指定的附件
	 * 2. 导出格式与批量下载功能保持一致
	 * 3. 一级文件夹：项目编码-姓名-身份证
	 * 4. 二级文件夹：资料类型，该文件夹下为具体附件
	 * 5. 压缩包命名规则为：电子档案勾选导出_时间戳.zip
	 * 6. 只导出status为可用（0）的附件
	 * </p>
	 *
	 * @param requestList 导出请求列表
	 * @param response HTTP响应
	 * @author huych
	 * @date 2025-12-17
	 */
	@Override
	public void selectedExportArchives(List<ArchiveSelectedExportRequestVO> requestList, HttpServletResponse response) {

		// 反馈列表
		List<ArchiveDownloadFeedbackVO> feedbackList = new ArrayList<>();
		
		// 用于存储需要导出的档案及其附件
		Map<String, ArchiveDownloadInfo> downloadMap = new LinkedHashMap<>();

		try {
			// 1. 校验请求并查询档案信息
			for (ArchiveSelectedExportRequestVO request : requestList) {
				try {
					validateAndPrepareSelectedExport(request, downloadMap, feedbackList);
				} catch (Exception e) {
					log.error("处理勾选导出请求失败，明细ID：{}，任务ID：{}", 
							request.getDetailId(), request.getTaskId(), e);
					feedbackList.add(createSelectedExportFeedbackVO(request, "系统异常：" + e.getMessage()));
				}
			}
			
			// 2. 检查是否有可导出的档案
			if (downloadMap.isEmpty()) {
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().write("{\"code\":1,\"msg\":\"没有可导出的档案\"}");
				return;
			}

			// 2. 校验文件总大小是否超过500MB
			long totalSize = calculateTotalFileSize(downloadMap);
			long maxSize = 500 * 1024 * 1024L; // 500MB
			if (totalSize > maxSize) {
				log.warn("批量下载文件总大小超过限制，总大小：{}MB，限制：500MB", totalSize / 1024 / 1024);
				response.setContentType("application/json;charset=UTF-8");
				response.setCharacterEncoding("UTF-8");
				String errorMsg = String.format("{\"code\":1,\"msg\":\"下载文件总大小超过限制，当前总大小：%.2fMB，最大允许：500MB\"}",
						totalSize / 1024.0 / 1024.0);
				response.getWriter().write(errorMsg);
				return;
			}

			// 3. 设置响应头
			String fileName = "电子档案勾选导出_" + 
					LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".zip";
			response.setContentType("application/zip");
			response.setCharacterEncoding("UTF-8");
			response.setHeader(CommonConstants.CONTENT_DISPOSITION,
					"attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
			
			// 4. 创建ZIP输出流
			try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
				
				// 5. 下载并打包档案文件
				for (Map.Entry<String, ArchiveDownloadInfo> entry : downloadMap.entrySet()) {
					String folderName = entry.getKey();
					ArchiveDownloadInfo downloadInfo = entry.getValue();
					
					try {
						downloadArchiveFiles(zipOut, folderName, downloadInfo);
						
						// 更新反馈状态为成功
						updateFeedbackStatus(feedbackList, downloadInfo, "成功", "");
					} catch (Exception e) {
						log.error("导出档案文件失败，文件夹：{}", folderName, e);
						// 更新反馈状态为失败
						updateFeedbackStatus(feedbackList, downloadInfo, "失败", "导出文件失败：" + e.getMessage());
					}
				}
				
				// 6. 生成并添加反馈Excel表
				addFeedbackExcel(zipOut, feedbackList);
				
				zipOut.finish();
			}
			
		} catch (Exception e) {
			log.error("勾选导出电子档案失败", e);
		}
	}
	
	/**
	 * 校验请求并准备勾选导出信息
	 * 
	 * @param request 导出请求
	 * @param downloadMap 下载信息Map
	 * @param feedbackList 反馈列表
	 */
	private void validateAndPrepareSelectedExport(ArchiveSelectedExportRequestVO request, 
												  Map<String, ArchiveDownloadInfo> downloadMap,
												  List<ArchiveDownloadFeedbackVO> feedbackList) {
		
		// 1. 校验明细是否存在
		TElectronicArchiveDetail detail = baseMapper.selectById(request.getDetailId());
		if (Common.isEmpty(detail)) {
			feedbackList.add(createSelectedExportFeedbackVO(request, "电子档案明细不存在"));
			return;
		}
		
		// 2. 校验任务是否存在
		TElectronicArchiveTask task = taskService.getById(request.getTaskId());
		if (task == null) {
			feedbackList.add(createSelectedExportFeedbackVO(request, "任务不存在"));
			return;
		}
		
		// 3. 校验任务和明细的关联关系
		LambdaQueryWrapper<TTaskArchiveRelation> relationWrapper = Wrappers.<TTaskArchiveRelation>lambdaQuery()
				.eq(TTaskArchiveRelation::getTaskId, request.getTaskId())
				.eq(TTaskArchiveRelation::getArchiveDetailId, request.getDetailId());
		TTaskArchiveRelation relation = relationService.getOne(relationWrapper);
		if (relation == null) {
			feedbackList.add(createSelectedExportFeedbackVO(request, "任务和档案明细没有关联关系"));
			return;
		}
		
		// 4. 校验资料类型是否为空
		if (CollUtil.isEmpty(request.getMaterialTypes())) {
			feedbackList.add(createSelectedExportFeedbackVO(request, "资料类型列表不能为空"));
			return;
		}
		
		// 5. 构建文件夹名称：项目编码-姓名-身份证
		String folderName = detail.getDeptNo() + "-" + detail.getEmpName() + "-" + detail.getEmpIdcard();

		// 6. 按资料类型查询附件
		Map<String, List<TAttaInfo>> materialTypeAttaMap = new LinkedHashMap<>();
		
		for (String materialType : request.getMaterialTypes()) {
			// 校验资料类型是否有效
			if (isValidMaterialType(materialType)) {
				continue;
			}
			
			// 查询该明细下该任务的指定资料类型的附件
			// 通过附件路径匹配资料类型和任务
			List<TAttaInfo> attaList = attaInfoService.list(Wrappers.<TAttaInfo>lambdaQuery()
					.eq(TAttaInfo::getDomainId, request.getDetailId())
					.eq(TAttaInfo::getTaskId, request.getTaskId())
					.eq(TAttaInfo::getRelationType, ArchiveConstants.ATTA_RELATION_TYPE_PER)
					.eq(TAttaInfo::getStatus, ArchiveConstants.ATTA_STATUS_ENABLED)
					.like(TAttaInfo::getAttaSrc, materialType)); // 附件路径中包含资料类型
			
			// 如果该资料类型下有附件，添加到Map中
			if (CollUtil.isNotEmpty(attaList)) {
				materialTypeAttaMap.put(materialType, attaList);
				
				// 添加成功的反馈
				feedbackList.add(createSelectedExportSuccessFeedbackVO(detail, task, materialType));
			} else {
				feedbackList.add(createSelectedExportFeedbackVOWithDetail(detail, task, materialType, 
						"该资料类型下没有可用的附件"));
			}
		}
		
		// 7. 如果有可导出的附件，添加到下载Map
		if (!materialTypeAttaMap.isEmpty()) {
			ArchiveDownloadInfo downloadInfo = downloadMap.get(folderName);
			if (downloadInfo == null) {
				downloadInfo = new ArchiveDownloadInfo();
				downloadInfo.setDetail(detail);
				downloadInfo.setMaterialTypeAttaMap(materialTypeAttaMap);
				downloadMap.put(folderName, downloadInfo);
			} else {
				Map<String, List<TAttaInfo>> targetMap = downloadInfo.getMaterialTypeAttaMap();
				// 合并资料类型附件
				for (Map.Entry<String, List<TAttaInfo>> entry : materialTypeAttaMap.entrySet()) {
					String key = entry.getKey();
					List<TAttaInfo> sourceValue = entry.getValue();

					if (targetMap.containsKey(key)) {
						// 如果key已存在，合并两个List
						List<TAttaInfo> targetValue = targetMap.get(key);
						targetValue.addAll(sourceValue);
						materialTypeAttaMap.put(key, targetValue);

					}
					downloadInfo.getMaterialTypeAttaMap().putAll(materialTypeAttaMap);
				}

			}
			
			// 设置资料类型列表
			downloadInfo.setMaterialType(String.join(",", materialTypeAttaMap.keySet()));
		}
	}
	
	/**
	 * 创建勾选导出失败反馈VO
	 * 
	 * @param request 请求VO
	 * @param failReason 失败原因
	 * @return 反馈VO
	 */
	private ArchiveDownloadFeedbackVO createSelectedExportFeedbackVO(ArchiveSelectedExportRequestVO request, String failReason) {
		ArchiveDownloadFeedbackVO feedback = new ArchiveDownloadFeedbackVO();
		feedback.setEmpName("未知");
		feedback.setEmpIdcard("未知");
		feedback.setDeptNo("未知");
		feedback.setDeptName("未知");
		feedback.setMaterialType(request.getMaterialTypes() != null ? String.join(",", request.getMaterialTypes()) : "");
		feedback.setCustomerCode("");
		feedback.setCustomerName("");
		feedback.setFileAddress("");
		feedback.setDownloadStatus("失败");
		feedback.setFailReason(failReason);
		return feedback;
	}
	
	/**
	 * 创建勾选导出失败反馈VO（带明细和任务信息）
	 * 
	 * @param detail 明细
	 * @param task 任务
	 * @param materialType 资料类型
	 * @param failReason 失败原因
	 * @return 反馈VO
	 */
	private ArchiveDownloadFeedbackVO createSelectedExportFeedbackVOWithDetail(TElectronicArchiveDetail detail, 
																		   TElectronicArchiveTask task,
																		   String materialType, 
																		   String failReason) {
		ArchiveDownloadFeedbackVO feedback = new ArchiveDownloadFeedbackVO();
		feedback.setEmpName(detail.getEmpName());
		feedback.setEmpIdcard(detail.getEmpIdcard());
		feedback.setDeptNo(detail.getDeptNo());
		feedback.setDeptName(detail.getDeptName());
		feedback.setMaterialType(materialType);
		feedback.setCustomerCode(detail.getCustomerCode());
		feedback.setCustomerName(detail.getCustomerName());
		feedback.setFileAddress(detail.getFileAddress());
		feedback.setDownloadStatus("失败");
		feedback.setFailReason(failReason);
		return feedback;
	}
	
	/**
	 * 创建勾选导出成功反馈VO
	 * 
	 * @param detail 明细
	 * @param task 任务
	 * @param materialType 资料类型
	 * @return 反馈VO
	 */
	private ArchiveDownloadFeedbackVO createSelectedExportSuccessFeedbackVO(TElectronicArchiveDetail detail,
																		TElectronicArchiveTask task,
																		String materialType) {
		ArchiveDownloadFeedbackVO feedback = new ArchiveDownloadFeedbackVO();
		feedback.setEmpName(detail.getEmpName());
		feedback.setEmpIdcard(detail.getEmpIdcard());
		feedback.setDeptNo(detail.getDeptNo());
		feedback.setDeptName(detail.getDeptName());
		feedback.setMaterialType(materialType);
		feedback.setCustomerCode(detail.getCustomerCode());
		feedback.setCustomerName(detail.getCustomerName());
		feedback.setFileAddress(detail.getFileAddress());
		feedback.setDownloadStatus("处理中");
		feedback.setFailReason("");
		return feedback;
	}
	
	/**
	 * 更新反馈状态
	 * 
	 * @param feedbackList 反馈列表
	 * @param downloadInfo 下载信息
	 * @param status 状态
	 * @param failReason 失败原因
	 */
	private void updateFeedbackStatus(List<ArchiveDownloadFeedbackVO> feedbackList,
									  ArchiveDownloadInfo downloadInfo,
									  String status,
									  String failReason) {
		TElectronicArchiveDetail detail = downloadInfo.getDetail();
		Set<String> materialTypes = downloadInfo.getMaterialTypeAttaMap().keySet();
		
		for (ArchiveDownloadFeedbackVO feedback : feedbackList) {
			if (feedback.getEmpName().equals(detail.getEmpName()) &&
				feedback.getEmpIdcard().equals(detail.getEmpIdcard()) &&
				feedback.getDeptNo().equals(detail.getDeptNo()) &&
				materialTypes.contains(feedback.getMaterialType()) &&
					Common.isEmpty(feedback.getFailReason())) {
				feedback.setDownloadStatus(status);
				feedback.setFailReason(failReason);
			}
		}
	}

	public void initSearchVo(TElectronicArchiveDetailSearchVo searchVo) {
		YifuUser user = SecurityUtils.getUser();
		long roleId = 1569585030423158786L;
		boolean isSsc = this.haveRole(user, roleId);
		long seeRoleId = 2003347621962379266L;
		boolean isSeeSsc = this.haveRole(user, seeRoleId);
		if (isSeeSsc || isSsc || CommonConstants.ZERO_STRING.equals(user.getSystemFlag())) {
			searchVo.setAuthSql(null);
		}
	}

	public boolean haveRole(YifuUser user, long roleId) {
		List<Long> roleList = user.getClientRoleMap().get(ClientNameConstants.CLIENT_MVP);
		for (Long role : roleList) {
			if (role == roleId) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 根据查询条件导出电子档案
	 * <p>
	 * 功能说明：
	 * 1. 根据查询条件查询符合条件的电子档案明细列表
	 * 2. 导出所有查询结果的电子档案附件
	 * 3. 导出格式与批量下载功能保持一致
	 * 4. 一级文件夹：项目编码-姓名-身份证
	 * 5. 二级文件夹：资料类型，该文件夹下为具体附件
	 * 6. 压缩包命名规则为：电子档案条件导出_时间戳.zip
	 * 7. 只导出status为可用（1）的明细和可用（0）的附件
	 * </p>
	 *
	 * @param searchVo 查询条件
	 * @param response HTTP响应
	 * @author huych
	 * @date 2025-12-30
	 */
	@Override
	public void exportArchivesByCondition(TElectronicArchiveDetailSearchVo searchVo, HttpServletResponse response) {
		
		// 反馈列表
		List<ArchiveDownloadFeedbackVO> feedbackList = new ArrayList<>();
		
		// 用于存储需要导出的档案及其附件
		Map<String, ArchiveDownloadInfo> downloadMap = new LinkedHashMap<>();
		
		try {
			// 1. 根据查询条件查询电子档案明细列表
			initSearchVo(searchVo);
			
			// 查询符合条件的档案明细列表
			List<TElectronicArchiveDetail> detailList = baseMapper.getTElectronicArchiveDetailList(searchVo);
			
			// 2. 检查是否有查询结果
			if (CollUtil.isEmpty(detailList)) {
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().write("{\"code\":1,\"msg\":\"未找到符合条件的电子档案\"}");
				return;
			}
			
			log.info("根据查询条件找到{}条电子档案明细，开始准备导出", detailList.size());
			
			// 3. 遍历每个明细，准备下载信息
			for (TElectronicArchiveDetail detail : detailList) {
				try {
					prepareExportByDetail(detail, downloadMap, feedbackList);
				} catch (Exception e) {
					log.error("准备导出档案失败，明细ID：{}，姓名：{}，身份证：{}", 
							detail.getId(), detail.getEmpName(), detail.getEmpIdcard(), e);
					feedbackList.add(createExportFeedbackVO(detail, "系统异常：" + e.getMessage()));
				}
			}
			
			// 4. 检查是否有可导出的档案
			if (downloadMap.isEmpty()) {
				response.setContentType("application/json;charset=UTF-8");
				response.getWriter().write("{\"code\":1,\"msg\":\"没有可导出的档案附件\"}");
				return;
			}

			// 2. 校验文件总大小是否超过500MB
			long totalSize = calculateTotalFileSize(downloadMap);
			long maxSize = 500 * 1024 * 1024L; // 500MB
			if (totalSize > maxSize) {
				log.warn("批量下载文件总大小超过限制，总大小：{}MB，限制：500MB", totalSize / 1024 / 1024);
				response.setContentType("application/json;charset=UTF-8");
				response.setCharacterEncoding("UTF-8");
				String errorMsg = String.format("{\"code\":1,\"msg\":\"下载文件总大小超过限制，当前总大小：%.2fMB，最大允许：500MB\"}",
						totalSize / 1024.0 / 1024.0);
				response.getWriter().write(errorMsg);
				return;
			}
			
			// 5. 设置响应头
			String fileName = "电子档案条件导出_" + 
					LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".zip";
			response.setContentType("application/zip");
			response.setCharacterEncoding("UTF-8");
			response.setHeader(CommonConstants.CONTENT_DISPOSITION,
					"attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
			
			// 6. 创建ZIP输出流
			try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
				
				// 7. 下载并打包档案文件
				for (Map.Entry<String, ArchiveDownloadInfo> entry : downloadMap.entrySet()) {
					String folderName = entry.getKey();
					ArchiveDownloadInfo downloadInfo = entry.getValue();
					
					try {
						downloadArchiveFiles(zipOut, folderName, downloadInfo);
						
						// 更新反馈状态为成功
						updateExportFeedbackStatus(feedbackList, downloadInfo, "成功", "");
					} catch (Exception e) {
						log.error("导出档案文件失败，文件夹：{}", folderName, e);
						// 更新反馈状态为失败
						updateExportFeedbackStatus(feedbackList, downloadInfo, "失败", "导出文件失败：" + e.getMessage());
					}
				}
				
				// 8. 生成并添加反馈Excel表
				addFeedbackExcel(zipOut, feedbackList);
				
				zipOut.finish();
			}

		} catch (Exception e) {
			log.error("根据查询条件导出电子档案失败", e);
		}
	}
	
	/**
	 * 准备单个明细的导出信息
	 * 
	 * @param detail 档案明细
	 * @param downloadMap 下载信息Map
	 * @param feedbackList 反馈列表
	 */
	private void prepareExportByDetail(TElectronicArchiveDetail detail, 
									   Map<String, ArchiveDownloadInfo> downloadMap,
									   List<ArchiveDownloadFeedbackVO> feedbackList) {
		
		// 1. 查询该明细下所有可用附件
		List<TAttaInfo> allAttaList = attaInfoService.list(Wrappers.<TAttaInfo>lambdaQuery()
				.eq(TAttaInfo::getDomainId, detail.getId())
				.eq(TAttaInfo::getRelationType, ArchiveConstants.ATTA_RELATION_TYPE_PER)
				.eq(TAttaInfo::getStatus, ArchiveConstants.ATTA_STATUS_ENABLED));
		
		// 2. 如果没有附件，添加失败反馈
		if (CollUtil.isEmpty(allAttaList)) {
			feedbackList.add(createExportFeedbackVO(detail, "该人员在此项目下没有可用的附件"));
			return;
		}
		
		// 3. 构建文件夹名称：项目编码-姓名-身份证
		String folderName = detail.getDeptNo() + "-" + detail.getEmpName() + "-" + detail.getEmpIdcard();
		
		// 4. 按资料类型分组附件
		Map<String, List<TAttaInfo>> materialTypeAttaMap = new LinkedHashMap<>();
		for (TAttaInfo atta : allAttaList) {
			String materialType = extractMaterialTypeFromPath(atta.getAttaSrc());
			if (StrUtil.isBlank(materialType)) {
				materialType = "其他";
			}
			materialTypeAttaMap.computeIfAbsent(materialType, k -> new ArrayList<>()).add(atta);
		}
		
		// 5. 添加到下载Map
		ArchiveDownloadInfo downloadInfo = downloadMap.get(folderName);
		if (downloadInfo == null) {
			downloadInfo = new ArchiveDownloadInfo();
			downloadInfo.setDetail(detail);
			downloadInfo.setMaterialTypeAttaMap(materialTypeAttaMap);
			downloadMap.put(folderName, downloadInfo);
		} else {
			// 合并资料类型附件（理论上不会出现重复的folderName，但为了安全起见）
			downloadInfo.getMaterialTypeAttaMap().putAll(materialTypeAttaMap);
		}
		downloadInfo.setMaterialType(String.join(",", materialTypeAttaMap.keySet()));
		
		// 6. 添加成功的反馈（状态暂时为"处理中"，实际导出完成后更新）
		feedbackList.add(createExportSuccessFeedbackVO(detail, materialTypeAttaMap.keySet()));
	}
	
	/**
	 * 创建导出失败反馈VO
	 * 
	 * @param detail 档案明细
	 * @param failReason 失败原因
	 * @return 反馈VO
	 */
	private ArchiveDownloadFeedbackVO createExportFeedbackVO(TElectronicArchiveDetail detail, String failReason) {
		ArchiveDownloadFeedbackVO feedback = new ArchiveDownloadFeedbackVO();
		feedback.setEmpName(detail.getEmpName());
		feedback.setEmpIdcard(detail.getEmpIdcard());
		feedback.setDeptNo(detail.getDeptNo());
		feedback.setDeptName(detail.getDeptName());
		feedback.setMaterialType(detail.getMaterialType());
		feedback.setCustomerCode(detail.getCustomerCode());
		feedback.setCustomerName(detail.getCustomerName());
		feedback.setFileAddress(detail.getFileAddress());
		feedback.setDownloadStatus("失败");
		feedback.setFailReason(failReason);
		return feedback;
	}
	
	/**
	 * 创建导出成功反馈VO
	 * 
	 * @param detail 档案明细
	 * @param materialTypes 资料类型集合
	 * @return 反馈VO
	 */
	private ArchiveDownloadFeedbackVO createExportSuccessFeedbackVO(TElectronicArchiveDetail detail, 
																	 Set<String> materialTypes) {
		ArchiveDownloadFeedbackVO feedback = new ArchiveDownloadFeedbackVO();
		feedback.setEmpName(detail.getEmpName());
		feedback.setEmpIdcard(detail.getEmpIdcard());
		feedback.setDeptNo(detail.getDeptNo());
		feedback.setDeptName(detail.getDeptName());
		feedback.setMaterialType(String.join(",", materialTypes));
		feedback.setCustomerCode(detail.getCustomerCode());
		feedback.setCustomerName(detail.getCustomerName());
		feedback.setFileAddress(detail.getFileAddress());
		feedback.setDownloadStatus("处理中");
		feedback.setFailReason("");
		return feedback;
	}
	
	/**
	 * 更新导出反馈状态
	 * 
	 * @param feedbackList 反馈列表
	 * @param downloadInfo 下载信息
	 * @param status 状态
	 * @param failReason 失败原因
	 */
	private void updateExportFeedbackStatus(List<ArchiveDownloadFeedbackVO> feedbackList,
											ArchiveDownloadInfo downloadInfo,
											String status,
											String failReason) {
		TElectronicArchiveDetail detail = downloadInfo.getDetail();
		
		for (ArchiveDownloadFeedbackVO feedback : feedbackList) {
			if (feedback.getEmpName().equals(detail.getEmpName()) &&
				feedback.getEmpIdcard().equals(detail.getEmpIdcard()) &&
				feedback.getDeptNo().equals(detail.getDeptNo())) {
				feedback.setDownloadStatus(status);
				feedback.setFailReason(failReason);
			}
		}
	}

	/**
	 * 删除电子档案明细表
	 * <p>
	 * 功能说明：
	 * 1. 校验明细是否存在
	 * 2. 查询该明细下所有关联的附件
	 * 3. 将明细状态更新为不可用（逻辑删除）
	 * 4. 将所有附件删除
	 * 5. 同时删除OSS上的附件文件
	 * 6. 删除任务与档案的关联关系
	 * </p>
	 *
	 * @param id 明细ID
	 * @return 删除结果
	 * @author huych
	 * @date 2025-12-30
	 */
	@Override
	public R<String> deleteArchiveDetail(String id) {
		
		try {
			// 1. 校验明细是否存在
			TElectronicArchiveDetail detail = baseMapper.selectById(id);
			if (Common.isEmpty(detail)) {
				return R.failed("电子档案明细不存在");
			}

			List<TTaskArchiveRelation> relationList = relationService.list(Wrappers.<TTaskArchiveRelation>lambdaQuery()
					.eq(TTaskArchiveRelation::getArchiveDetailId, id));
			if (Common.isNotNull(relationList)) {
				for (TTaskArchiveRelation relation: relationList) {
					//查找是否存在任务
					TElectronicArchiveTask task = taskService.getById(relation.getTaskId());
					if (Common.isNotNull(task) && task.getStatus().equals(ArchiveConstants.TASK_STATUS_NOT_UPLOADED)) {
						return R.failed("该明细正在被待上传zip文件的任务使用，请先删除任务或者上传对应的zip文件，"
								+ "任务编码：" +task.getTaskCode());
					}
				}
			}

			// 2. 查询该明细下所有的附件（包括可用和不可用的）
			List<TAttaInfo> attachments = attaInfoService.list(Wrappers.<TAttaInfo>lambdaQuery()
					.eq(TAttaInfo::getDomainId, id)
					.eq(TAttaInfo::getRelationType, ArchiveConstants.ATTA_RELATION_TYPE_PER));
			
			// 3. 删除所有附件并删除OSS文件
			int attachmentSuccessCount = 0;
			int attachmentFailCount = 0;
			
			if (CollUtil.isNotEmpty(attachments)) {
				for (TAttaInfo atta : attachments) {
					try {
						boolean updated = attaInfoService.removeById(atta);
						if (updated) {
							// 删除OSS文件
							try {
								ossUtil.deleteObject("yf-hr-efile-source", atta.getAttaSrc());
								attachmentSuccessCount++;

							} catch (Exception e) {
								log.error("删除OSS文件失败，附件ID：{}，路径：{}", atta.getId(), atta.getAttaSrc(), e);
								// OSS删除失败不影响整体流程，已经逻辑删除了
								attachmentSuccessCount++;
							}
						} else {
							attachmentFailCount++;
							log.error("更新附件状态失败，附件ID：{}", atta.getId());
						}
					} catch (Exception e) {
						attachmentFailCount++;
						log.error("删除附件失败，附件ID：{}", atta.getId(), e);
					}
				}
			}
			
			// 5. 删除任务与档案的关联关系
			try {
				relationService.remove(Wrappers.<TTaskArchiveRelation>lambdaQuery()
						.eq(TTaskArchiveRelation::getArchiveDetailId, id));
			} catch (Exception e) {
				log.error("删除任务档案关联关系失败，明细ID：{}", id, e);
				// 关联关系删除失败不影响主流程
			}
			
			// 6. 逻辑删除明细（更新状态为不可用，deleteFlag设为1）
			detail.setStatus(ArchiveConstants.DETAIL_STATUS_DISABLED);
			detail.setDeleteFlag(CommonConstants.ONE_STRING);
			boolean detailUpdated = baseMapper.updateById(detail) > 0;
			
			if (!detailUpdated) {
				return R.failed("删除电子档案明细失败");
			}
			
			// 7. 构建返回消息
			String resultMsg = String.format("删除成功。明细：%s-%s-%s，附件删除成功：%d个",
					detail.getDeptNo(), detail.getEmpName(), detail.getEmpIdcard(), attachmentSuccessCount);
			
			if (attachmentFailCount > 0) {
				resultMsg += String.format("，附件删除失败：%d个", attachmentFailCount);
			}
			
			return R.ok(null, resultMsg);
			
		} catch (Exception e) {
			log.error("删除电子档案明细失败，明细ID：{}", id, e);
			return R.failed("删除失败：" + e.getMessage());
		}
	}
	
	/**
	 * 计算下载文件总大小
	 * 
	 * @param downloadMap 下载信息Map
	 * @return 文件总大小（字节）
	 */
	private long calculateTotalFileSize(Map<String, ArchiveDownloadInfo> downloadMap) {
		long totalSize = 0L;
		
		for (Map.Entry<String, ArchiveDownloadInfo> entry : downloadMap.entrySet()) {
			ArchiveDownloadInfo downloadInfo = entry.getValue();
			Map<String, List<TAttaInfo>> materialTypeAttaMap = downloadInfo.getMaterialTypeAttaMap();
			
			if (materialTypeAttaMap != null) {
				for (Map.Entry<String, List<TAttaInfo>> attaEntry : materialTypeAttaMap.entrySet()) {
					List<TAttaInfo> attaList = attaEntry.getValue();
					if (CollUtil.isNotEmpty(attaList)) {
						for (TAttaInfo atta : attaList) {
							if (atta.getAttaSize() != null) {
								totalSize += atta.getAttaSize();
							}
						}
					}
				}
			}
		}
		
		log.info("批量下载文件总大小：{}字节（{}MB）", totalSize, totalSize / 1024.0 / 1024.0);
		return totalSize;
	}
}
