package com.fs.app.taskService.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.fs.app.taskService.SopLogsTaskService;
import com.fs.common.utils.BatchUtils;
import com.fs.common.utils.StringUtils;
import com.fs.company.domain.CompanyUser;
import com.fs.company.service.ICompanyUserService;
import com.fs.course.config.CourseConfig;
import com.fs.course.domain.*;
import com.fs.course.mapper.*;
import com.fs.qw.domain.QwExternalContact;
import com.fs.qw.domain.QwUser;
import com.fs.qw.mapper.QwExternalContactMapper;
import com.fs.qw.mapper.QwUserMapper;
import com.fs.qw.service.impl.QwExternalContactServiceImpl;
import com.fs.qw.vo.QwSopCourseFinishTempSetting;
import com.fs.qw.vo.QwSopRuleTimeVO;
import com.fs.qw.vo.QwSopTempSetting;
import com.fs.sop.domain.*;
import com.fs.sop.mapper.*;
import com.fs.sop.service.IQwSopLogsService;
import com.fs.sop.service.IQwSopTempContentService;
import com.fs.sop.service.IQwSopTempRulesService;
import com.fs.sop.service.IQwSopTempVoiceService;
import com.fs.sop.vo.QwCreateLinkByAppVO;
import com.fs.sop.vo.SopUserLogsVo;
import com.fs.system.service.ISysConfigService;
import com.fs.voice.utils.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import static com.fs.course.utils.LinkUtil.generateRandomStringWithLock;

@Service
@Slf4j
public class SopLogsTaskServiceImpl implements SopLogsTaskService {


    private static final String REAL_LINK_PREFIX = "/courseH5/pages/course/learning?course=";
    private static final String SHORT_LINK_PREFIX = "/courseH5/pages/course/learning?s=";
    private static final String miniappRealLink = "/pages_course/video.html?course=";
    private static final String appRealLink = "/pages/courseAnswer/index?link=";
    private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
//    private static final String miniappRealLink = "/pages/index/index?course=";

    private static final String QWSOP_KEY_PREFIX = "qwsop:";
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    // Cached configurations and domain names
    private CourseConfig cachedCourseConfig;
    private final Object configLock = new Object();

    private List<FsCourseDomainName> cachedDomainNames;
    private final Object domainLock = new Object();


    // Batch size for database inserts, configurable via application.properties
    private final int BATCH_SIZE = 1000;

    @Autowired
    private SopUserLogsMapper sopUserLogsMapper;
    @Autowired
    private QwSopTagMapper qwSopTagMapper ;
    @Autowired
    private QwSopMapper sopMapper;


    @Autowired
    private QwExternalContactServiceImpl qwExternalContactService;

    @Autowired
    private FsCourseWatchLogMapper fsCourseWatchLogMapper;

    @Autowired
    private IQwSopLogsService qwSopLogsService;

    @Autowired
    private QwSopLogsMapper qwSopLogsMapper;

    @Autowired
    private FsCourseLinkMapper fsCourseLinkMapper;

    @Autowired
    private FsCourseSopAppLinkMapper fsCourseSopAppLinkMapper;

    @Autowired
    private ISysConfigService configService;

    @Autowired
    private FsCourseDomainNameMapper fsCourseDomainNameMapper;

    @Autowired
    private SopUserLogsInfoMapper sopUserLogsInfoMapper;
    @Autowired
    private QwUserMapper qwUserMapper;
    @Autowired
    private IQwSopTempRulesService qwSopTempRulesService;
    @Autowired
    private IQwSopTempContentService qwSopTempContentService;
    @Autowired
    private IQwSopTempVoiceService qwSopTempVoiceService;

    // Blocking queues with bounded capacity to implement backpressure
    private final BlockingQueue<QwSopLogs> qwSopLogsQueue = new LinkedBlockingQueue<>(20000);
    private final BlockingQueue<FsCourseWatchLog> watchLogsQueue = new LinkedBlockingQueue<>(20000);
    private final BlockingQueue<FsCourseLink> linkQueue = new LinkedBlockingQueue<>(20000);
    private final BlockingQueue<FsCourseSopAppLink> sopAppLinks = new LinkedBlockingQueue<>(20000);

    // Executors for consumer threads
    private ExecutorService qwSopLogsExecutor;
    private ExecutorService watchLogsExecutor;
    private ExecutorService courseLinkExecutor;
    private ExecutorService courseSopAppLinkExecutor;

    // Shutdown flags
    private volatile boolean running = true;
    @Autowired
    private QwSopTempMapper qwSopTempMapper;

    @Autowired
    private ICompanyUserService companyUserService;


    @PostConstruct
    public void init() {
        loadCourseConfig();
        startConsumers();
    }

    private void loadCourseConfig() {
        try {
            String json = configService.selectConfigByKey("course.config");
            CourseConfig config = JSON.parseObject(json, CourseConfig.class);
            if (config != null) {
                cachedCourseConfig = config;
                log.info("Loaded course.config successfully.");
            } else {
                log.error("Failed to load course.config from configService.");
            }
        } catch (Exception e) {
            log.error("Exception while loading course.config: {}", e.getMessage(), e);
        }
    }



