package com.cmeeting.service.impl;

import cn.chatbot.openai.completion.chat.ChatCompletionRequest;
import cn.chatbot.openai.completion.chat.ChatMessage;
import cn.chatbot.openai.completion.chat.ChatMessageRole;
import cn.chatbot.openai.completion.chat.Message;
import cn.chatbot.openai.service.LLMService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cmeeting.ad.entity.RobotSecurityUser;
import com.cmeeting.ad.util.SecurityUtil;
import com.cmeeting.constant.MeetingState;
import com.cmeeting.constant.RecordTemplateConstant;
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.SysUserSyncMapper;
import com.cmeeting.mapper.primary.UserIdMapper;
import com.cmeeting.pojo.MeetingInfo;
import com.cmeeting.pojo.MeetingRecordTemplate;
import com.cmeeting.pojo.SysUserSyncCategory;
import com.cmeeting.pojo.UserId;
import com.cmeeting.service.ISysUserSyncCategoryService;
import com.cmeeting.service.MeetingInfoService;
import com.cmeeting.service.SysUserSyncService;
import com.cmeeting.util.AESUtils;
import com.cmeeting.util.MinioUtils;
import com.cmeeting.util.RedisUtils;
import com.cmeeting.util.TencentMeetingApiUtil;
import com.cmeeting.util.page.PageUtil;
import com.cmeeting.vo.MeetingInfoVO;
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.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class MeetingInfoServiceImpl extends ServiceImpl<MeetingInfoMapper, MeetingInfo>  implements MeetingInfoService {

    @Resource
    private MeetingInfoMapper mapper;
    @Resource
    private MinioUtils minioUtils;
    @Value(value = "${llm.api-addr}")
    private String llmApiAddr;
    @Value(value = "${llm.api-model}")
    private String llmModel;
    @Value(value = "${llm.api-token}")
    private String llmToken;
    @Value(value = "${llm.api-max-tokens}")
    private Integer llmMaxTokens;
    @Value("${permission.tenantId}")
    public String permissionTenantId;
    @Resource
    private ProcessLogService processLogService;
    @Resource
    private MeetingRecordTemplateMapper meetingRecordTemplateMapper;
    @Resource
    private MeetingInfoMapper meetingInfoMapper;
    @Resource
    private UserIdMapper userIdMapper;
    @Autowired
    private SysUserSyncMapper sysUserSysMapper;

    @Value("${aec.key}")
    public String aesKey;

    @Resource
    private SysUserSyncService sysUserSyncService;
    @Resource
    private ISysUserSyncCategoryService iSysUserSyncCategoryService;

    @Resource(name = "regenerateProcessExecutor")
    private ThreadPoolTaskExecutor regenerateProcessExecutor;
    @Resource
    private RedisUtils redisUtils;

    @Override
    public IPage<MeetingInfo> getPage(MeetingInfoVO vo) {
        RobotSecurityUser user = SecurityUtil.getUser();
        String userId = user.getId();
        //根据员工号获取腾会uid
        String tid = userIdMapper.getTidByWid(userId);
        if(user.getRole().equals(RecordTemplateConstant.TEMPLATE_TYPE_CUSTOM) && StringUtils.isEmpty(tid)){
            log.error("获取历史会议列表失败：根据员工号"+userId+"获取腾会uid失败");
            return new Page<>(0,0);
        }
        LambdaQueryWrapper<MeetingInfo> queryWrapper = new LambdaQueryWrapper<MeetingInfo>()
                .eq(user.getRole().equals(RecordTemplateConstant.TEMPLATE_TYPE_CUSTOM), MeetingInfo::getHostUid, tid)
                .like(StringUtils.isNotEmpty(vo.getSubject()),MeetingInfo::getSubject,vo.getSubject())
                .between(vo.getStartTime() != null, MeetingInfo::getStartTime, vo.getStartTime(), vo.getEndTime())
                .orderByDesc(MeetingInfo::getStartTime)
                .select(MeetingInfo::getId, MeetingInfo::getMeetingId, MeetingInfo::getSubject, MeetingInfo::getHost, MeetingInfo::getHostUid,
                        MeetingInfo::getStartTime, MeetingInfo::getEndTime, MeetingInfo::getIsGenerated, MeetingInfo::getIsPushed, MeetingInfo::getReprocess, MeetingInfo::getClear, MeetingInfo::getStatus);
        Page<MeetingInfo> meetingInfoPage = mapper.selectPage(new Page<>(vo.getCurrent(), vo.getSize()), queryWrapper);
        if (CollUtil.isNotEmpty(meetingInfoPage.getRecords())) {
            List<MeetingInfo> records = meetingInfoPage.getRecords();
            List<String> uids = records.stream().map(MeetingInfo::getHostUid).collect(Collectors.toList());
            List<UserId> userIds = userIdMapper.selectList(new LambdaQueryWrapper<UserId>().in(UserId::getTid, uids).select(UserId::getTid, UserId::getWid));
            Map<String, String> collect = userIds.stream().collect(Collectors.toMap(UserId::getTid, UserId::getWid));
            for (MeetingInfo record : records) {
                record.setUserId(collect.get(record.getHostUid()));
            }
            meetingInfoPage.setRecords(records);
        }
        return meetingInfoPage;
    }

    @Override
    public boolean updateRecordXml(MeetingInfoVO vo) {
        //前端的表单json转xml，xml格式的纪要内容便于生成会议纪要文件
        String recordJson = vo.getRecordJson();
        String recordXml = convertJSONToXml(recordJson);
        String encrypt = AESUtils.encrypt(recordXml, aesKey);
        String key = String.format("%s/%s", DateUtil.today(), vo.getMeetingId() + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + ".xml");
        minioUtils.upload(key, encrypt.getBytes(StandardCharsets.UTF_8));
        vo.setRecordXml(key);

        LambdaUpdateWrapper<MeetingInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(MeetingInfo::getId,vo.getId());
        updateWrapper.set(MeetingInfo::getUpdateTime,LocalDateTime.now());
        updateWrapper.set(MeetingInfo::getRecordXml,key);
        return update(null,updateWrapper);
    }

    @Override
    public boolean regenerateXml(MeetingInfoVO vo) {
        MeetingRecordTemplate meetingRecordTemplate = meetingRecordTemplateMapper.selectById(vo.getTemplateId());
        if (meetingRecordTemplate == null) {
            throw new RobotBaseException("param error! template is not exit!!");
        }
        MeetingInfo meetingInfo = mapper.selectById(vo.getId());
        if (meetingInfo == null) {
            throw new RobotBaseException("meeting is not exit!!");
        }
        String key = "meet_process" + meetingInfo.getMeetingId() + "_" + (meetingInfo.getSubMeetingId() == null ? "" : meetingInfo.getSubMeetingId());
        if (!redisUtils.setnx(key, 1, 240)) {
            log.warn("key already exists in redis!, key: {}", key);
            return false;
        }
        meetingInfoMapper.update(null,
                new LambdaUpdateWrapper<MeetingInfo>()
                        .eq(MeetingInfo::getMeetingId, meetingInfo.getMeetingId())
                        .eq(meetingInfo.getSubMeetingId() != null, MeetingInfo::getSubMeetingId, meetingInfo.getSubMeetingId())
                        .set(MeetingInfo::getReprocess, true)
        );
        regenerateProcessExecutor.execute(()->{
            try{
                regenerateXml(meetingInfo, meetingRecordTemplate);
            } finally {
                meetingInfoMapper.update(null,
                        new LambdaUpdateWrapper<MeetingInfo>()
                                .eq(MeetingInfo::getMeetingId, meetingInfo.getMeetingId())
                                .eq(meetingInfo.getSubMeetingId() != null, MeetingInfo::getSubMeetingId, meetingInfo.getSubMeetingId())
                                .set(MeetingInfo::getReprocess, false)
                );
                redisUtils.del(key);
            }

        });
        return true;
    }

    public boolean regenerateXml(MeetingInfo meetingInfo, MeetingRecordTemplate meetingRecordTemplate) {
        String meetingId = meetingInfo.getMeetingId();
        String subMeetingId = meetingInfo.getSubMeetingId();
        String recordFileIdArray = meetingInfo.getRecordFileId();
        try{
            if(StringUtils.isEmpty(recordFileIdArray)){
                return false;
            }
            String meetingDate = meetingInfo.getStartTime().toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
            // 获取参会成员明细
            V1MeetingsMeetingIdParticipantsGet200Response participantsData = TencentMeetingApiUtil.ApiV1MeetingsMeetingIdParticipantsGetRequest(meetingId, subMeetingId);
            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);

            //每场会议可能会分段录制，查出每个文件的转录记录后拼接
            StringBuffer recordTextBuffer = new StringBuffer();
            List<String> recordFileIdList = Arrays.asList(meetingInfo.getRecordFileId().split(","));
            for (String recordFileId : recordFileIdList) {
                //查询录制转写详情
                recordTextBuffer.append(TencentMeetingApiUtil.ApiV1AddressesRecordFileIdGetRequest(recordFileId));
            }
            if(StringUtils.isEmpty(recordTextBuffer.toString().replaceAll("\\n","").trim())){
                log.info("获取的转录文本为空，跳过纪要生成，meetingId:{}，fileRecordId:{}",meetingId,recordFileIdList.toString());
                processLogService.log(meetingId,subMeetingId,"获取的转录文本为空，跳过纪要生成");
                throw new RuntimeException("获取的转录文本为空，跳过纪要生成");
            }
            //获取系统模板
            String processedResult = processWithClaude(recordTextBuffer.toString(),meetingDate,meetingRecordTemplate.getPrompt(),meetingId,subMeetingId);
            log.info("processedResult-> {}", processedResult);
            saveResult(processedResult, recordTextBuffer.toString().getBytes(StandardCharsets.UTF_8), meetingInfo,  meetingRecordTemplate);
            meetingInfoMapper.update(null,
                    new LambdaUpdateWrapper<MeetingInfo>()
                            .eq(MeetingInfo::getMeetingRecordId, meetingInfo.getMeetingRecordId())
                            .eq(subMeetingId != null,MeetingInfo::getSubMeetingId,subMeetingId)
                            .set(MeetingInfo::getRecordXml,meetingInfo.getRecordXml())
                            .set(MeetingInfo::getTemplateId, meetingRecordTemplate.getId())
            );
            return true;
        }catch (Exception e){
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            processLogService.log(meetingId,subMeetingId,sw.toString());
            return false;
        }
    }

    /**
     * 统计
     *
     * @param searchValue     查询值
     * @param createTimeStart 起始时间
     * @param createTimeEnd   截止时间
     * @param page            是否分页
     * @return
     */
    @Override
    public List<Map<String, String>> statistics(String searchValue, Date createTimeStart, Date createTimeEnd, Boolean page) {
        List<String> dateList = DateUtil.rangeToList(createTimeStart, createTimeEnd, DateField.DAY_OF_YEAR).stream()
                                                .map(date -> DateUtil.format(date, "yyyy-MM-dd"))
                                                .collect(Collectors.toList());

        // 获取统计信息
        String startTime = DateUtil.format(createTimeStart, "yyyy-MM-dd");
        String endTime = DateUtil.format(createTimeEnd, "yyyy-MM-dd");
        List<Map<String, String>> statisticsList = meetingInfoMapper.statistics(startTime, endTime, dateList);

        if (page) {
            PageUtil.startPage();
        }
        // 获取用户信息
        List<String> userIdList = statisticsList.stream().map(map -> map.get("userId")).collect(Collectors.toList());
        List<Map<String, String>> userInfoList = sysUserSysMapper.selectParamByUserIdList(searchValue, userIdList);

        if (CollUtil.isNotEmpty(userInfoList)) {
            List<SysUserSyncCategory> categoryList = iSysUserSyncCategoryService.list(new LambdaQueryWrapper<SysUserSyncCategory>().select(SysUserSyncCategory::getDeptId, SysUserSyncCategory::getParentId, SysUserSyncCategory::getName));
            Map<String, String> parentIdMap = categoryList.stream().collect(Collectors.toMap(SysUserSyncCategory::getDeptId, SysUserSyncCategory::getParentId));
            Map<String, String> nameMap = categoryList.stream().collect(Collectors.toMap(SysUserSyncCategory::getDeptId, SysUserSyncCategory::getName));
            for (Map<String, String> map : userInfoList) {
                String deptId = map.get("deptId");
                String pathName = iSysUserSyncCategoryService.getPathName(deptId, parentIdMap, nameMap);
                map.put("deptName", pathName);
            }
        }

        for (Map<String, String> userInfo : userInfoList) {
            String userId = userInfo.get("userId");
            statisticsList.stream().filter(statistics -> statistics.get("userId").equals(userId)).findFirst().ifPresent(userInfo::putAll);
            // 横向加法
            Integer sum = userInfo.values().stream()
                            // 小于4是为了和用户编号区分开
                            .filter(value -> NumberUtil.isNumber(value) && value.length() < 4)
                            .map(value -> Integer.parseInt(value))
                            .reduce(Integer::sum)
                            .orElse(0);
            userInfo.put("sum", sum.toString());
        }
        userInfoList.removeIf(map -> !map.containsKey("host_uid"));
        if (userInfoList.isEmpty()) {
            return new ArrayList<>();
        }

        // 初始化一行用来纵向加和
        Map<String, String> lastRowMap = new LinkedHashMap<>();
        for (String key : userInfoList.get(0).keySet()) {
            lastRowMap.put(key, "");
        }
        for (Map<String, String> stringMap : userInfoList) {
            for (Map.Entry<String, String> entry : stringMap.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (NumberUtil.isNumber(value) && value.length() < 4) {
                    lastRowMap.put(key, String.valueOf(NumberUtil.add(lastRowMap.get(key), value)));
                }
            }
        }
        lastRowMap.put("userId", "总计");
        userInfoList.add(lastRowMap);

        userInfoList.forEach(map -> map.remove("host_uid"));

        return userInfoList;
    }

    /**
     * 导出
     *
     * @param searchValue     搜索值
     * @param createTimeStart 起始时间
     * @param createTimeEnd   截止时间
     * @param response
     */
    @Override
    public void exportRecordTemplateUsingInfo(String searchValue, Date createTimeStart, Date createTimeEnd, HttpServletResponse response) {
        List<Map<String, String>> statistics = statistics(searchValue, createTimeStart, createTimeEnd, false);
        List<Map<String, String>> list = new ArrayList<>(statistics.size());
        for (Map<String, String> map : statistics) {
            Map<String, String> temp = new LinkedHashMap<>();

            String host = map.get("deptName");
            host = host.replace("/中集集团/", "");
            String[] split = host.split("/");
            temp.put("level1", split[0]);
            temp.put("level2", split.length>=2?split[1]:"");
            temp.put("level3", split.length>=3?split[2]:"");
            temp.put("level4", split.length>=4?split[3]:"");
            temp.put("level5", split.length>=5?split[4]:"");
            map.remove("deptId");
            map.remove("deptName");
            temp.putAll(map);
            list.add(temp);
        }

        if (list.isEmpty()) {
            List<String> dateList = DateUtil.rangeToList(createTimeStart, createTimeEnd, DateField.DAY_OF_YEAR).stream()
                    .map(date -> DateUtil.format(date, "yyyy-MM-dd"))
                    .collect(Collectors.toList());
            Map<String, String> map = new LinkedHashMap<>();
            map.put("部门", "");
            map.put("姓名", "");
            map.put("工号", "");
            dateList.forEach(date -> map.put(date, ""));
            map.put("总计", "");
            list.add(map);
        }
        int column = list.get(0).keySet().size();
        List<Map<String, String>> finalStatistics = new ArrayList<>();
        for (Map<String, String> statistic : list) {
            Map<String, String> newStatistic = new LinkedHashMap<>();
            Set<Map.Entry<String, String>> entriedSet = statistic.entrySet();
            for (Map.Entry<String, String> entry : entriedSet) {
                String key = entry.getKey();
                String value = entry.getValue();
                if ("level1".equals(key)) {
                    newStatistic.put("板块", value);
                } else if ("level2".equals(key)) {
                    newStatistic.put("二级", value);
                } else if ("level3".equals(key)) {
                    newStatistic.put("三级", value);
                } else if ("level4".equals(key)) {
                    newStatistic.put("四级", value);
                } else if ("level5".equals(key)) {
                    newStatistic.put("五级", value);
                } else if ("name".equals(key)) {
                    newStatistic.put("姓名", value);
                } else if ("userId".equals(key)) {
                    newStatistic.put("工号", value);
                } else if ("sum".equals(key)) {
                    newStatistic.put("总计", value);
                } else {
                    newStatistic.put(key, value);
                }
            }
            finalStatistics.add(newStatistic);
        }

        // 导出
        OutputStream outputStream = null;
        ExcelWriter writer = ExcelUtil.getWriter(true);
        try {
            String fileName = String.format("会议使用次数.xlsx");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf8"));

            outputStream = response.getOutputStream();
//            writer.addHeaderAlias("deptName", "部门");
//            writer.addHeaderAlias("name", "姓名");
//            writer.addHeaderAlias("userId", "工号");
//            writer.addHeaderAlias("sum", "总计");
            // 合并单元格后的标题行，使用默认标题样式
            writer.merge(column - 1, "会议纪要使用次数");
            // 一次性写出内容，使用默认样式，强制输出标题
            writer.write(finalStatistics, true);
            writer.flush(outputStream);
            // 关闭writer，释放内存
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            writer.close();
            IoUtil.close(outputStream);
        }


    }

    @Override
    public int deleteDataAfterThan2Days(String date) {
        return baseMapper.deleteDataAfterThan2Days(date);
    }

    /**
     * 保存会议纪要相关的文件
     * @param content 大模型返回的不规则xml
     * @param recordData 转录文本
     * @param meetingInfo 会议对象
     * @param meetingRecordTemplate 模板信息
     */
    private void saveResult(String content, byte[] recordData, MeetingInfo meetingInfo,MeetingRecordTemplate meetingRecordTemplate) {
        String meetingId = meetingInfo.getMeetingId();
        String subMeetingId = meetingInfo.getSubMeetingId();
        //转录文件临时存储路径
        String recordContentPath = meetingId + "-recordContent-" + IdUtil.fastSimpleUUID() + ".txt";
        //生成的xml临时存储路径
        String recordXmlPath = meetingId + "-recordXmlPath-" + IdUtil.fastSimpleUUID() + ".xml";
        try {
            //去除内容中除了xml内容以外其他的信息，格式化xml
            String xml = extractXmlFromMarkdown(content, meetingId, subMeetingId);
            String encrypt = AESUtils.encrypt(xml, aesKey);
            minioUtils.upload(recordXmlPath, encrypt.getBytes(StandardCharsets.UTF_8));
            meetingInfo.setRecordXml(recordXmlPath);
        } 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("填充会议纪要失败");
        }
    }

    /**
     * 大模型生成纪要xml
     * @param textContent 转录文件
     * @param prompt 提示词
     * @return
     */
    private String processWithClaude(String textContent, String meetingDate, String prompt,String meetingId, String subMeetingId) {
        //将文件传送给大模型处理
        String token = "Bearer " + llmToken;
        String apiAddr = llmApiAddr;
        String model = llmModel;
        int maxTokens = llmMaxTokens;
        List<Message> messages = new ArrayList<>();

        ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), prompt.replaceAll("\\{transcript\\}",textContent).replaceAll("\\{meetingDate\\}",meetingDate));
        messages.add(chatMessage);

        messages.add(chatMessage);

        // 调用Claude API处理文件
        String ret = call_llm(apiAddr, model, token, messages, maxTokens,meetingId,subMeetingId);
        return ret;
    }

    private String call_llm(String apiAddr, String model, String token, List<Message> messages, int maxTokens,String meetingId, String subMeetingId) {
        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();
    }

    private String convertJSONToXml(String json) {
        JSONArray jsonArray = JSON.parseArray(json);;
        StringBuilder xmlBuilder = new StringBuilder();

        xmlBuilder.append("<root>");
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObj = jsonArray.getJSONObject(i);

            String key = jsonObj.getString("key");
            String label = jsonObj.getString("keyName");
            String value = jsonObj.getString("value");

            xmlBuilder.append(String.format("<%s label=\"%s\">%s</%s>",
                    key, label, value, key));
        }
        xmlBuilder.append("</root>");

        return xmlBuilder.toString();
    }

    private 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,String meetingId, String subMeetingId) {
        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();
    }

    private byte[] downloadFile(String url) {
        // 实现文件下载逻辑
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(url).build();
        try {
            Response response = client.newCall(request).execute();
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
            return response.body().bytes();
        }catch (Exception e){
            throw new RuntimeException("下载文件失败", e);
        }
    }


    /**
     * 统计邮件推送情况
     */
    @Override
    public void statisticsEmail(Integer type, Date startTime, Date endTime, HttpServletResponse response, Map<String, String> deptMap) {
        DateTime now = DateUtil.date();
        if (ObjectUtils.isEmpty(startTime) || ObjectUtils.isEmpty(endTime)) {
            if (1== type) {
                startTime = DateUtil.beginOfDay(DateUtil.offset(now, DateField.DAY_OF_MONTH, -2));
                endTime = DateUtil.offset(now, DateField.HOUR_OF_DAY, -2);
            } else {
                startTime = DateUtil.beginOfDay(now);
                endTime = now;
            }
        }

        List<MeetingInfo> meetingInfoList = meetingInfoMapper.selectList(new LambdaQueryWrapper<MeetingInfo>()
//                                                .eq(MeetingInfo::getEmailPushAccess, true)
                .between(MeetingInfo::getStartTime, startTime, endTime));
        Map<String, List<MeetingInfo>> hostUidMeetingInfoMap = meetingInfoList.stream().collect(Collectors.groupingBy(MeetingInfo::getHostUid));
        Map<String, String> hostMap = new HashMap<>();
        meetingInfoList.forEach(meetingInfo -> {
            if (!hostMap.containsKey(meetingInfo.getHostUid())) {
                hostMap.put(meetingInfo.getHostUid(), meetingInfo.getHost());
            }
        });

        // 总计
        Map<String, Object> totalMap = new LinkedHashMap<>();
        totalMap.put("host", "总计");

        List<Map<String, Object>> mapList = new ArrayList<>();
        for (Map.Entry<String, List<MeetingInfo>> entry : hostUidMeetingInfoMap.entrySet()) {
            // 使用linkedHashMap，保证输出到excel中的顺序
            Map<String, Object> map = new LinkedHashMap<>();

            String host = hostMap.get(entry.getKey());
            List<MeetingInfo> meetingList = entry.getValue();
            Integer totalNum = meetingList.size();
            //    没有转录文件会议，需要生成会议纪要的会议，   待处理，    处理中，             纪要生成成功，         推送成功，         推送失败
            Integer  emptyNum=0, emailPushAccessNum=0, newNum=0,  generatErrorNum=0, noteGeneratedNum=0, pushSuccessNum=0, pushErrorNum=0;

            for (MeetingInfo meetingInfo : meetingList) {
                if (meetingInfo.getStatus() == MeetingState.NEW.getCode()) {
                    newNum++;
                } else if (meetingInfo.getStatus() == MeetingState.GENERATE_ERROR.getCode()) {
                    generatErrorNum++;
                } else if (meetingInfo.getStatus() == MeetingState.PUSH_SUCCESS.getCode()) {
                    pushSuccessNum++;
                } else if (meetingInfo.getStatus() == MeetingState.PUSH_ERROR.getCode()) {
                    pushErrorNum++;
                } else if (meetingInfo.getStatus() == MeetingState.EMPTY.getCode()) {
                    emptyNum++;
                }
                if (meetingInfo.getIsGenerated() || meetingInfo.getStatus() == MeetingState.NOTE_GENERATED.getCode()) {
                    noteGeneratedNum++;
                }
                // 有转录文件 且 email_push_access为true，
                if (meetingInfo.getStatus() != MeetingState.EMPTY.getCode() && meetingInfo.getEmailPushAccess()) {
                    emailPushAccessNum++;
                }
            }
            Integer avaliableNum = totalNum - emptyNum;
            map.put("host", host);
            map.put("totalNum", totalNum);
            map.put("avaliableNum", avaliableNum);
            map.put("emptyNum", emptyNum);
            map.put("emailPushAccessNum", emailPushAccessNum);
            map.put("newNum", newNum);
            map.put("generatErrorNum", generatErrorNum);
            map.put("noteGeneratedNum", noteGeneratedNum);
            map.put("pushErrorNum", pushErrorNum);
            map.put("pushSuccessNum", pushSuccessNum);
            mapList.add(map);

            // 计算总计
            totalMap.put("totalNum", Integer.valueOf(totalMap.getOrDefault("totalNum", 0).toString()) + totalNum);
            totalMap.put("avaliableNum", Integer.valueOf(totalMap.getOrDefault("avaliableNum", 0).toString()) + avaliableNum);
            totalMap.put("emptyNum", Integer.valueOf(totalMap.getOrDefault("emptyNum", 0).toString()) + emptyNum);
            totalMap.put("emailPushAccessNum", Integer.valueOf(totalMap.getOrDefault("emailPushAccessNum", 0).toString()) + emailPushAccessNum);
            totalMap.put("newNum", Integer.valueOf(totalMap.getOrDefault("newNum", 0).toString()) + newNum);
            totalMap.put("generatErrorNum", Integer.valueOf(totalMap.getOrDefault("generatErrorNum", 0).toString()) + generatErrorNum);
            totalMap.put("noteGeneratedNum", Integer.valueOf(totalMap.getOrDefault("noteGeneratedNum", 0).toString()) + noteGeneratedNum);
            totalMap.put("pushErrorNum", Integer.valueOf(totalMap.getOrDefault("pushErrorNum", 0).toString()) + pushErrorNum);
            totalMap.put("pushSuccessNum", Integer.valueOf(totalMap.getOrDefault("pushSuccessNum", 0).toString()) + pushSuccessNum);
        }
        mapList.add(totalMap);

        // 导出
        OutputStream outputStream = null;
        ExcelWriter writer = ExcelUtil.getWriter(true);
        try {
            String title = String.format("会议纪要推送统计表_%s-%s", DateUtil.format(startTime, "yyyyMMdd"), DateUtil.format(endTime, "yyyyMMdd"));

            outputStream = response.getOutputStream();
            String fileName = String.format("%s.xlsx", title);
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "utf8"));


            writer.addHeaderAlias("host", "主持人");
            writer.addHeaderAlias("totalNum", "总会议");
            writer.addHeaderAlias("avaliableNum", "有效会议");
            writer.addHeaderAlias("emptyNum", "没有转录文件");
            writer.addHeaderAlias("emailPushAccessNum", "需生成纪要的会议总数");
            writer.addHeaderAlias("newNum", "待处理");
            writer.addHeaderAlias("generatErrorNum", "处理中");
            writer.addHeaderAlias("noteGeneratedNum", "纪要生成成功");
            writer.addHeaderAlias("pushSuccessNum", "纪要推送成功");
            writer.addHeaderAlias("pushErrorNum", "纪要推送失败");
            // 合并单元格后的标题行，使用默认标题样式
            writer.merge(writer.getHeaderAlias().size() - 1, title);
            // 一次性写出内容，使用默认样式，强制输出标题
            writer.write(mapList, true);
            writer.flush(outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            writer.close();
            IoUtil.close(outputStream);
        }
    }




    /**
     * 统计邮件推送情况
     */
    @Override
    public boolean statisticsEmail(String title, Date startTime, Date endTime, OutputStream outputStream, Map<String, String> DEPT_MAP) {
        List<Map<String, Object>> mapList = statisticsEmail(startTime, endTime, DEPT_MAP);
        List<Map<String, Object>> list = new ArrayList<>();
        for (Map<String, Object> map : mapList) {
            Map<String, Object> temp = new LinkedHashMap<>();
            String host = map.get("host").toString();
            host = host.replace("/中集集团/", "");
            String[] split = host.split("/");
            temp.put("level1", split[0]);
            temp.put("level2", split.length>=2?split[1]:"");
            temp.put("level3", split.length>=3?split[2]:"");
            map.remove("host");
            temp.putAll(map);
            list.add(temp);
        }
        // 导出
        ExcelWriter writer = ExcelUtil.getWriter(true);
        try {
            writer.addHeaderAlias("level1", "模块");
            writer.addHeaderAlias("level2", "二级");
            writer.addHeaderAlias("level3", "三级");
            writer.addHeaderAlias("totalNum", "总会议");
            writer.addHeaderAlias("avaliableNum", "有效会议");
            writer.addHeaderAlias("emptyNum", "没有转录文件");
            writer.addHeaderAlias("emailPushAccessNum", "需生成纪要的会议总数");
            writer.addHeaderAlias("newNum", "待处理");
            writer.addHeaderAlias("generatErrorNum", "纪要生成失败");
            writer.addHeaderAlias("noteGeneratedNum", "纪要生成成功");
            writer.addHeaderAlias("pushSuccessNum", "纪要推送成功");
            writer.addHeaderAlias("pushErrorNum", "纪要推送失败");
            // 合并单元格后的标题行，使用默认标题样式
            writer.merge(writer.getHeaderAlias().size() - 1, title);
            // 一次性写出内容，使用默认样式，强制输出标题
            writer.write(list, true);
            writer.flush(outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            writer.close();
            IoUtil.close(outputStream);
        }
        return true;
    }


    /**
     * 统计邮件推送情况
     */
    @Override
    public List<Map<String, Object>> statisticsEmail(Date startTime, Date endTime, Map<String, String> DEPT_MAP) {
        List<MeetingInfo> meetingInfoList = meetingInfoMapper.selectList(new LambdaQueryWrapper<MeetingInfo>().eq(MeetingInfo::getIsPushed, true)
                .between(MeetingInfo::getStartTime, startTime, endTime));
        log.info("会议总量: {}", meetingInfoList.size());
        if (meetingInfoList.size() == 0) {
            log.info("时间段内没有会议");
            return new ArrayList<>();
        }

        // 查询工号以及部门
        List<String> uids = meetingInfoList.stream().map(MeetingInfo::getHostUid).distinct().collect(Collectors.toList());
        List<UserId> userIds = userIdMapper.selectList(new LambdaQueryWrapper<UserId>().in(UserId::getTid, uids));

        Map<String, String> idMap = userIds.stream().collect(Collectors.toMap(UserId::getTid, UserId::getWid));
        Map<String, List<String>> deptMap = new HashMap<>();
        for (UserId userId : userIds) {
            List<String> deptPathByUserIdAndInSet = sysUserSyncService.getDeptPathByUserIdAndInSet(userId.getWid());
            deptMap.put(userId.getWid(), deptPathByUserIdAndInSet);
        }

        Map<String, List<MeetingInfo>> hostUidMeetingInfoMap = new HashMap<>();
        for (MeetingInfo meetingInfo : meetingInfoList) {
            for (String path : deptMap.get(idMap.get(meetingInfo.getHostUid()))) {
                for (String deptId : DEPT_MAP.keySet()) {
                    if (path.contains(deptId)) {
                        List<MeetingInfo> meetingInfos = hostUidMeetingInfoMap.get(DEPT_MAP.get(deptId));
                        if (CollUtil.isEmpty(meetingInfos)) {
                            meetingInfos = new ArrayList<>();
                        }
                        meetingInfos.add(meetingInfo);
                        hostUidMeetingInfoMap.put(DEPT_MAP.get(deptId), meetingInfos);
                        break;
                    }
                }
            }
        }

        Map<String, String> hostMap = new HashMap<>();
        meetingInfoList.forEach(meetingInfo -> {
            if (!hostMap.containsKey(meetingInfo.getHostUid())) {
                hostMap.put(meetingInfo.getHostUid(), meetingInfo.getHost());
            }
        });

        // 总计
        Map<String, Object> totalMap = new LinkedHashMap<>();
        totalMap.put("host", "总计");

        List<Map<String, Object>> mapList = new ArrayList<>();

        for (Map.Entry<String, List<MeetingInfo>> entry : hostUidMeetingInfoMap.entrySet()) {
            // 使用linkedHashMap，保证输出到excel中的顺序
            Map<String, Object> map = new LinkedHashMap<>();

            List<MeetingInfo> meetingList = entry.getValue();
            Integer totalNum = meetingList.size();
            //    没有转录文件会议，需要生成会议纪要的会议，   待处理，    处理中，             纪要生成成功，         推送成功，         推送失败
            Integer emptyNum = 0, emailPushAccessNum = 0, newNum = 0, generatErrorNum = 0, noteGeneratedNum = 0, pushSuccessNum = 0, pushErrorNum = 0;

            for (MeetingInfo meetingInfo : meetingList) {
                if (meetingInfo.getStatus() == MeetingState.GENERATE_ERROR.getCode()) {
                    generatErrorNum++;
                } else if (meetingInfo.getStatus() == MeetingState.PUSH_SUCCESS.getCode()) {
                    pushSuccessNum++;
                } else if (meetingInfo.getStatus() == MeetingState.PUSH_ERROR.getCode()) {
                    pushErrorNum++;
                } else if (meetingInfo.getStatus() == MeetingState.EMPTY.getCode()) {
                    emptyNum++;
                } else if (meetingInfo.getStatus() == MeetingState.NEW.getCode() && meetingInfo.getEmailPushAccess()) {
                    newNum++;
                }
                if (meetingInfo.getIsGenerated() || meetingInfo.getStatus() == MeetingState.NOTE_GENERATED.getCode()) {
                    noteGeneratedNum++;
                }
                // 有转录文件 且 email_push_access为true，
                if (meetingInfo.getStatus() != MeetingState.EMPTY.getCode() && meetingInfo.getEmailPushAccess()) {
                    emailPushAccessNum++;
                }
            }
            Integer avaliableNum = totalNum - emptyNum;
            map.put("host", entry.getKey());
            map.put("totalNum", totalNum);
            map.put("avaliableNum", avaliableNum);
            map.put("emptyNum", emptyNum);
            map.put("emailPushAccessNum", emailPushAccessNum);
            map.put("newNum", newNum);
            map.put("generatErrorNum", generatErrorNum);
            map.put("noteGeneratedNum", noteGeneratedNum);
            map.put("pushErrorNum", pushErrorNum);
            map.put("pushSuccessNum", pushSuccessNum);
            mapList.add(map);

            // 计算总计
            totalMap.put("totalNum", Integer.valueOf(totalMap.getOrDefault("totalNum", 0).toString()) + totalNum);
            totalMap.put("avaliableNum", Integer.valueOf(totalMap.getOrDefault("avaliableNum", 0).toString()) + avaliableNum);
            totalMap.put("emptyNum", Integer.valueOf(totalMap.getOrDefault("emptyNum", 0).toString()) + emptyNum);
            totalMap.put("emailPushAccessNum", Integer.valueOf(totalMap.getOrDefault("emailPushAccessNum", 0).toString()) + emailPushAccessNum);
            totalMap.put("newNum", Integer.valueOf(totalMap.getOrDefault("newNum", 0).toString()) + newNum);
            totalMap.put("generatErrorNum", Integer.valueOf(totalMap.getOrDefault("generatErrorNum", 0).toString()) + generatErrorNum);
            totalMap.put("noteGeneratedNum", Integer.valueOf(totalMap.getOrDefault("noteGeneratedNum", 0).toString()) + noteGeneratedNum);
            totalMap.put("pushErrorNum", Integer.valueOf(totalMap.getOrDefault("pushErrorNum", 0).toString()) + pushErrorNum);
            totalMap.put("pushSuccessNum", Integer.valueOf(totalMap.getOrDefault("pushSuccessNum", 0).toString()) + pushSuccessNum);
        }
        mapList.add(totalMap);

        return mapList;
    }

}
