package com.cmeeting.job;

import cn.chatbot.meeting.LLMConfig;
import cn.chatbot.meeting.LLMResult;
import cn.chatbot.meeting.MeetingProcess;
import cn.chatbot.openai.completion.chat.ChatCompletionRequest;
import cn.chatbot.openai.completion.chat.Message;
import cn.chatbot.openai.service.LLMService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cmeeting.constant.KnowledgePlatformRouteConstant;
import com.cmeeting.constant.MeetingState;
import com.cmeeting.dto.DocResultDto;
import com.cmeeting.dto.UserDTO;
import com.cmeeting.email.EmailSender;
import com.cmeeting.exception.RobotBaseException;
import com.cmeeting.log.service.ProcessLogService;
import com.cmeeting.mapper.primary.MeetingInfoMapper;
import com.cmeeting.mapper.primary.MeetingRecordTemplateMapper;
import com.cmeeting.mapper.primary.UserIdMapper;
import com.cmeeting.pojo.MeetType;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.service.MeetTypeService;
import com.cmeeting.service.MeetingRecordTemplateService;
import com.cmeeting.util.*;
import com.cmeeting.vo.EmailPush;
import com.deepoove.poi.XWPFTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdParticipantsGet200Response;
import com.tencentcloudapi.wemeet.service.meetings.model.V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Data
@NoArgsConstructor
@Slf4j
@Service
public class FileProcessTask {
    private List<String> recordFileIdList;
    private String meetingRecordId;
    private String meetingId;
    private String subMeetingId;
    private String savePath;
    private Map<String, Object> metadata;

    private int retryCount = 0;
    private static final int MAX_RETRY = 3;

    private String llmApiAddr;
    private String llmModel;
    private String llmToken;
    private Integer llmMaxTokens;
    private Boolean finalRetry; //表示是兜底重试机制

    private MeetingInfoMapper meetingInfoMapper;
    private MeetingRecordTemplateService meetingRecordTemplateService;
    private MeetTypeService meetTypeService;
    private UserIdMapper userIdMapper;
    private MinioUtils minioUtils;
    private RedisUtils redisUtils;
    private EmailSender emailSender;
    private MeetingRecordTemplateMapper meetingRecordTemplateMapper;
    private ProcessLogService processLogService;
    //获取模板授权的人员
    private List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers;
    //腾会id企微id对应关系
    private Map<String, String> tidWidRelations;
    private UserAdminConfig userAdminConfig;
    //获取到的token
    private String adminToken;
    private String applicationId;
    private String fileDownloadPath;
    private String permTenantId;
    // AES加密秘钥
    private String aesKey;