    private void startConsumers() {
        qwSopLogsExecutor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "QwSopLogsConsumer");
            t.setDaemon(true);
            return t;
        });
        watchLogsExecutor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "WatchLogsConsumer");
            t.setDaemon(true);
            return t;
        });
        courseLinkExecutor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "courseLinkConsumer");
            t.setDaemon(true);
            return t;
        });

        courseSopAppLinkExecutor = Executors.newSingleThreadExecutor(r -> {
            Thread t = new Thread(r, "courseSopAppLinkConsumer");
            t.setDaemon(true);
            return t;
        });


        qwSopLogsExecutor.submit(this::consumeQwSopLogs);
        watchLogsExecutor.submit(this::consumeWatchLogs);
        courseLinkExecutor.submit(this::consumeCourseLink);
        courseSopAppLinkExecutor.submit(this::consumeCourseSopAppLink);
    }

    // Scheduled tasks to refresh configurations and domain names periodically
    @Scheduled(fixedDelay = 60000) // 每60秒刷新一次
    public void refreshCourseConfig() {
        synchronized(configLock) {
            try {
                String json = configService.selectConfigByKey("course.config");
                CourseConfig newConfig = JSON.parseObject(json, CourseConfig.class);
                if (newConfig != null) {
                    cachedCourseConfig = newConfig;
                    log.info("Refreshed course.config.");
                } else {
                    log.error("Failed to refresh course.config.");
                }
            } catch (Exception e) {
                log.error("Exception while refreshing course.config: {}", e.getMessage(), e);
            }
        }
    }



    @PreDestroy
    public void shutdownConsumers() {
        running = false;
        qwSopLogsExecutor.shutdown();
        watchLogsExecutor.shutdown();
        courseLinkExecutor.shutdown();
        courseSopAppLinkExecutor.shutdown();
        try {
            if (!qwSopLogsExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                qwSopLogsExecutor.shutdownNow();
            }
            if (!watchLogsExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                watchLogsExecutor.shutdownNow();
            }
            if (!courseLinkExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                courseLinkExecutor.shutdownNow();
            }
            if (!courseSopAppLinkExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                courseSopAppLinkExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            qwSopLogsExecutor.shutdownNow();
            watchLogsExecutor.shutdownNow();
            courseLinkExecutor.shutdownNow();
            courseSopAppLinkExecutor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void selectSopUserLogsListByTime(LocalDateTime currentTime) throws Exception {
        long startTimeMillis = System.currentTimeMillis();
        log.info("====== 开始选择和处理 SOP 用户日志 ======");

        // 获取缓存的配置
        CourseConfig config;
        synchronized(configLock) {
            config = cachedCourseConfig;
        }

        List<SopUserLogsVo> sopUserLogsVos = sopUserLogsMapper.selectSopUserLogsListByTime();
        if (sopUserLogsVos.isEmpty()) {
            log.info("没有需要处理的 SOP 用户日志。");
            return;
        }

//        Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
//                .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));
        // 查询销售二级域名
        Set<Long> ids = sopUserLogsVos.stream().map(s -> {
            String[] userKey = s.getUserId().split("\\|");
            if (userKey.length < 3) {
                return null;
            }
            return Long.parseLong(userKey[1]);
        }).filter(Objects::nonNull).collect(Collectors.toSet());
        List<CompanyUser> companyUserList;
        if (ids.isEmpty()) {
            companyUserList = new ArrayList<>();
        } else {
            companyUserList = companyUserService.selectCompanyUserByIds(ids);
        }

        Map<String, List<SopUserLogsVo>> sopLogsGroupedById = sopUserLogsVos.stream()
                .peek(s -> {
                    String[] userKey = s.getUserId().split("\\|");
                    if (userKey.length < 3) {
                        return;
                    }

                    // 销售ID
                    Long companyUserId = Long.parseLong(userKey[1]);
                    CompanyUser companyUser = companyUserList.stream().filter(cu -> Objects.equals(cu.getUserId(), companyUserId)).findFirst().orElse(null);
                    if (Objects.nonNull(companyUser)) {
                        if (StringUtils.isNotBlank(companyUser.getDomain())) {
                            s.setDomain(companyUser.getDomain().trim());
                        } else {
                            s.setDomain(config.getRealLinkDomainName().trim());
                        }
                    } else {
                        s.setDomain(config.getRealLinkDomainName().trim());
                    }
                })
                .collect(Collectors.groupingBy(SopUserLogsVo::getSopId));

        log.info("共分组 {} 个 SOP ID 进行处理。", sopLogsGroupedById.size());

        CountDownLatch sopGroupLatch = new CountDownLatch(sopLogsGroupedById.size());

        for (Map.Entry<String, List<SopUserLogsVo>> entry : sopLogsGroupedById.entrySet()) {
            String sopId = entry.getKey();
            List<SopUserLogsVo> userLogsVos = entry.getValue();
            processSopGroupAsync(sopId, userLogsVos, sopGroupLatch,currentTime);
        }

        // 等待所有 SOP 分组处理完成
        sopGroupLatch.await();

        // 触发批量插入（可选，如果需要立即插入队列中的数据）
        // batchInsertQwSopLogs();
        // batchInsertFsCourseWatchLogs();

        long endTimeMillis = System.currentTimeMillis();
        log.info("====== SOP 用户日志处理完成，耗时 {} 毫秒 ======", (endTimeMillis - startTimeMillis));
    }

    @Async("sopTaskExecutor")
    @Retryable(
            value = { Exception.class },
            maxAttempts = 3,
            backoff = @Backoff(delay = 2000)
    )
    public void processSopGroupAsync(String sopId, List<SopUserLogsVo> userLogsVos, CountDownLatch latch ,LocalDateTime currentTime) {
        try {
            processSopGroup(sopId, userLogsVos,currentTime);
        } catch (Exception e) {
            log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
        } finally {
            latch.countDown();
        }
    }


    private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos,LocalDateTime currentTime) throws Exception {
        QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);

        if (ruleTimeVO == null) {
//            sopUserLogsMapper.deleteSopUserLogsBySopId(sopId);
            log.error("SOP ID {} 已删除或不存在，相关日志已清除。", sopId);
            return;
        }
        QwSopTemp qwSopTemp = qwSopTempMapper.selectQwSopTempById(ruleTimeVO.getTempId());
        if (qwSopTemp == null) {
//            sopUserLogsMapper.deleteSopUserLogsBySopId(sopId);
            log.error("SOP ID {} 模板不存在，相关日志已清除。", sopId);
            return;
        }
        ruleTimeVO.setTempStatus(qwSopTemp.getStatus());
        ruleTimeVO.setTempGap(qwSopTemp.getGap());

        if (ruleTimeVO.getStatus() == 0 || "0".equals(ruleTimeVO.getTempStatus())) {
//            SopUserLogs sopUserLogs = new SopUserLogs();
//            sopUserLogs.setSopId(sopId);
//            sopUserLogs.setStatus(2);
//            sopUserLogsMapper.updateSopUserLogsByStatus(sopUserLogs);
            log.error("SOP ID {} 的状态为停用，相关日志状态已更新。", sopId);
            return;
        }

        List<QwSopTempRules> rulesList = qwSopTempRulesService.listByTempId(ruleTimeVO.getTempId());
        if (rulesList.isEmpty()) {
            log.error("SOP ID {} 的 TempSetting 为空，跳过处理。", sopId);
            return;
        }

        CountDownLatch userLogsLatch = new CountDownLatch(userLogsVos.size());
        for (SopUserLogsVo logVo : userLogsVos) {
            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime);
        }

        // 等待所有用户日志处理完成
        try {
            userLogsLatch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("等待用户日志处理完成时被中断: {}", e.getMessage(), e);
        }
        log.info("SOP ID {} 的所有用户日志已处理完毕。", sopId);
    }

    @Async("sopTaskExecutor")
    @Retryable(
            value = { Exception.class },
            maxAttempts = 3,
            backoff = @Backoff(delay = 2000)
    )
    public void processUserLogAsync(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings, CountDownLatch latch, LocalDateTime currentTime) {
        try {
            processUserLog(logVo, ruleTimeVO, tempSettings,currentTime);
        } catch (Exception e) {
            log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
        } finally {
            latch.countDown();
        }
    }


    private void processUserLog(SopUserLogsVo logVo, QwSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings, LocalDateTime currentTime) {
        try {
            LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
            LocalDate currentDate = currentTime.toLocalDate();

            long daysBetween = ChronoUnit.DAYS.between(startDate, currentDate);
            int tempGap = ruleTimeVO.getTempGap();

            if (tempGap <= 0) {
                log.error("SOP ID {} 的 TempGap {} 无效，跳过处理。", logVo.getSopId(), tempGap);
                return;
            }

            int intervalDay = (int) (daysBetween / tempGap);
            if (intervalDay < 0 || intervalDay >= tempSettings.size()) {
                log.info("用户日志 {} 的 intervalDay {} 超出 TempSettings 范围，跳过处理。", logVo.getId(), intervalDay);
                return;
            }
            long day = daysBetween;
            if(day == 0 && ruleTimeVO.getIsAutoSop() == 1){
                day = 1;
            }else{
                day++;
            }
            List<QwSopTempSetting.Content> contents = getDay(tempSettings, day);
            if (contents == null || contents.isEmpty()) {
                log.error("SOP ID {} 的 TempSetting 内容为空，跳过处理。", logVo.getSopId());
                return;
            }

            String[] userKey = logVo.getUserId().split("\\|");
            if (userKey.length < 3) {
                log.error("用户 ID {} 格式不正确，跳过处理。", logVo.getUserId());
                return;
            }
            String qwUserId = userKey[0].trim();
            String companyUserId = userKey[1].trim();
            String companyId = userKey[2].trim();


            //获取企业微信员工的称呼//从redis里或者从库里取
            QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedis(logVo.getCorpId(),logVo.getQwUserId());
            if (qwUserByRedis==null){
                log.error("无企微员工信息 {} 跳过处理。:{}", logVo.getUserId(),logVo.getCorpId());
                return;
            }

            //寻找时间
//            LocalDateTime currentTime = LocalDateTime.of(2024, 12, 25,23 , 40);

            // 先算好 60分钟后 ~ 再60分钟后的时间段
            LocalDateTime startRangeFirst = currentTime.plusMinutes(60);

            // 如果发现已经跨天
            if (!startRangeFirst.toLocalDate().equals(currentDate)) {
                // 更新 currentDate
                currentDate = startRangeFirst.toLocalDate();

                // 重新计算 daysBetween
                daysBetween = ChronoUnit.DAYS.between(startDate, currentDate);
                intervalDay = (int) (daysBetween / tempGap);
                day = daysBetween;
                if(day == 0 && ruleTimeVO.getIsAutoSop() == 1){
                    day = 1;
                }else{
                    day++;
                }
//
//                // 再次验证 intervalDay 是否在范围内
//                if (intervalDay < 0 || intervalDay >= tempSettings.size()) {
//                    log.info("跨天后，intervalDay={} 超出 TempSettings 范围，跳过。", intervalDay);
//                    return;
//                }
//
//                if (daysBetween % tempGap != 0) {
//                    log.error("天数差 {} 不是 tempGap {} 的整数倍，跳过操作,SopId {} ", daysBetween, tempGap,logVo.getSopId());
//                    return;
//                }

                // 重新拿新的 “天” 的 Setting
                contents = getDay(tempSettings, day);
                if (contents == null || contents.isEmpty()) {
                    log.error("跨天-SOP ID {} 的 TempSetting 内容为空，跳过处理。", logVo.getSopId());
                    return;
                }
            }


            // 只有整倍数才做事
            if (daysBetween % tempGap != 0) {
                log.error("天数差 {} 不是 tempGap {} 的整数倍，跳过操作,SopId {} ", daysBetween, tempGap,logVo.getSopId());
                return;
            }


            for (QwSopTempSetting.Content content : contents) {
                try {

                    LocalTime elementLocalTime = LocalTime.parse(content.getTime());
                    LocalDateTime elementDateTime = LocalDateTime.of(currentTime.toLocalDate(), elementLocalTime);

                    // 动态调整 elementDateTime 的日期
                    if (elementLocalTime.isBefore(currentTime.toLocalTime())) {
                        elementDateTime = elementDateTime.plusDays(1);
                    }

                    LocalDateTime startRange = currentTime.plusMinutes(60);
                    LocalDateTime endRange = startRange.plusMinutes(60);

                    // 跨天逻辑修正：仅当 startRange 的时间晚于 endRange 的时间时调整
                    if (startRange.toLocalTime().isAfter(endRange.toLocalTime())
                            && startRange.toLocalDate().equals(endRange.toLocalDate())) {
                        endRange = endRange.plusDays(1); // 将 endRange 调整为第二天
                    }
                    if (!elementDateTime.isBefore(startRange) && !elementDateTime.isAfter(endRange.minusMinutes(1))) {

                        // 如果时间差在目标范围内，更新记录
                        // 组合年月日和element的时间
                        LocalDate targetDate = startDate.plusDays(intervalDay * tempGap);

                        // 将 targetDate 和 elementTime 组合成 LocalDateTime
                        LocalDateTime dateTime = LocalDateTime.of(targetDate, elementLocalTime);

                        // 将 LocalDateTime 转换为 Date
                        Date sendTime = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());

                        SopUserLogsInfo userLogsInfo=new SopUserLogsInfo();
                        userLogsInfo.setSopId(logVo.getSopId());
                        userLogsInfo.setUserLogsId(logVo.getId());

                        List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoList(userLogsInfo);

                        insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, qwUserId, companyUserId, companyId, qwUserByRedis.getWelcomeText(),qwUserByRedis.getQwUserName());

                    }
                } catch (Exception e) {
                    log.error("解析模板内容 {} 失败: {}", content.getTime(), e.getMessage(), e);
                }
            }

        } catch (Exception e) {
            log.error("解析解析模板 {} 失败: {}", logVo.getStartTime(), e.getMessage(), e);
        }
    }


    private List<QwSopTempSetting.Content> getDay(List<QwSopTempRules> tempSettings, long days){
        List<QwSopTempRules> collect = tempSettings.stream().filter(e -> e.getDayNum() == days).collect(Collectors.toList());
        return collect.stream().map(e -> {
            QwSopTempSetting.Content content = new QwSopTempSetting.Content();
            content.setId(e.getId());
            content.setType(e.getType());
            content.setContentType(e.getContentType() != null ? e.getContentType().toString() : null);
            content.setSetting(e.getSettingList().stream().map(s -> {
                QwSopTempSetting.Content.Setting setting = JSON.parseObject(s.getContent(), QwSopTempSetting.Content.Setting.class);
                setting.setId(s.getId());
                return setting;
            }).collect(Collectors.toList()));
            content.setAddTag(e.getAddTag());
            content.setDelTag(e.getDelTag());
            content.setTime(e.getTime());
            content.setIsOfficial(e.getIsOfficial());
            content.setCourseId(e.getCourseId());
            content.setVideoId(e.getVideoId());
            content.setCourseType(e.getCourseType());
            content.setAiTouch(e.getAiTouch());
            return content;
        }).collect(Collectors.toList());
    }

    //消息处理
    private void insertSopUserLogs(List<SopUserLogsInfo> sopUserLogsInfos, SopUserLogsVo logVo, Date sendTime,
                                   QwSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
                                   String qwUserId,String companyUserId,String companyId,String welcomeText,String qwUserName) {
        String formattedSendTime = sendTime.toInstant()
                .atZone(ZoneId.systemDefault())
                .format(DATE_TIME_FORMATTER);
        int type = content.getType();
        Long courseId = content.getCourseId();
        Long videoId = content.getVideoId();
        Integer isOfficial = content.getIsOfficial() != null ? Integer.valueOf(content.getIsOfficial()) : 0;


        // 发送语音 start
        if(content.getSetting() == null){
            return;
        }
//        // 发送语音 end
        if (content.getType()==5){
            sopAddTag(logVo,content,sendTime);
        }


        // 处理每个 externalContactId
        sopUserLogsInfos.forEach(contactId -> {
            try {
                String externalId = contactId.getExternalId().toString();
                String externalUserName = contactId.getExternalUserName();
                Long fsUserId = contactId.getFsUserId();
                QwSopLogs sopLogs = createBaseLog(formattedSendTime, logVo, ruleTimeVO, contactId.getExternalContactId(), externalUserName, fsUserId,isOfficial,contactId.getExternalId());
                handleLogBasedOnType(sopLogs, content, logVo, sendTime, courseId, videoId,
                        type, qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName);
            } catch (Exception e) {
                log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
            }
        });
    }

    private void sopAddTag(SopUserLogsVo logVo, QwSopTempSetting.Content content, Date sendTime) {
        String id = logVo.getId();
        String addTag = content.getAddTag();
        String delTag = content.getDelTag();
        String corpId = logVo.getCorpId();
        if (addTag!=null || delTag!=null) {
            QwSopTag qwSopTag = new QwSopTag();
            qwSopTag.setAddTags(addTag);
            qwSopTag.setDelTags(delTag);
            qwSopTag.setCorpId(corpId);
            qwSopTag.setSopUserLogsId(id);
            qwSopTag.setType(1);
            qwSopTag.setStatus(1);
            qwSopTag.setSendTime(sendTime);
            qwSopTag.setCreateTime(new Date());
            qwSopTagMapper.insertQwSopTag(qwSopTag);
        }
    }

    private QwSopLogs createBaseLog(String formattedSendTime, SopUserLogsVo logVo,
                                    QwSopRuleTimeVO ruleTimeVO, String externalContactId,
                                    String externalUserName, Long fsUserId,Integer isOfficial,
                                    Long externalId) {
        QwSopLogs sopLogs = new QwSopLogs();
        sopLogs.setSendTime(formattedSendTime);
        sopLogs.setQwUserid(logVo.getQwUserId());
        sopLogs.setCorpId(logVo.getCorpId());
        sopLogs.setLogType(ruleTimeVO.getType());
        sopLogs.setSendType(isOfficial == 1 ? 1 : ruleTimeVO.getSendType());
        sopLogs.setSendStatus(3L);
        sopLogs.setReceivingStatus(0L);

        String[] userKey = logVo.getUserId().split("\\|");
        sopLogs.setCompanyId(Long.valueOf(userKey[2].trim()));
        sopLogs.setSopId(logVo.getSopId());

        sopLogs.setExternalUserId(externalContactId);
        sopLogs.setExternalId(externalId);
        sopLogs.setExternalUserName(externalUserName);
        sopLogs.setFsUserId(fsUserId);

        return sopLogs;
    }

    private void handleLogBasedOnType(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                      SopUserLogsVo logVo, Date sendTime, Long courseId,
                                      Long videoId, int type, String qwUserId,
                                      String companyUserId, String companyId, String externalId,String welcomeText,String qwUserName) {
        switch (type) {
            case 1:
                handleNormalMessage(sopLogs, content,companyUserId);
                break;
            case 2:
                handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
                        qwUserId, companyUserId, companyId, externalId, welcomeText,qwUserName);
                break;
            case 3:
                handleOrderMessage(sopLogs, content);
                break;
            case 4:
//                handleAIMessage(sopLogs, content);
                break;
            case 5:
//                handleTagMessage(sopLogs, content);
                break;
            default:
                log.error("未知的消息类型 {}，跳过处理。", type);
                break;
        }
    }

    private void handleNormalMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,String companyUserId) {

        sopLogs.setContentJson(JSON.toJSONString(content));
        enqueueQwSopLogs(sopLogs);
    }

    private void handleAIMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content) {
        sopLogs.setContentJson(JSON.toJSONString(content));
        sopLogs.setSort(3);
        enqueueQwSopLogs(sopLogs);
    }

    private void handleCourseMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content,
                                     SopUserLogsVo logVo, Date sendTime, Long courseId,
                                     Long videoId, String qwUserId, String companyUserId,
                                     String companyId, String externalId,String welcomeText,String qwUserName) {
        // 深拷贝 Content 对象，避免使用 JSON
        QwSopTempSetting.Content clonedContent = deepCopyContent(content);
        if (clonedContent == null) {
            log.error("Failed to clone content, skipping handleCourseMessage.");
            return;
        }

//
//        Integer courseType = clonedContent.getCourseType();

        List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
        if (settings == null || settings.isEmpty()) {
            log.error("Cloned content settings are empty, skipping.");
            return;
        }


        // 顺序处理每个 Setting，避免过多的并行导致线程开销
        for (QwSopTempSetting.Content.Setting setting : settings) {
            switch (setting.getContentType()) {
                //文字和短链一起
                case "1":
                case "3":
                    if ("1".equals(setting.getIsBindUrl())) {

                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);
                        String sortLink = generateShortLink(setting, logVo, sendTime, courseId, videoId,
                                qwUserId, companyUserId, companyId, externalId);
                        if (StringUtils.isNotEmpty(sortLink)) {
                            if ("3".equals(setting.getContentType())) {
                                setting.setLinkUrl(sortLink);
                            } else {
                                String currentValue = setting.getValue();
                                if (currentValue == null) {
                                    setting.setValue(sortLink);
                                } else {
//                                    setting.setValue(currentValue + "\n" + sortLink);
                                    setting.setValue(currentValue
                                            .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(welcomeText)?"":welcomeText)
                                            + "\n" + sortLink);
                                }
                            }
                        } else {
                            log.error("生成短链失败，跳过设置 URL。");
                        }

                    }else {
                        if ("1".equals(setting.getContentType())) {
                            setting.setValue(setting.getValue()
                                    .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText)?"":welcomeText));
                        }
                    }
                    break;
                //小程序单独
                case "4":

                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);

                    String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                            qwUserId, companyUserId, companyId, externalId);

                    setting.setMiniprogramPage(sortLink);

                    break;
                //app
                case "9":
                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId,logVo);

                    QwCreateLinkByAppVO linkByApp = createLinkByApp(setting, logVo, sendTime, courseId, videoId,
                            qwUserId, companyUserId, companyId, externalId,sopLogs.getCorpId(),qwUserName);

                    setting.setLinkUrl(linkByApp.getSortLink().replaceAll("^[\\s\\u2005]+", ""));
                    setting.setAppLinkUrl(linkByApp.getAppMsgLink().replaceAll("^[\\s\\u2005]+", ""));

                    break;
                default:
                    break;
            }

        }
        sopLogs.setContentJson(JSON.toJSONString(clonedContent));
        enqueueQwSopLogs(sopLogs);
    }

    /**
     * 深拷贝 Content 对象，避免使用 JSON 进行序列化和反序列化
     */
    private QwSopTempSetting.Content deepCopyContent(QwSopTempSetting.Content content) {
        if (content == null) {
            return null;
        }
        return content.clone();
    }

    private void handleOrderMessage(QwSopLogs sopLogs, QwSopTempSetting.Content content) {
        sopLogs.setContentJson(JSON.toJSONString(content));
        enqueueQwSopLogs(sopLogs);
    }




    private String generateShortLink(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                     Long courseId, Long videoId, String qwUserId,
                                     String companyUserId, String companyId, String externalId) {
        // 获取缓存的配置
        CourseConfig config;
        synchronized(configLock) {
            config = cachedCourseConfig;
        }

        if (config == null) {
            log.error("CourseConfig is not loaded.");
            return "";
        }

        // 手动创建 FsCourseLink 对象，避免使用 BeanUtils.copyProperties
        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(Long.parseLong(companyId));
        link.setQwUserId(qwUserId);
        link.setCompanyUserId(Long.parseLong(companyUserId));
        link.setVideoId(videoId.longValue());
        link.setCorpId(logVo.getCorpId());
        link.setCourseId(courseId.longValue());
        link.setQwExternalId(Long.parseLong(externalId));
        link.setLinkType(0); //正常链接

        FsCourseRealLink courseMap = new FsCourseRealLink();
        courseMap.setCompanyId(link.getCompanyId());
        courseMap.setQwUserId(link.getQwUserId());
        courseMap.setCompanyUserId(link.getCompanyUserId());
        courseMap.setVideoId(link.getVideoId());
        courseMap.setCorpId(link.getCorpId());
        courseMap.setCourseId(link.getCourseId());
        courseMap.setQwExternalId(link.getQwExternalId());
        courseMap.setLinkType(0);

        String courseJson = JSON.toJSONString(courseMap);
        String realLinkFull = REAL_LINK_PREFIX + courseJson;
        link.setRealLink(realLinkFull);

        String randomString = generateRandomStringWithLock();
        link.setLink(randomString);
        link.setCreateTime(sendTime);

        Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
                ? config.getVideoLinkExpireDate()
                : setting.getExpiresDays();

        // 使用 Java 8 时间 API 计算过期时间
        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
        link.setUpdateTime(updateTime);

        //取销售绑定的二级域名
        String sortLink = logVo.getDomain() + SHORT_LINK_PREFIX + link.getLink();
        enqueueCourseLink(link);
        return sortLink.replaceAll("^[\\s\\u2005]+", "");
    }

    private QwCreateLinkByAppVO createLinkByApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                                Long courseId, Long videoId, String qwUserId,
                                                String companyUserId, String companyId, String externalId,String corpId,String qwUserName){
        // 获取缓存的配置
        CourseConfig config;
        synchronized(configLock) {
            config = cachedCourseConfig;
        }

        if (config == null) {
            log.error("CourseConfig is not loaded.");
            return null;
        }
        FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
                companyUserId, companyId, externalId, 4);

        FsCourseRealLink courseMap = new FsCourseRealLink();
        BeanUtils.copyProperties(link,courseMap);

        String courseJson = JSON.toJSONString(courseMap);
        String realLinkFull = REAL_LINK_PREFIX + courseJson;
        link.setRealLink(realLinkFull);

        Date updateTime = createUpdateTime(setting, sendTime, config);

        link.setUpdateTime(updateTime);

        String sortLink = appLink+link.getLink()+"&videoId="+videoId;

        String appMsgLink=appRealLink+link.getLink();

        QwCreateLinkByAppVO byAppVO=new QwCreateLinkByAppVO();
        byAppVO.setSortLink(sortLink);
        byAppVO.setAppMsgLink(appMsgLink);

        FsCourseSopAppLink fsCourseSopAppLink = createFsCourseSopAppLink(link.getLink(), sendTime, updateTime, companyId, companyUserId, qwUserId,
                qwUserName, corpId, courseId, setting.getLinkTitle(), setting.getLinkImageUrl(), videoId,
                setting.getLinkDescribe(), appMsgLink, externalId);

        enqueueCourseSopAppLink(fsCourseSopAppLink);

        enqueueCourseLink(link);

        return byAppVO;
    }


    public FsCourseSopAppLink createFsCourseSopAppLink(String link, Date sendTime, Date updateTime, String companyId,
                                         String companyUserId,String qwUserId,String qwUserName,String corpId,
                                         Long courseId,String linkTile,String linkImageUrl,Long videoId,
                                         String linkDescribe,String appMsgLink,String externalId){

        FsCourseSopAppLink sopAppLink=new FsCourseSopAppLink();
        sopAppLink.setLink(link);
        sopAppLink.setCreateTime(sendTime);
        sopAppLink.setUpdateTime(updateTime);
        sopAppLink.setCompanyId(Long.parseLong(companyId));
        sopAppLink.setCompanyUserId(Long.parseLong(companyUserId));
        sopAppLink.setQwUserId(Long.parseLong(qwUserId));
        sopAppLink.setQwUserName(qwUserName);
        sopAppLink.setCorpId(corpId);
        sopAppLink.setCourseId(courseId);
        sopAppLink.setCourseTitle(linkTile);
        sopAppLink.setCourseUrl(linkImageUrl);
        sopAppLink.setVideoId(videoId);
        sopAppLink.setVideoTitle(linkDescribe);
        sopAppLink.setAppRealLink(appMsgLink);
        sopAppLink.setQwExternalId(Long.parseLong(externalId));


        return sopAppLink;
    }

    public FsCourseLink createFsCourseLink(String corpId, Date sendTime,Long courseId,Long videoId, String qwUserId,
                                           String companyUserId, String companyId,String externalId,Integer type){
        // 手动创建 FsCourseLink 对象，避免使用 BeanUtils.copyProperties
        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(Long.parseLong(companyId));
        link.setQwUserId(qwUserId);
        link.setCompanyUserId(Long.parseLong(companyUserId));
        link.setVideoId(videoId.longValue());
        link.setCorpId(corpId);
        link.setCourseId(courseId.longValue());
        link.setQwExternalId(Long.parseLong(externalId));
        link.setLinkType(type); //小程序
        String randomString = generateRandomStringWithLock();
        link.setLink(randomString);
        link.setCreateTime(sendTime);

        return link;
    }

    private Date createUpdateTime(QwSopTempSetting.Content.Setting setting,Date sendTime,CourseConfig config){

        Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
                ? config.getVideoLinkExpireDate()
                : setting.getExpiresDays();

//         使用 Java 8 时间 API 计算过期时间
        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());

        return updateTime;
    }

    private String createLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
                                     Long courseId, Long videoId, String qwUserId,
                                     String companyUserId, String companyId, String externalId) {
        // 获取缓存的配置
        CourseConfig config;
        synchronized(configLock) {
            config = cachedCourseConfig;
        }

        if (config == null) {
            log.error("CourseConfig is not loaded.");
            return "";
        }
//        if (StringUtils.isEmpty(config.getMiniprogramPage())){
//            log.error("miniprogramPage is not loaded.");
//            return "";
//        }

        // 手动创建 FsCourseLink 对象，避免使用 BeanUtils.copyProperties
        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(Long.parseLong(companyId));
        link.setQwUserId(qwUserId);
        link.setCompanyUserId(Long.parseLong(companyUserId));
        link.setVideoId(videoId.longValue());
        link.setCorpId(logVo.getCorpId());
        link.setCourseId(courseId.longValue());
        link.setQwExternalId(Long.parseLong(externalId));
        link.setLinkType(3); //正常链接
        String randomString = generateRandomStringWithLock();
        link.setLink(randomString);
        link.setCreateTime(sendTime);

        FsCourseRealLink courseMap = new FsCourseRealLink();
        BeanUtils.copyProperties(link,courseMap);

        String courseJson = JSON.toJSONString(courseMap);
        String realLinkFull = miniappRealLink + courseJson;
//        String realLinkFull = config.getMiniprogramPage() + courseJson;
        link.setRealLink(realLinkFull);


        Integer expireDays = (setting.getExpiresDays() == null || setting.getExpiresDays() == 0)
                ? config.getVideoLinkExpireDate()
                : setting.getExpiresDays();

        // 使用 Java 8 时间 API 计算过期时间
        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        LocalDateTime expireDateTime = sendDateTime.plusDays(expireDays-1);
        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
        link.setUpdateTime(updateTime);

        //存短链-
        enqueueCourseLink(link);
        return link.getRealLink().replaceAll("^[\\s\\u2005]+", "");
    }

    private void addWatchLogIfNeeded(QwSopLogs sopLogs, Long videoId, Long courseId,
                                     Date sendTime, String qwUserId, String companyUserId,
                                     String companyId, String externalId,SopUserLogsVo logsVo) {
        FsCourseWatchLog watchLog = new FsCourseWatchLog();
        watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
        watchLog.setQwExternalContactId(externalId != null ? Long.valueOf(externalId) : null);
        watchLog.setSendType(2);
        watchLog.setQwUserId(qwUserId);
        watchLog.setSopId(sopLogs.getSopId());
        watchLog.setDuration(0L);
        watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
        watchLog.setCompanyUserId(companyUserId != null ? Long.valueOf(companyUserId) : null);
        watchLog.setCompanyId(companyId != null ? Long.valueOf(companyId) : null);
        watchLog.setCreateTime(convertStringToDate(sopLogs.getSendTime(),"yyyy-MM-dd HH:mm:ss"));
        watchLog.setUpdateTime(new Date());
        watchLog.setLogType(3);
        watchLog.setUserId(sopLogs.getFsUserId());
        watchLog.setCampPeriodTime(convertStringToDate(logsVo.getStartTime(),"yyyy-MM-dd"));
        enqueueWatchLog(watchLog);
    }

    /**
     * 时间字符串转Date时间
     * @param dateString
     * @return
     */
    public static Date convertStringToDate(String dateString,String pattern) {
        if (dateString == null || dateString.isEmpty()) {
            return null;
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        LocalDateTime localDateTime;
        LocalDate localDate;
        // 先解析成 LocalDate（只含年月日）
        if (pattern.equals("yyyy-MM-dd")){
            // 先解析成 LocalDate（只含年月日）
            localDate = LocalDate.parse(dateString, formatter);
            // 将 LocalDate 转为当天 00:00:00 的 LocalDateTime
            localDateTime = localDate.atStartOfDay();
        }else {
            localDateTime = LocalDateTime.parse(dateString, formatter);
        }
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }


    /**
     * 将 QwSopLogs 放入队列
     */
    private void enqueueQwSopLogs(QwSopLogs sopLogs) {
        try {
            boolean offered = qwSopLogsQueue.offer(sopLogs, 5, TimeUnit.SECONDS);
            if (!offered) {
                log.error("QwSopLogs 队列已满，无法添加日志: {}", JSON.toJSONString(sopLogs));
                // 处理队列已满的情况，例如记录到失败队列或持久化存储
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("插入 QwSopLogs 队列时被中断: {}", e.getMessage(), e);
        }
    }

    /**
     * 将 FsCourseWatchLog 放入队列
     */
    private void enqueueWatchLog(FsCourseWatchLog watchLog) {
        try {
            boolean offered = watchLogsQueue.offer(watchLog, 5, TimeUnit.SECONDS);
            if (!offered) {
                log.error("FsCourseWatchLog 队列已满，无法添加日志: {}", JSON.toJSONString(watchLog));
                // 处理队列已满的情况，例如记录到失败队列或持久化存储
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("插入 FsCourseWatchLog 队列时被中断: {}", e.getMessage(), e);
        }
    }

    /**
     * 将 FsCourseWatchLog 放入队列
     */
    private void enqueueCourseLink(FsCourseLink courseLink) {
        try {
            boolean offered = linkQueue.offer(courseLink, 5, TimeUnit.SECONDS);
            if (!offered) {
                log.error("FsCourseLink 队列已满，无法添加日志: {}", JSON.toJSONString(courseLink));
                // 处理队列已满的情况，例如记录到失败队列或持久化存储
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("插入 FsCourseLink 队列时被中断: {}", e.getMessage(), e);
        }
    }

    /**
     * 将 FsCourseSopAppLing 放入队列
     */
    private void enqueueCourseSopAppLink(FsCourseSopAppLink sopAppLink) {
        try {
            boolean offered = sopAppLinks.offer(sopAppLink, 5, TimeUnit.SECONDS);
            if (!offered) {
                log.error("FsCourseSopAppLink 队列已满，无法添加日志: {}", JSON.toJSONString(sopAppLink));
                // 处理队列已满的情况，例如记录到失败队列或持久化存储
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.error("插入 FsCourseLink 队列时被中断: {}", e.getMessage(), e);
        }
    }

    /**
     * 消费 QwSopLogs 队列并进行批量插入
     */
    private void consumeQwSopLogs() {
        List<QwSopLogs> batch = new ArrayList<>(BATCH_SIZE);
        while (running || !qwSopLogsQueue.isEmpty()) {
            try {
                QwSopLogs log = qwSopLogsQueue.poll(1, TimeUnit.SECONDS);
                if (log != null) {
                    batch.add(log);
                }
                if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && log == null)) {
                    if (!batch.isEmpty()) {
                        batchInsertQwSopLogs(new ArrayList<>(batch));
                        batch.clear();
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("QwSopLogs 消费线程被中断: {}", e.getMessage(), e);
            }
        }

        // 处理剩余的数据
        if (!batch.isEmpty()) {
            batchInsertQwSopLogs(batch);
        }
    }

    /**
     * 消费 FsCourseWatchLog 队列并进行批量插入
     */
    private void consumeCourseLink() {
        List<FsCourseLink> batch = new ArrayList<>(BATCH_SIZE);
        while (running || !linkQueue.isEmpty()) {
            try {
                FsCourseLink courseLink = linkQueue.poll(1, TimeUnit.SECONDS);
                if (courseLink != null) {
                    batch.add(courseLink);
                }
                if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && courseLink == null)) {
                    if (!batch.isEmpty()) {
                        batchInsertFsCourseLink(new ArrayList<>(batch));
                        batch.clear();
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("FsCourseLink 消费线程被中断: {}", e.getMessage(), e);
            }
        }

        // 处理剩余的数据
        if (!batch.isEmpty()) {
            batchInsertFsCourseLink(batch);
        }
    }

    /**
     * 消费 FsCourseSopAppLink 队列并进行批量插入
     */
    private void consumeCourseSopAppLink() {
        List<FsCourseSopAppLink> batch = new ArrayList<>(BATCH_SIZE);
        while (running || !sopAppLinks.isEmpty()) {
            try {
                FsCourseSopAppLink courseSopAppLink = sopAppLinks.poll(1, TimeUnit.SECONDS);
                if (courseSopAppLink != null) {
                    batch.add(courseSopAppLink);
                }
                if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && courseSopAppLink == null)) {
                    if (!batch.isEmpty()) {
                        batchInsertFsCourseSopAppLink(new ArrayList<>(batch));
                        batch.clear();
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("FsCourseSopAppLink 消费线程被中断: {}", e.getMessage(), e);
            }
        }

        // 处理剩余的数据
        if (!batch.isEmpty()) {
            batchInsertFsCourseSopAppLink(batch);
        }
    }

    /**
     * 消费 FsCourseWatchLog 队列并进行批量插入
     */
    private void consumeWatchLogs() {
        List<FsCourseWatchLog> batch = new ArrayList<>(BATCH_SIZE);
        while (running || !watchLogsQueue.isEmpty()) {
            try {
                FsCourseWatchLog watchLog = watchLogsQueue.poll(1, TimeUnit.SECONDS);
                if (watchLog != null) {
                    batch.add(watchLog);
                }
                if (batch.size() >= BATCH_SIZE || (!batch.isEmpty() && watchLog == null)) {
                    if (!batch.isEmpty()) {
                        batchInsertFsCourseWatchLogs(new ArrayList<>(batch));
                        batch.clear();
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                log.error("FsCourseWatchLog 消费线程被中断: {}", e.getMessage(), e);
            }
        }

        // 处理剩余的数据
        if (!batch.isEmpty()) {
            batchInsertFsCourseWatchLogs(batch);
        }
    }

    /**
     * 批量插入 QwSopLogs
     */
    @Transactional
    @Retryable(
            value = { Exception.class },
            maxAttempts = 3,
            backoff = @Backoff(delay = 2000)
    )
    public void batchInsertQwSopLogs(List<QwSopLogs> logsToInsert) {
        try {
            qwSopLogsService.batchInsertQwSopLogs(logsToInsert);
            log.info("批量插入 QwSopLogs 完成，共插入 {} 条记录。", logsToInsert.size());
        } catch (Exception e) {
            log.error("批量插入 QwSopLogs 失败: {}", e.getMessage(), e);
            // 可选：将失败的数据记录到失败队列或持久化存储以便后续重试
        }
    }

    /**
     * 批量插入 FsCourseWatchLog
     */
    @Transactional
    @Retryable(
            value = { Exception.class },
            maxAttempts = 3,
            backoff = @Backoff(delay = 2000)
    )
    public void batchInsertFsCourseWatchLogs(List<FsCourseWatchLog> watchLogsToInsert) {
        try {
            fsCourseWatchLogMapper.insertFsCourseWatchLogBatch(watchLogsToInsert);
            log.info("批量插入 FsCourseWatchLog 完成，共插入 {} 条记录。", watchLogsToInsert.size());
        } catch (Exception e) {
            log.error("批量插入 FsCourseWatchLog 失败: {}", e.getMessage(), e);
            // 可选：将失败的数据记录到失败队列或持久化存储以便后续重试
        }
    }


    /**
     * 批量插入 FsCourseLink
     */
    @Transactional
    @Retryable(
            value = { Exception.class },
            maxAttempts = 3,
            backoff = @Backoff(delay = 2000)
    )
    public void batchInsertFsCourseLink(List<FsCourseLink> courseLinkToInsert) {
        try {
            fsCourseLinkMapper.insertFsCourseLinkBatch(courseLinkToInsert);
            log.info("批量插入 FsCourseLink 完成，共插入 {} 条记录。", courseLinkToInsert.size());
        } catch (Exception e) {
            log.error("批量插入 FsCourseLink 失败: {}", e.getMessage(), e);
            // 可选：将失败的数据记录到失败队列或持久化存储以便后续重试
        }
    }


    /**
     * 批量插入 FsCourseSopAppLink
     */
    @Transactional
    @Retryable(
            value = { Exception.class },
            maxAttempts = 3,
            backoff = @Backoff(delay = 2000)
    )
    public void batchInsertFsCourseSopAppLink(List<FsCourseSopAppLink> courseSopAppLinkToInsert) {
        try {
            fsCourseSopAppLinkMapper.insertFsCourseSopAppLinkBatch(courseSopAppLinkToInsert);
            log.info("批量插入 FsCourseSopAppLink 完成，共插入 {} 条记录。", courseSopAppLinkToInsert.size());
        } catch (Exception e) {
            log.error("批量插入 FsCourseSopAppLink 失败: {}", e.getMessage(), e);
            // 可选：将失败的数据记录到失败队列或持久化存储以便后续重试
        }
    }


    @Override
    public void updateSopLogsByCancel() {
        List<QwSopLogs> sopLogs = qwSopLogsMapper.selectQwSopLogsByCancel();
        log.info("补发过期完课消息总条数：{}",sopLogs.size());
        processUpdateQwSopLogs(sopLogs);
    }


    // 定义一个方法来批量处理插入逻辑，支持每 500 条数据一次的批量插入
    private void processUpdateQwSopLogs(List<QwSopLogs> sopLogs) {
        // 定义批量插入的大小
        int batchSize = 500;

        // 循环处理外部用户 ID，每次处理批量大小的子集
        for (int i = 0; i < sopLogs.size(); i += batchSize) {

            int endIndex = Math.min(i + batchSize, sopLogs.size());
            List<QwSopLogs> batchList = sopLogs.subList(i, endIndex);  // 获取当前批次的子集

            // 直接使用批次数据进行批量更新，不需要额外的 List
            try {
                qwSopLogsMapper.batchUpdateQwSopLogsByCancel(batchList);
                log.info("正在补发条数：{}",batchSize);
            } catch (Exception e) {
                // 记录异常日志，方便后续排查问题
                log.error("批量更新数据时发生异常，处理的批次起始索引为: " + i, e);
            }
        }
    }

    @Autowired
    private FsCourseFinishTempMapper fsCourseFinishTempMapper;
    @Autowired
    private QwExternalContactMapper qwExternalContactMapper;


//    @Override
//    @Transactional
//    public void creatMessMessage(QwSopLogs logs) {
//       // qwSopLogsMapper.insertQwSopLogs(logs);
//        QwSopTempSetting.Content content = JSON.parseObject(logs.getContentJson(), QwSopTempSetting.Content.class);
//        handleNormalMessage(logs, content,null);
//    }

    @Override
    public void createCourseFinishMsg() {
        // 查询所有需要处理的完课记录
        List<FsCourseWatchLog> finishLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogFinish();
        if (finishLogs.isEmpty()) {
            log.info("没有需要处理的完课记录");
            return;
        }

        // 用于批量更新的 finishLog 列表
        List<FsCourseWatchLog> finishLogsToUpdate = new ArrayList<>();
        // 用于批量插入的 sopLogs 列表
        List<QwSopLogs> sopLogsToInsert = new ArrayList<>();

        for (FsCourseWatchLog finishLog : finishLogs) {
            try {
                // 查询外部联系人信息
                QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(finishLog.getQwExternalContactId());
                if (externalContact == null) {
                    log.error("外部联系人不存在: " + finishLog.getQwExternalContactId());
                    continue;
                }

                // 查询完课模板信息
//                FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyUserId(finishLog.getCompanyUserId(), finishLog.getVideoId());
                FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyId(finishLog.getCompanyUserId(),finishLog.getCompanyId(), finishLog.getVideoId());

                // 设置 finishLog 为已发送状态，并加入批量更新列表
                finishLog.setSendFinishMsg(1);
                finishLogsToUpdate.add(finishLog);

                if (finishTemp == null) {
                    log.error("完课模板不存在: " + finishLog.getCompanyUserId() + ", " + finishLog.getVideoId());
                    continue;
                }

                // 构建 sopLogs 对象
                QwSopLogs sopLogs = buildSopLogs(finishLog, externalContact, finishTemp);
                if (sopLogs == null) {
                    continue;
                }

                // 如果客户状态有效，则加入批量插入列表
                if (isValidExternalContact(externalContact)) {
                    sopLogsToInsert.add(sopLogs);
                } else {
                    log.info("完课消息-客户信息有误,不生成完课消息: " + finishLog.getQwExternalContactId());
                }
            } catch (Exception e) {
                log.error("处理完课记录失败: " + finishLog.getLogId(), e);
            }
        }

        // 分批更新 finishLog（每批 100 条）
        if (!finishLogsToUpdate.isEmpty()) {
            List<List<FsCourseWatchLog>> updateBatches = BatchUtils.splitList(finishLogsToUpdate, 100);
            for (List<FsCourseWatchLog> batch : updateBatches) {
                try {
                    fsCourseWatchLogMapper.batchUpdateWatchLogSendMsg(batch);
                    log.info("批量更新 finishLog 成功，更新数量: " + batch.size());
                } catch (Exception e) {
                    log.error("批量更新 finishLog 失败", e);
                }
            }
        }

        // 分批插入 sopLogs（每批 500 条）
        if (!sopLogsToInsert.isEmpty()) {
            List<List<QwSopLogs>> insertBatches = BatchUtils.splitList(sopLogsToInsert, 500);
            for (List<QwSopLogs> batch : insertBatches) {
                try {
                    qwSopLogsService.batchInsertQwSopLogs(batch);
                    log.info("批量插入 sopLogs 成功，插入数量: " + batch.size());
                } catch (Exception e) {
                    log.error("批量插入 sopLogs 失败", e);
                }
            }
        }
    }

    /**
     * 构建 QwSopLogs 对象
     */
    private QwSopLogs buildSopLogs(FsCourseWatchLog finishLog, QwExternalContact externalContact, FsCourseFinishTemp finishTemp) {
        QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime currentTime = LocalDateTime.now();
        LocalDateTime newTime = currentTime.plusMinutes(3);
        String newTimeString = newTime.format(formatter);

        QwSopLogs sopLogs = new QwSopLogs();
        sopLogs.setSendTime(newTimeString);
        sopLogs.setQwUserid(externalContact.getUserId());
        sopLogs.setCorpId(externalContact.getCorpId());
        sopLogs.setLogType(2);
        sopLogs.setSendType(3);
        sopLogs.setSendStatus(3L);
        sopLogs.setReceivingStatus(0L);
        sopLogs.setSort(4);
        sopLogs.setCompanyId(finishLog.getCompanyId());
        sopLogs.setSopId(finishLog.getSopId());
        sopLogs.setExternalUserId(externalContact.getExternalUserId());
        sopLogs.setExternalUserName(externalContact.getName());
        sopLogs.setFsUserId(finishLog.getUserId() != null ? finishLog.getUserId() : null );
        // 解析模板设置
        List<QwSopCourseFinishTempSetting.Setting> settings = parseSettings(finishTemp.getSetting());
        if (settings == null) {
            return null;
        }

        // 处理音频内容
//        for (QwSopCourseFinishTempSetting.Setting st : settings) {
//            if (st.getContentType().equals("7")) {
//                try {
//                    AudioVO audioVO = AudioUtils.transferAudioSilkFromText(st.getValue(), finishLog.getCompanyUserId(), false);
//                    st.setVoiceUrl(audioVO.getUrl());
//                    st.setVoiceDuration(audioVO.getDuration() + "");
//                } catch (Exception e) {
//                    log.error("音频生成失败: " + finishLog.getCompanyUserId(), e);
//                }
//            }
//        }

        setting.setSetting(settings);
        sopLogs.setContentJson(JSON.toJSONString(setting));
        return sopLogs;
    }

    /**
     * 解析模板设置
     */
    private List<QwSopCourseFinishTempSetting.Setting> parseSettings(String jsonData) {
        try {
            if (jsonData.startsWith("[") && jsonData.endsWith("]")) {
                return JSONArray.parseArray(jsonData, QwSopCourseFinishTempSetting.Setting.class);
            } else {
                String fixedJson = JSON.parseObject(jsonData, String.class);
                return JSONArray.parseArray(fixedJson, QwSopCourseFinishTempSetting.Setting.class);
            }
        } catch (Exception e) {
            log.error("解析模板设置失败", e);
            return null;
        }
    }

    /**
     * 检查外部联系人状态是否有效
     */
    private boolean isValidExternalContact(QwExternalContact externalContact) {
        return externalContact.getStatus() == 0 || externalContact.getStatus() == 2 || externalContact.getStatus() == 3;
    }
}