    // 实际处理逻辑
    public void process() {
        boolean isSuccess = false;
        String key = "meet_process-" + meetingRecordId;
        if (!redisUtils.setnx(key, 1, 240)) {
            log.warn("key already exists in redis!, key: {}", key);
            return;
        }
        log.info("线程开始------------>");
        long l = System.currentTimeMillis();
        Integer status = null;
        while (retryCount <= MAX_RETRY && !isSuccess) {
            try {
                //已保存的会议信息
                MeetingInfo meetingInfo = meetingInfoMapper.selectOne(new LambdaQueryWrapper<MeetingInfo>()
                        .eq(MeetingInfo::getMeetingRecordId, meetingRecordId));
                if (meetingInfo.getIsGenerated()) {
                    log.warn("Generating is down, meetingRecordId: {}", meetingRecordId);
                    return;
                }
                if (!meetingInfo.getEmailPushAccess()) {
                    log.warn("会议主持人没有推送邮件权限, userId: {}", meetingInfo.getHostUid());
                    return;
                }
                String meetingDate = meetingInfo.getStartTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);

                // 获取参会成员明细
                V1MeetingsMeetingIdParticipantsGet200Response participantsData = TencentMeetingApiUtil.ApiV1MeetingsMeetingIdParticipantsGetRequest(meetingId, subMeetingId);
                if (participantsData == null) {
                    throw new RobotBaseException("获取参会成员明细失败, meetingId: " + meetingId);
                }
                List<V1MeetingsMeetingIdParticipantsGet200ResponseParticipantsInner> participants = participantsData.getParticipants();
                String participantNames = participants.stream().map(item -> new String(Base64.getDecoder().decode(item.getUserName()))).distinct().collect(Collectors.joining("、"));
                meetingInfo.setParticipantUsers(participantNames);

                //每场会议可能会分段录制，查出每个文件的转录记录后拼接
                StringBuilder recordTextBuffer = new StringBuilder();
                for (String recordFileId : recordFileIdList) {
                    recordTextBuffer.append(TencentMeetingApiUtil.ApiV1AddressesRecordFileIdGetRequest(recordFileId));
                }
                if(StringUtils.isEmpty(recordTextBuffer.toString().replaceAll("\\n","").trim())){
                    log.info("获取的转录文本为空，跳过纪要生成，meetingRecordId:{}", meetingRecordId);
                    processLogService.log(meetingId,subMeetingId,meetingRecordId+"获取的转录文本为空，跳过纪要生成");
                    status = MeetingState.EMPTY.getCode();
                    throw new RuntimeException("获取的转录文本为空，跳过纪要生成");
                }
                // 1. 根据转录文件内容recordTextBuffer判断使用模板类型

                Boolean emailGenerateAccess = meetingInfo.getEmailGenerateAccess();
                if (emailGenerateAccess) {
                    List<Long> choiceTemplateType = choiceTemplateType(meetingInfo.getSubject());
                    log.info("choiceTemplateType -> meet: {}, type: {}", meetingInfo.getSubject(), choiceTemplateType);
                    // 2. 获取这个会议需要使用的一个模板
                    MeetingRecordTemplate template = meetingRecordTemplateService.getEnabledRecordTemplate(choiceTemplateType, userIdMapper.getWidByTid(meetingInfo.getHostUid()));
                    if (template == null) {
                        throw new RobotBaseException("未找到模板!");
                    }
                    log.info("使用模版: {}", template.getName());
                    List<EmailPush.Attachment> attachments = new ArrayList<>();
                    String hostUid = meetingInfo.getHostUid();
                    String toUserCode = tidWidRelations.get(meetingInfo.getHostUid());
                    if (!tidWidRelations.containsKey(hostUid)) {
                        log.info("用户{}暂未关联企微信息，无法生成纪要文件", hostUid);
                        processLogService.log(meetingId, subMeetingId, "用户" + hostUid + "暂未关联企微信息，无法生成纪要文件");
                        continue;
                    }
                    String processedResult = null;
                    //暂时让所有模板共用一个提示词，两个模板输出同样的结果
                        LLMResult llmResult = processWithClaude(recordTextBuffer.toString(), meetingDate, participantNames, template.getPrompt());
                        if (llmResult.success) {
                            processedResult = llmResult.respond;
                        } else if (llmResult.reason.equals("会议记录 过短，退出")) {
                            status = MeetingState.EMPTY.getCode();
                            throw new RuntimeException("会议记录 过短，退出");
                        }
                    log.info("meetingRecordId: {},结果长度:{}", meetingRecordId, processedResult.length());

                    String minutesPath = saveResult(processedResult, recordTextBuffer.toString(), meetingInfo, toUserCode, template);
                    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd");
                    try (InputStream is = new FileInputStream(minutesPath)) {
                        byte[] meetingMinutesBytes = IOUtils.toByteArray(is);
                        EmailPush.Attachment attachment = EmailPush.Attachment.builder().name(meetingInfo.getSubject() + "会议纪要_" + fmt.format(meetingInfo.getStartTime())).bytes(meetingMinutesBytes).build();
                        attachments.add(attachment);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    } finally {
                        FileUtil.del(minutesPath);
                    }
                    if (CollectionUtils.isEmpty(attachments)) {
                        log.info("meetingRecordId:{}用户{}暂无任何模板权限，纪要生成失败", meetingRecordId, hostUid);
                        isSuccess = false;
                        continue;
                    }

                    Resource resource = new ClassPathResource("static/Cmeeting会议助手使用手册.pdf");
                    byte[] meetingMinutesBytes = IOUtils.toByteArray(resource.getInputStream());
                    EmailPush.Attachment attachment = EmailPush.Attachment.builder().name("Cmeeting会议助手使用手册.pdf").bytes(meetingMinutesBytes).build();
                    attachments.add(attachment);

                    if (!tidWidRelations.containsKey(meetingInfo.getHostUid())) {
                        log.error("邮件推送重试失败: 主持人对应关系未配置。meetingRecordId {}", meetingRecordId);
                        processLogService.log(meetingId, subMeetingId, "邮件推送重试失败: 主持人对应关系未配置。meetingRecordId:"+meetingRecordId);
                        continue;
                    }

                    EmailPush emailPushBuilder = EmailPush.builder()
                            .toEmail(meetingInfo.getEmail())
                            .meetingId(meetingId)
                            .meetingRecordId(meetingRecordId)
                            .attachments(attachments)
                            .subject(meetingInfo.getSubject())
                            .meetingInstanceId(meetingInfo.getId())
                            .subMeetingId(meetingInfo.getSubMeetingId())
                            .toUserCode(toUserCode)
                            .toUser(meetingInfo.getHost())
                            .emailPushAccess(meetingInfo.getEmailPushAccess())
                            .build();
                    emailPush(emailPushBuilder);
                    isSuccess = true;
                }
            } catch (Exception e) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                e.printStackTrace(pw);
                processLogService.log(meetingId,subMeetingId,meetingRecordId + sw.toString());
                // 异常处理
                retryCount++;
                if (status != null && status == MeetingState.EMPTY.getCode()) {
                    meetingInfoMapper.update(null,
                            new LambdaUpdateWrapper<MeetingInfo>()
                                    .eq(MeetingInfo::getMeetingRecordId, meetingRecordId)
                                    .set(MeetingInfo::getStatus, status));
                    break;
                } else {
                    if (retryCount > MAX_RETRY) {
                        log.error("达到最大重试次数:meetingId {}", meetingId);
                        //如果是兜底重试，最终还是失败了，设置会议的重试状态为已重试
                        if (finalRetry) {
                            meetingInfoMapper.update(null,
                                    new LambdaUpdateWrapper<MeetingInfo>()
                                            .eq(MeetingInfo::getMeetingRecordId, meetingRecordId)
                                            .set(MeetingInfo::getStatus, status != null ? status : MeetingState.GENERATE_ERROR.getCode())
                                            .set(MeetingInfo::getGenerateRetry, Boolean.TRUE));
                        } else {
                            meetingInfoMapper.update(null,
                                    new LambdaUpdateWrapper<MeetingInfo>()
                                            .eq(MeetingInfo::getMeetingRecordId, meetingRecordId)
                                            .set(MeetingInfo::getStatus, status != null ? status : MeetingState.GENERATE_ERROR.getCode())
                            );
                        }
                    } else {
                        // 指数退避
                        try {
                            Thread.sleep((long) Math.pow(2, retryCount) * 1000);
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                            throw new RuntimeException("重试失败", ie);
                        }
                    }
                }
            }
        }
        redisUtils.del(key);
        log.info("线程结束, 耗时: {} ms", System.currentTimeMillis() - l);
    }

    /**
     * 提供会议转录文件和会议主题，判断会议类型
     *
     * @param subject 会议主题
     * @return
     */
    private List<Long> choiceTemplateType(String subject) {
        List<Long> list = new ArrayList<>();
        list.add(1L);
        List<MeetType> meetTypeList = meetTypeService.list(new LambdaQueryWrapper<MeetType>().select(MeetType::getId, MeetType::getName, MeetType::getRegex).ne(MeetType::getName, "重要"));
        if (CollUtil.isNotEmpty(meetTypeList)) {
            for (MeetType meetType : meetTypeList) {
                if (checkRegex(meetType.getRegex(), subject) || subject.contains(meetType.getName())) {
                    list.add(meetType.getId());
                }
            }
        }
        return list;
    }

    private boolean checkRegex(String regex, String subject) {
        if (StrUtil.isBlank(regex)) {
            return false;
        }
        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        return pattern.matcher(subject).find();
    }

    /**
     * 大模型生成纪要xml
     *
     * @param textContent      转录文件
     * @param meetingDate      会议日期
     * @param participantNames 参会人员
     * @param prompt           提示词
     * @return
     */
    private LLMResult processWithClaude(String textContent, String meetingDate,String participantNames, String prompt) {
//        //将文件传送给大模型处理
        LLMConfig baseLLM = new LLMConfig(llmModel,
                llmApiAddr,
                "Bearer " + llmToken,
                llmMaxTokens);
        LLMResult llmResult = MeetingProcess.processMeeting(prompt, textContent,meetingDate,participantNames, baseLLM, new ArrayList<>());
        return llmResult;
//        if (llmResult.success) {
//            return llmResult.respond;
//        }
//        throw new RuntimeException(llmResult.reason);
//        DebugOutputTool.println(llmResult.respond);
    }

    /**
     * 保存会议纪要相关的文件
     *
     * @param content               大模型返回的不规则xml
     * @param recordData            转录文本
     * @param meetingInfo           会议对象
     * @param toUserCode            人员工号
     * @param meetingRecordTemplate 模板信息
     */
    private String saveResult(String content, String recordData, MeetingInfo meetingInfo, String toUserCode, MeetingRecordTemplate meetingRecordTemplate) {
        String meetingName;
        String uuid = IdUtil.fastSimpleUUID();
        String today = DateUtil.today();

        // 生成的xml临时存储路径
        String recordXmlPath = String.format("%s/%s", today, meetingId + "-recordXmlPath-" + uuid + ".xml");
        // 腾讯会议转录文件存储路径
        String recordContentPath = String.format("%s/%s", today, meetingId + "-recordContent-" + uuid + ".txt");
        //填充后的会议纪要名称
        String meetingMinutesFileName;
        //填充后的会议纪要word文件临时路径
        String meetingMinutesPath;
        boolean success = false;
        try {
            String subject = meetingInfo.getSubject();

            String fileName = String.format(subject + "_转写原文_%s.txt", DateUtil.today());
            MultipartFile multipartFile = new CustomMultipartFile(
                    "file",            // 表单中的字段名
                    fileName,     // 原始文件名
                    "text/plain; charset=utf-8",      // MIME类型
                    recordData.getBytes(StandardCharsets.UTF_8)          // 字节内容
            );
            //将转录文件存到知识向量库
            Map<String, String> otherParams = new HashMap<>();
            otherParams.put("userId", toUserCode);
            otherParams.put("tenantId", permTenantId);
            otherParams.put("layout", "V1");
            String responseData = HttpClientKnowledgePlatformUtil.sendPostByFormDataFiles(userAdminConfig.getDocDomain() + KnowledgePlatformRouteConstant.DOC.SIMPLE_DOC_UPLOAD_URL, Arrays.asList(multipartFile), otherParams, null);
            if (StringUtils.isNotBlank(responseData)) {
                R result = JSON.parseObject(responseData, R.class);
                List<DocResultDto> docResultDtoList = JSON.parseObject(JSONObject.toJSONString(result.getData()), new TypeReference<List<DocResultDto>>() {
                });
                DocResultDto docResultDto = docResultDtoList.get(0);
//                String previewPath = docResultDto.getPreviewPath();
//                recordContentPath = previewPath.replaceAll(fileDownloadPath,"");
                meetingInfo.setTransDocId(docResultDto.getId());
            } else {
                processLogService.log(meetingId, subMeetingId, "填充会议纪要失败，上传转录文件到向量知识库失败");
//                throw new RuntimeException("填充会议纪要失败");
                meetingInfo.setTransDocId("");
            }

            // 将转录文件保存到MinIO
            String encryptedRecordData = AESUtils.encrypt(recordData, aesKey);
            minioUtils.upload(recordContentPath, encryptedRecordData.getBytes(StandardCharsets.UTF_8));

            //去除内容中除了xml内容以外其他的信息，格式化xml
            String xml = extractXmlFromMarkdown(content);
            String encryptedXml = AESUtils.encrypt(xml, aesKey);
            minioUtils.upload(recordXmlPath, encryptedXml.getBytes(StandardCharsets.UTF_8));

            //将xml格式的内容转换为map,用于填充模板
            Map<String, Object> dataModel = convertXmlToMap(xml);
            //判断会议名称关键词，如果是用户自己定义的主题，不做修改
//            1***预定的会议
//            2***的快速会议
//            3**的周期会议
//            4**预定的网络研讨会
            String[] keywords = {"预定的会议", "的快速会议", "的周期会议", "预定的网络研讨会"};
            boolean hostCustomSubject = Arrays.stream(keywords).noneMatch(item -> subject.contains(item));
            if (hostCustomSubject) {
                meetingName = subject;
                dataModel.put("meeting_name", subject);
            } else {
                meetingName = dataModel.get("meeting_name") != null ? String.valueOf(dataModel.get("meeting_name")) : subject;
                meetingInfo.setSubject(meetingName);
            }
            meetingMinutesFileName = meetingName + "_" + meetingRecordTemplate.getName();
            //追加参会人员信息
            Map<String, Object> participantsMap = new ConcurrentHashMap<>();
            DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            String meetingDate = meetingInfo.getStartTime().toLocalDate().format(df);
            participantsMap.put("meeting_date", meetingDate);
            participantsMap.put("meeting_location", "线上腾讯会议");
            participantsMap.put("meeting_participants", meetingInfo.getParticipantUsers());
            participantsMap.put("meeting_host",meetingInfo.getHost() == null ? "" : meetingInfo.getHost());
            dataModel.putAll(participantsMap);

            XWPFTemplate template;
            try (InputStream inputStream = minioUtils.getFile(meetingRecordTemplate.getTemplate())) {
                template = XWPFTemplate.compile(inputStream).render(dataModel);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            meetingMinutesPath = savePath + IdUtil.getSnowflake(1L, 1L).nextId() + ".docx";
            template.writeAndClose(new FileOutputStream(meetingMinutesPath));
            processLogService.log(meetingId, subMeetingId, "填充会议纪要成功");
            success = true;
        } catch (Exception e) {
            success = false;
            log.error("填充会议纪要失败: {}", e.getMessage(), e);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            processLogService.log(meetingId, subMeetingId, "填充会议纪要失败" + sw.toString());
            throw new RuntimeException("填充会议纪要失败");
        } finally {
            meetingInfoMapper.update(meetingInfo,
                    new LambdaUpdateWrapper<MeetingInfo>()
                            .eq(MeetingInfo::getMeetingRecordId, meetingInfo.getMeetingRecordId())
                            .set(MeetingInfo::getRecordContent,recordContentPath)
                            .set(MeetingInfo::getRecordXml,recordXmlPath)
                            .set(MeetingInfo::getParticipantUsers,meetingInfo.getParticipantUsers())
                            .set(MeetingInfo::getIsGenerated, success)
                            .set(MeetingInfo::getTemplateId, meetingRecordTemplate.getId())
                            .set(MeetingInfo::getTransDocId, meetingInfo.getTransDocId())
                            .set(MeetingInfo::getSubject, meetingInfo.getSubject())
                            .set(MeetingInfo::getStatus, success ? MeetingState.NOTE_GENERATED.getCode() : MeetingState.GENERATE_ERROR.getCode())
            );
        }
        meetingInfo.setRecordContent(recordContentPath);
        meetingInfo.setRecordXml(recordXmlPath);
        return meetingMinutesPath;
    }

    private void emailPush(EmailPush emailPushBuilder) {
        Boolean isPushed;
        log.info("开始邮件推送------");
        //邮件推送
        try {
            isPushed = emailSender.sendEmailWithAttachment(emailPushBuilder);
        } catch (Exception e) {
            log.error("邮件推送失败: {}", e.getMessage(), e);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            processLogService.log(meetingId, subMeetingId, "【邮件推送】：" + sw.toString());
            throw new RuntimeException(e);
        }
        if (isPushed)
            processLogService.log(meetingId, subMeetingId, "用户允许邮件推送，推送邮件至" + emailPushBuilder.getToEmail());

        meetingInfoMapper.update(null,
                new LambdaUpdateWrapper<MeetingInfo>()
                        .eq(MeetingInfo::getMeetingRecordId, meetingRecordId)
                        .set(MeetingInfo::getIsPushed, isPushed)
                        .set(MeetingInfo::getStatus, isPushed ? MeetingState.PUSH_SUCCESS.getCode() : MeetingState.PUSH_ERROR.getCode())
        );
    }

    private String call_llm(String apiAddr, String model, String token, List<Message> messages, int maxTokens) {
        LLMService service = new LLMService(token, apiAddr);
        StringBuilder stringBuilder = new StringBuilder();
        try {
            ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                    .model(model)
                    .messages(messages)
                    .stream(true)
                    .n(1)
                    .maxTokens(maxTokens)
                    .logitBias(new HashMap<>())
                    .build();

            service.streamChatCompletion(chatCompletionRequest).doOnError(Throwable::printStackTrace).blockingForEach(chunk -> {
                chunk.getChoices().stream().map(choice -> choice.getMessage().getContent())
                        .filter(Objects::nonNull).findFirst().ifPresent(o -> {
                            try {
                                stringBuilder.append(new String(o.getBytes(Charset.defaultCharset())));
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        });
            });
        } catch (Exception e) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            processLogService.log(meetingId, subMeetingId, "【大模型处理异常】:" + sw.toString());
            throw new RuntimeException(e);
        }
        service.shutdownExecutor();
        return stringBuilder.toString();
    }

    public static Map<String, Object> convertXmlToMap(String xml) throws Exception {

        XmlMapper xmlMapper = new XmlMapper();
        ObjectMapper objectMapper = new ObjectMapper();

        // 先将 XML 读取为 Map
        Map<?, ?> xmlMap = xmlMapper.readValue(xml, Map.class);

        // 转换为更标准的 Map<String, Object>
        Map<String, Object> map = objectMapper.convertValue(xmlMap, Map.class);
        //特殊处理map格式
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Map<String, Object> value = (Map<String, Object>) entry.getValue();
            //取出正确的value并设置
            String realValue = String.valueOf(value.get("")).replaceAll("^\\n+", "");
            //内容段首移除换行，段末追加换行(会议名称结尾不换行
            entry.setValue(realValue.endsWith("\n") || "meeting_name".equals(entry.getKey()) || "meeting_purpose".equals(entry.getKey()) ? realValue : realValue + "\n");
        }
        return map;
    }

    /**
     * markdown转xml
     *
     * @param markdown
     * @return
     */
    private String extractXmlFromMarkdown(String markdown) {
        StringBuffer sb;
        try {
            int start = markdown.indexOf("<");
            if (start == -1) {
                processLogService.log(meetingId, subMeetingId, "markdown转xml失败，未输出正确的xml格式，markdown内容：" + markdown);
            }
            int end = markdown.lastIndexOf(">") + 1;
            sb = new StringBuffer();
            sb.append("<root>");
            String xml = markdown.substring(start, end).trim().replaceAll("\n\n", "\n");
            sb.append(xml);
            sb.append("</root>");
        } catch (Exception e) {
            log.info("markdown转xml，markdown->{}", markdown);
            throw new RuntimeException(e.getMessage());
        }
        return sb.toString();
    }


    public FileProcessTask(List<String> recordFileIdList, String meetingRecordId, String meetingId, String subMeetingId, String savePath, Map<String, Object> metadata,
                           MeetingInfoMapper meetingInfoMapper, MinioUtils minioUtils, RedisUtils redisUtils, EmailSender emailSender,
                           MeetingRecordTemplateMapper meetingRecordTemplateMapper, MeetingRecordTemplateService meetingRecordTemplateService, MeetTypeService meetTypeService, UserIdMapper userIdMapper,
                           String llmApiAddr, String llmModel, String llmToken, Integer llmMaxTokens, Boolean finalRetry, ProcessLogService processLogService, List<UserDTO.TemplateAuthorizedUserDTO> authorizedUsers, Map<String, String> tidWidRelations,
                           UserAdminConfig userAdminConfig, String applicationId, String fileDownloadPath, String permTenantId,
                           String aesKey) {
        this.recordFileIdList = recordFileIdList;
        this.savePath = savePath;
        this.metadata = metadata;
        this.meetingRecordId = meetingRecordId;
        this.meetingId = meetingId;
        this.subMeetingId = subMeetingId;
        this.meetingInfoMapper = meetingInfoMapper;
        this.minioUtils = minioUtils;
        this.redisUtils = redisUtils;
        this.emailSender = emailSender;
        this.meetingRecordTemplateMapper = meetingRecordTemplateMapper;
        this.meetingRecordTemplateService = meetingRecordTemplateService;
        this.meetTypeService = meetTypeService;
        this.userIdMapper = userIdMapper;
        this.llmApiAddr = llmApiAddr;
        this.llmModel = llmModel;
        this.llmToken = llmToken;
        this.llmMaxTokens = llmMaxTokens;
        this.finalRetry = finalRetry;
        this.processLogService = processLogService;
        this.authorizedUsers = authorizedUsers;
        this.tidWidRelations = tidWidRelations;
        this.userAdminConfig = userAdminConfig;
        this.applicationId = applicationId;
        this.fileDownloadPath = fileDownloadPath;
        this.permTenantId = permTenantId;
        this.aesKey = aesKey;
    }

    public String getId() {
        return this.meetingId + (this.subMeetingId == null ? "" : this.subMeetingId);
    }
}