package com.fs.app.taskService.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fs.app.taskService.SopWxLogsTaskService;
import com.fs.common.config.FSSysConfig;
import com.fs.common.core.redis.RedisCache;
import com.fs.common.utils.CloudHostUtils;
import com.fs.common.utils.PubFun;
import com.fs.common.utils.StringUtils;
import com.fs.company.domain.Company;
import com.fs.company.domain.CompanyMiniapp;
import com.fs.company.domain.CompanyUser;
import com.fs.company.mapper.CompanyMapper;
import com.fs.company.mapper.CompanyWxAccountMapper;
import com.fs.company.service.ICompanyMiniappService;
import com.fs.company.service.ICompanyUserService;
import com.fs.config.cloud.CloudHostProper;
import com.fs.course.config.CourseConfig;
import com.fs.course.domain.*;
import com.fs.course.mapper.*;
import com.fs.course.service.IFsCourseLinkService;
import com.fs.crm.domain.CrmCustomer;
import com.fs.crm.mapper.CrmCustomerMapper;
import com.fs.his.domain.FsUser;
import com.fs.his.mapper.FsUserMapper;
import com.fs.his.utils.ConfigUtil;
import com.fs.live.domain.LiveWatchLog;
import com.fs.live.mapper.LiveWatchLogMapper;
import com.fs.qw.domain.*;
import com.fs.qw.mapper.LuckyBagCollectRecordMapper;
import com.fs.qw.mapper.LuckyBagMapper;
import com.fs.qw.mapper.QwExternalContactMapper;
import com.fs.qw.mapper.QwUserMapper;
import com.fs.qw.service.IQwCompanyService;
import com.fs.qw.service.IQwGroupChatService;
import com.fs.qw.service.IQwGroupChatUserService;
import com.fs.qw.service.impl.QwExternalContactServiceImpl;
import com.fs.qw.vo.*;
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.sop.vo.WxSopUserVo;
import com.fs.system.domain.SysConfig;
import com.fs.system.mapper.SysConfigMapper;
import com.fs.system.service.ISysConfigService;
import com.fs.voice.utils.StringUtil;
import com.fs.wx.sop.domain.WxSopLogs;
import com.fs.wx.sop.domain.WxSopUserInfo;
import com.fs.wx.sop.service.IWxSopLogsService;
import com.fs.wxcid.domain.WxContact;
import com.fs.wxcid.mapper.WxContactMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
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.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

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

@Service
@Slf4j
public class SopWxLogsTaskServiceImpl implements SopWxLogsTaskService {
    private static final String APP_LINK_PREFIX = "/appcourse/pages/course/learning?course=";
    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 appActivitlLink = "/pages_course/activity.html?link=";
    // 注册
    private static final String registeredRealLink = "/pages_course/register.html?link=";
    private static final String h5LiveShortLink = "/pages_course/livingInvite.html?s=";
    private static final String h5miniappLink = "/pages_course/shortLink.html?s=";

//    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");
    private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd 07:00:00");

    // 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 = 500;

    @Autowired
    private IFsCourseLinkService courseLinkService;
    @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 IWxSopLogsService wxSopLogsService;
    @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;
    @Autowired
    private CloudHostProper cloudHostProper;
    @Autowired
    private FsUserMapper fsUserMapper;
    @Autowired
    private LuckyBagCollectRecordMapper luckyBagCollectRecordMapper;
    @Autowired
    private LuckyBagMapper luckyBagMapper;
    @Autowired
    private CompanyMapper companyMapper;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private SysConfigMapper sysConfigMapper;
    @Autowired
    private ApplicationContext applicationContext;


    // Blocking queues with bounded capacity to implement backpressure
    private final BlockingQueue<QwSopLogs> qwSopLogsQueue = new LinkedBlockingQueue<>(20000);
    private final BlockingQueue<WxSopLogs> wxSopLogsQueue = 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);
    private final BlockingQueue<LiveWatchLog> zmLiveWatchQueue = new LinkedBlockingQueue<>(20000);

    // Executors for consumer threads
    private ExecutorService qwSopLogsExecutor;
    private ExecutorService watchLogsExecutor;
    private ExecutorService courseLinkExecutor;
    private ExecutorService courseSopAppLinkExecutor;
    private ExecutorService zmLiveWatchLogExecutor;
    @Autowired
    private IQwGroupChatService qwGroupChatService;
    @Autowired
    private IQwGroupChatUserService qwGroupChatUserService;
    @Autowired
    private ICompanyMiniappService companyMiniappService;
    // Shutdown flags
    private volatile boolean running = true;
    @Autowired
    private QwSopTempMapper qwSopTempMapper;
    @Autowired
    private ICompanyUserService companyUserService;
    @Autowired
    private IQwCompanyService iQwCompanyService;
    @Autowired
    private AsyncCourseWatchFinishService asyncCourseWatchFinishService;
    @Autowired
    private IQwSopTempVoiceService sopTempVoiceService;
    @Autowired
    LiveWatchLogMapper liveWatchLogMapper;
    @Autowired
    private ConfigUtil configUtil;
    @Autowired
    private WxContactMapper wxContactMapper;
    @Autowired
    private CompanyWxAccountMapper companyWxAccountMapper;
    @Autowired
    private CrmCustomerMapper crmCustomerMapper;

    @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;
        });

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

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

    // 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();
        zmLiveWatchLogExecutor.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();
            }
            if (!zmLiveWatchLogExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                zmLiveWatchLogExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            qwSopLogsExecutor.shutdownNow();
            watchLogsExecutor.shutdownNow();
            courseLinkExecutor.shutdownNow();
            courseSopAppLinkExecutor.shutdownNow();
            zmLiveWatchLogExecutor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

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

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

        List<WxSopUserVo> sopUserLogsVos = sopUserLogsMapper.selectWxSopUserLogsListByTime(sopidList);
        if (sopUserLogsVos.isEmpty()) {
            log.info("没有需要处理的 个微SOP 用户日志。");
            return;
        }
        Map<String, List<WxSopUserVo>> sopLogsGroupedById = sopUserLogsVos.stream()
                .collect(Collectors.groupingBy(WxSopUserVo::getSopId));

        // 查询公司关联小程序数据
        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));

        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));

        List<Company> companies = companyMapper.selectCompanyAllList();

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

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

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

        }

        // 等待所有 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<WxSopUserVo> wxSopUserVos, CountDownLatch latch, LocalDateTime currentTime,
                                     CourseConfig config, Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,List<Company> companies) {
        try {
            log.info("当前线程: {}", Thread.currentThread().getName());
            processSopGroup(sopId, wxSopUserVos, currentTime, config, miniMap, companies);
        } catch (Exception e) {
            log.error("处理 SOP ID {} 时发生异常: {}", sopId, e.getMessage(), e);
        } finally {
            latch.countDown();
        }
    }


    private void processSopGroup(String sopId, List<WxSopUserVo> wxSopUserVos, LocalDateTime currentTime, CourseConfig config, Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
                                 List<Company> companies) throws Exception {
        WxSopRuleTimeVO ruleTimeVO = sopMapper.selectWxSopByClickHouseId(sopId);

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

        ruleTimeVO.setTempStatus(qwSopTemp.getStatus());
        ruleTimeVO.setTempGap(qwSopTemp.getGap());

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

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

//        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(ruleTimeVO.getCorpId());

//        if (qwCompany == null) {
//            log.error("SOP ID {} 的 公司信息为空 为空，跳过处理。", sopId);
//            return;
//        }

        CountDownLatch userLogsLatch = new CountDownLatch(wxSopUserVos.size());
        for (WxSopUserVo logVo : wxSopUserVos) {
            processUserLogAsync(logVo, ruleTimeVO, rulesList, userLogsLatch, currentTime, qwCompany.getMiniAppId(),
                    config, miniMap, companies);
        }
        // 等待所有用户日志处理完成
        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(WxSopUserVo logVo, WxSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
                                    CountDownLatch latch, LocalDateTime currentTime,String miniAppId, CourseConfig config,
                                    Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
                                    List<Company> companies) {
        try {
            processUserLog(logVo, ruleTimeVO, tempSettings, currentTime, miniAppId, config, miniMap, companies);
        } catch (Exception e) {
            log.error("处理用户日志 {} 时发生异常: {}", logVo.getId(), e.getMessage(), e);
        } finally {
            latch.countDown();
        }
    }


    private void processUserLog(WxSopUserVo logVo, WxSopRuleTimeVO ruleTimeVO, List<QwSopTempRules> tempSettings,
                                LocalDateTime currentTime, String miniAppId,CourseConfig config, Map<Long, Map<Integer,
                                List<CompanyMiniapp>>> miniMap,List<Company> companies) {
        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 && ObjectUtil.isNotEmpty(ruleTimeVO.getIsAutoSop()) && 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(), day);
                return;
            }

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

            WxContact wxContact = wxContactMapper.selectOne(new LambdaQueryWrapper<WxContact>().eq(WxContact::getCustomerId, logVo.getCustomerId()));
            // 获取客户信息
            CrmCustomer crmCustomer = crmCustomerMapper.selectCrmCustomerById(wxContact.getCustomerId());

            String wxUserId = String.valueOf(wxUserByRedis.getCustomerId()).trim();
            String companyId = String.valueOf(wxUserByRedis.getCompanyId()).trim();
            String companyUserId = String.valueOf(wxContact.getCompanyUserId()).trim();
            Integer sendMsgType = null;
            String welcomeText = "hello";

            if (StringUtil.strIsNullOrEmpty(companyUserId) || StringUtil.strIsNullOrEmpty(companyId) || "null".equals(companyUserId)) {
                log.error("员工未绑定销售账号或公司，跳过处理:" + wxUserId);
                return;
            }


            CompanyUser companyUser = companyUserService.selectCompanyUserByIdForRedis(Long.valueOf(companyUserId));
            if (Objects.nonNull(companyUser)) {
                if (!StringUtil.strIsNullOrEmpty(companyUser.getDomain())) {
                    logVo.setDomain(companyUser.getDomain().trim());
                } else {
                    logVo.setDomain(config.getRealLinkDomainName().trim());
                }
            } else {
                logVo.setDomain(config.getRealLinkDomainName().trim());
            }

            // 先算好 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++;
                }

                // 重新拿新的 “天” 的 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());

                        WxSopUserInfo wxSopUserInfo = new WxSopUserInfo();
                        wxSopUserInfo.setSopId(Long.valueOf(logVo.getSopId()));
                        wxSopUserInfo.setSopUserId(Long.valueOf(logVo.getId()));
                        wxSopUserInfo.setCustomerId(Long.valueOf(logVo.getCustomerId()));

                        List<WxSopUserInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectWxSopUserLogsInfoList(wxSopUserInfo);
                        insertSopUserLogs(sopUserLogsInfos, logVo, sendTime, ruleTimeVO, content, wxUserId,
                                companyUserId, companyId, welcomeText, wxUserByRedis.getCustomerName(),
                                miniAppId, config, miniMap, sendMsgType, companies,crmCustomer);
                    }
                } 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 && e.getTime() != null).collect(Collectors.toList());
        AtomicInteger i = new AtomicInteger();
        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());
            content.setIsAtAll(e.getIsAtAll());
            return content;
        }).sorted(Comparator.comparing(e -> LocalTime.parse(e.getTime() + ":00"))).peek(e -> e.setIndex(i.getAndIncrement())).collect(Collectors.toList());
    }

    //消息处理
    private void insertSopUserLogs(List<WxSopUserInfo> sopUserLogsInfos, WxSopUserVo logVo, Date sendTime,
                                   WxSopRuleTimeVO ruleTimeVO, QwSopTempSetting.Content content,
                                   String wxUserId, String companyUserId, String companyId, String welcomeText, String qwUserName,
                                   String miniAppId, CourseConfig config,Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap, Integer sendMsgType,
                                   List<Company> companies,CrmCustomer crmCustomer) {
        String formattedSendTime = sendTime.toInstant()
                .atZone(ZoneId.systemDefault())
                .format(DATE_TIME_FORMATTER);
        int type = content.getType();
        Long courseId = content.getCourseId();
        Long videoId = content.getVideoId();
        Long liveId = content.getLiveId();
        Integer isOfficial = content.getIsOfficial() != null ? Integer.valueOf(content.getIsOfficial()) : 0;


        // 发送语音 start
        if (content.getSetting() == null) {
            return;
        }
        List<QwSopTempSetting.Content.Setting> setting = content.getSetting().stream().filter(e -> "7".equals(e.getContentType()) || "16".equals(e.getContentType())).collect(Collectors.toList());
        if (!setting.isEmpty()) {
            List<String> valuesList = PubFun.listToNewList(setting, QwSopTempSetting.Content.Setting::getValue);
            if (valuesList != null && !valuesList.isEmpty()) {
                try {
                    List<QwSopTempVoice> voiceList = qwSopTempVoiceService.getVoiceByText(Long.parseLong(companyUserId), valuesList);
                    if (voiceList != null && !voiceList.isEmpty()) {
                        Map<String, QwSopTempVoice> collect = voiceList.stream().collect(Collectors.toMap(QwSopTempVoice::getVoiceTxt, e -> e));

                        setting.parallelStream().forEach(st -> {
                            QwSopTempVoice voice = collect.get(st.getValue());
                            if (voice == null || voice.getVoiceUrl() == null) {
                                return;
                            }
                            // 企微语音
                            if ("7".equals(st.getContentType())) {
                                st.setVoiceUrl(voice.getVoiceUrl());
                                st.setVoiceDuration(voice.getDuration() + "");
                            }
                            // app语音
                            else if ("16".equals(st.getContentType())) {
                                st.setVoiceUrl(voice.getUserVoiceUrl());
                                st.setVoiceDuration(voice.getDuration() + "");
                            }
                        });
                    }
                } catch (NumberFormatException e) {
                    throw new RuntimeException(e);
                }
            }
        }
//        // 发送语音 end
        if (content.getType() == 5) {
            sopAddTag(logVo, content, sendTime);
        }

        //当语音模板的qw_sop_temp_voice中无对应语音，就不生成qw_sop_logs记录
        if (content.getType() == 7 && content.getSetting() != null && !content.getSetting().isEmpty()) {
            if (content.getSetting().get(0).getVoiceUrl() == null) {
                return;
            }
        }

        // 处理每个 externalContactId
        sopUserLogsInfos.forEach(contactId -> {
            try {
//                String externalId = contactId.getExternalId().toString();
//                String externalUserName = contactId.getExternalUserName();
                Long customerId = contactId.getCustomerId();

                String customerName = crmCustomer.getCustomerName();
//                String fsUserId = miniMap.get(ruleTimeVO.getCompanyId()).get(0).get(0).getAppId();
                Long fsUserId = null;
                Integer grade = contactId.getGrade();
                WxSopLogs wxSopLogs = createWxBaseLog(formattedSendTime, logVo, ruleTimeVO, null, customerName, fsUserId, isOfficial, customerId, contactId.getIsDaysNotStudy());
                handleLogBasedOnType(wxSopLogs, content, logVo, sendTime, courseId, videoId,
                        type, wxUserId, companyUserId, companyId, String.valueOf(customerId), welcomeText, qwUserName, fsUserId, false, miniAppId,
                        null, config, miniMap, grade, sendMsgType, companies, liveId);
            } catch (Exception e) {
                log.error("处理 externalContactId {} 时发生异常: {}", contactId, e.getMessage(), e);
            }
        });
//        // 处理每个 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(WxSopUserVo 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 WxSopLogs createWxBaseLog(String formattedSendTime, WxSopUserVo logVo,
                                      WxSopRuleTimeVO ruleTimeVO, String externalContactId,
                                      String customerName, Long fsUserId, Integer isOfficial,
                                      Long customerId, Integer isDaysNotStudy) {
        WxSopLogs wxSopLogs = new WxSopLogs();

        // 基础信息
        wxSopLogs.setSendTime(formattedSendTime);
        wxSopLogs.setAccountId(Long.valueOf(logVo.getCustomerId())); // 个微账号ID
        wxSopLogs.setType(logVo.getType());
        wxSopLogs.setFsUserId(fsUserId);
        wxSopLogs.setWxContactId(customerId);
        wxSopLogs.setWxContactName(customerName);


        // 发送状态设置
        if (isOfficial != 1 && Integer.valueOf(1).equals(isDaysNotStudy)) {
            wxSopLogs.setSendStatus(5); // 5-消息作废
            wxSopLogs.setRemark("E级客户不发送");
        } else {
            wxSopLogs.setSendStatus(3); // 3-待发送
        }

        // 发送类型设置
        if (isOfficial == 1) {
//            if (logVo.getIsSampSend() == 1) {
//                if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
//                    wxSopLogs.setSendType(2); // 2-单链补发
//                    wxSopLogs.setRemark("未绑定小程序用户,单链补发");
//                    // 时间设置成固定8点
//                    LocalDateTime dateTime = LocalDateTime.parse(formattedSendTime, DATE_TIME_FORMATTER);
//                    wxSopLogs.setSendTime(OUTPUT_FORMATTER.format(dateTime));
//                } else {
//                    wxSopLogs.setSendType(1); // 1-正常发送
//                }
//            } else {
                wxSopLogs.setSendType(1);
//            }
        } else if (isOfficial == 0) {
//            wxSopLogs.setSendType(ruleTimeVO.getSendType() == 1 ? 2 : ruleTimeVO.getSendType());
            wxSopLogs.setSendType(1);
        } else {
            wxSopLogs.setSendType(ruleTimeVO.getSendType());
        }

        // 任务相关信息
        wxSopLogs.setSopId(Long.valueOf(logVo.getSopId()));
        wxSopLogs.setSopUserId(Long.valueOf(logVo.getId())); // 对应wx_sop_logs的sop_user_id字段

        // 发送排序（使用开始时间去除横线后的数值）
        wxSopLogs.setSendSort(Integer.valueOf(logVo.getStartTime().replaceAll("-", "")));

        // 小程序用户ID
        wxSopLogs.setFsUserId(fsUserId);

        // 生成类型（默认为0自动生成）
        wxSopLogs.setGenerateType(0);

        // 备注信息（如果有额外需要记录的信息）
        // wxSopLogs.setSendRemark(""); // 如果有需要可以设置

        return wxSopLogs;
    }

    private QwSopLogs createBaseLog(String formattedSendTime, SopUserLogsVo logVo,
                                    WxSopRuleTimeVO ruleTimeVO, String externalContactId,
                                    String externalUserName, Long fsUserId, Integer isOfficial,
                                    Long externalId, Integer isDaysNotStudy) {
        QwSopLogs sopLogs = new QwSopLogs();
        sopLogs.setSendTime(formattedSendTime);
        sopLogs.setQwUserid(logVo.getQwUserId());
        sopLogs.setCorpId(logVo.getCorpId());
        sopLogs.setLogType(ruleTimeVO.getType());
        sopLogs.setTakeRecords(0);

        if (isOfficial != 1 && Integer.valueOf(1).equals(isDaysNotStudy)) {
            sopLogs.setSendStatus(5L);
            sopLogs.setRemark("E级客户不发送");
        } else {
            sopLogs.setSendStatus(3L);
        }

        sopLogs.setReceivingStatus(0L);

        if (isOfficial == 1) {

            if (logVo.getIsSampSend() == 1) {
                if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
                    sopLogs.setSendType(2);
                    sopLogs.setRemark("未绑定小程序用户,单链补发");
                    //时间设置成固定8点
                    LocalDateTime dateTime = LocalDateTime.parse(formattedSendTime, DATE_TIME_FORMATTER);
                    sopLogs.setSendTime(OUTPUT_FORMATTER.format(dateTime));
                } else {
                    sopLogs.setSendType(1);
                }

            } else {
                if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
                    sopLogs.setTakeRecords(1);
                    sopLogs.setSendType(1);
                } else {
                    sopLogs.setSendType(1);
                }
            }

        } else if (isOfficial == 0) {
            sopLogs.setSendType(ruleTimeVO.getSendType() == 1 ? 2 : ruleTimeVO.getSendType());
        } else {
            sopLogs.setSendType(ruleTimeVO.getSendType());
        }


        String[] userKey = logVo.getUserId().split("\\|");
        sopLogs.setCompanyId(Long.valueOf(userKey[2].trim()));
        if (StringUtils.isNotEmpty(userKey[0].trim())) {
            sopLogs.setQwUserKey(Long.valueOf(userKey[0].trim()));
        }
        sopLogs.setSopId(logVo.getSopId());
        sopLogs.setSort(Integer.valueOf(logVo.getStartTime().replaceAll("-", "")));
        sopLogs.setExternalUserId(externalContactId);
        sopLogs.setExternalId(externalId);
        sopLogs.setExternalUserName(externalUserName);
        sopLogs.setFsUserId(fsUserId);
        sopLogs.setUserLogsId(logVo.getId());

        if (ObjectUtil.isNotEmpty(logVo.getActualQwId())) {
            sopLogs.setQwUserKey(logVo.getActualQwId());
        }
        return sopLogs;
    }

    private void handleLogBasedOnType(WxSopLogs sopLogs, QwSopTempSetting.Content content,
                                      WxSopUserVo logVo, Date sendTime, Long courseId, Long videoId, int type, String qwUserId,
                                      String companyUserId, String companyId, String externalId, String welcomeText,
                                      String qwUserName, Long fsUserId, boolean isGroupChat, String miniAppId,
                                      QwGroupChat groupChat, CourseConfig config,
                                      Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
                                      Integer grade, Integer sendMsgType, List<Company> companies, Long liveId) {
        switch (type) {
            case 1:
                handleNormalMessage(sopLogs, content, companyUserId, companyId, isGroupChat, qwUserId, groupChat, externalId, logVo,sendTime);
                break;
            case 2:
                handleCourseMessage(sopLogs, content, logVo, sendTime, courseId, videoId,
                        qwUserId, companyUserId, companyId, externalId, welcomeText, qwUserName, fsUserId,
                        isGroupChat, miniAppId, groupChat, config, miniMap, grade, sendMsgType, companies);
                break;
            case 3:
                handleOrderMessage(sopLogs, content);
                break;
            case 4:
//                handleAIMessage(sopLogs, content);
                break;
            case 5:
//                handleTagMessage(sopLogs, content);
                break;
            case 7:
                handleVoiceMessage(sopLogs, content, companyUserId);
                break;
            //直播间发送类型
            case 20:
                handleLiveMessage(sopLogs, content, companyUserId, companyId, isGroupChat, qwUserId, groupChat, externalId, logVo, liveId);
                break;
            case 21:
                handleGroupNoticeMessage(sopLogs, content, isGroupChat);
                break;
            default:
                log.error("未知的消息类型 {}，跳过处理。", type);
                break;
        }
    }

    /**
     * 处理群公告消息
     * @param sopLogs 日志对象
     * @param content 内容对象
     * @param isGroupChat 是否为群聊
     */
    private void handleGroupNoticeMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content, boolean isGroupChat) {
        // 群公告只能发给群聊
        if (!isGroupChat) {
            log.warn("群公告只能发给群聊，跳过处理");
            return;
        }

        // 设置发送类型为21（群公告）
        sopLogs.setSendType(21);
        sopLogs.setContentJson(JSON.toJSONString(content));
        enqueueQwSopLogs(sopLogs);
    }

    private void handleVoiceMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId) {
        sopLogs.setContentJson(JSON.toJSONString(content));
        enqueueQwSopLogs(sopLogs);
    }

    private void handleNormalMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId, String companyId,
                                     boolean isGroupChat, String qwUserId, QwGroupChat groupChat, String externalId, WxSopUserVo logVo, Date sendTime) {

        // 深拷贝 Content 对象，避免使用 JSON
        QwSopTempSetting.Content clonedContent = deepCopyContent(content);
        if (clonedContent == null) {
//            log.error("Failed to clone content, skipping handleCourseMessage.");
            return;
        }

        List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
        List<QwSopTempSetting.Content.Setting> settingAll = new ArrayList<>();
        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":break;
                //图片
                case "2":
                //小程序
                case "4":
                //文件
                case "5":
                case "6":
                case "7":
                case "8":
                case "9":
                //app语音
                case "16":
                //app文本
                case "15":
                //群公告
                case "11":
                //直播小程序
                case "12":
                    //直播发送类型
                    sopLogs.setSendType(20);
                    clonedContent.setLiveId(setting.getLiveId());
                    String sortLiveLink;
                    sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId() + "&corpId=" + logVo.getCorpId() + "&qwUserId=" + qwUserId;
                    String json = configService.selectConfigByKey("his.config");
                    FSSysConfig sysConfig = JSON.parseObject(json, FSSysConfig.class);
                    if (isGroupChat) {
                        try {
                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
                                if (vo != null && vo.getId() != null) {
                                    sopLogs.setFsUserId(vo.getFsUserId());
                                    //写入直播待看课记录
                                    createLiveWatchLogAndEnQueue(companyId, companyUserId, vo.getId().toString(), setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId, logVo.getCorpId());
                                }
                            });
                            sortLiveLink += "&chatId=" + groupChat.getChatId();
                        } catch (Exception e) {
                            log.error("直播小程序群聊新增报错，{}", e.getMessage(), e);
                        }
                    } else {
                        try {
                            createLiveWatchLogAndEnQueue(companyId, companyUserId, externalId, setting.getLiveId(), sysConfig.getAppId(), 1, qwUserId, logVo.getCorpId());
                            sortLiveLink += "&externalId=" + externalId;
                        } catch (Exception e) {
                            log.error("直播小程序个人新增报错，{}", e.getMessage(), e);
                        }
                    }

                    String miniprogramLiveTitle = setting.getMiniprogramTitle();
                    int maxLiveLength = 17;
                    setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
                    setting.setMiniprogramAppid(sysConfig.getAppId());
                    setting.setMiniprogramPage(sortLiveLink);
                    setting.setContentType("4");
                    try {
                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
                    } catch (Exception e) {
                        log.error("赋值-小程序封面地址失败-" + e);
                    }

                    break;
                case "13":
                    try {
                        if (sopLogs.getFsUserId() != null && !Objects.equals(0L, sopLogs.getFsUserId())) {
                            sopLogs.setSendStatus(5);
                            sopLogs.setReceivingStatus(0);
                            sopLogs.setRemark("已经注册过的客户不发送");
                        }
                        if (ObjectUtil.isNotEmpty(setting.getValue())) {
                            QwSopTempSetting.Content.Setting setting1 =new QwSopTempSetting.Content.Setting();
                            setting1.setContentType("1");
                            setting1.setValue(setting.getValue());
                            settingAll.add(setting1);
                        }

                        String link;
                        if (isGroupChat && groupChat != null) {
                            link = createRegisteredGroupLinkByMiniApp(setting, logVo, sendTime,
                                    qwUserId, Long.parseLong(companyUserId), companyId, logVo.getChatId());
                        }else {
                            link = createRegisteredLinkByMiniApp(setting, logVo, sendTime,
                                    qwUserId, companyUserId, companyId, externalId, sopLogs.getFsUserId());
                        }
                        //算主备小程序
                        String luckyjson1 = configService.selectConfigByKey("luckyBag.config");
                        Map<String, Object> luckyBagConfig1 = JSON.parseObject(luckyjson1, Map.class);
                        String finalAppId = String.valueOf(luckyBagConfig1.get("appId"));
                        /*getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);

                        if (StringUtil.strIsNullOrEmpty(finalAppId)) {
                            finalAppId = miniAppId;
                        }*/

                        if (!StringUtil.strIsNullOrEmpty(finalAppId)) {
                            setting.setMiniprogramAppid(finalAppId);
                        } else {
                            log.error("公司的小程序id为空：采用了前端传的固定值" + sopLogs.getSopId());
                        }

                        setting.setMiniprogramTitle("点击注册");
                        setting.setMiniprogramPage(link);
//               try {
//                   item.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
//               } catch (Exception e) {
//                   log.error("赋值-小程序封面地址失败-" + e);
//               }
                    } catch (Exception e) {
                        log.error("任务模板发送注册页面失败：" + e);
                    }

                    break;
                //福袋
                case "14":
                    try {
                        // 1. 检查必要对象是否为空
                        if (sopLogs == null) {
                            log.warn("sopLogs为空，跳过福袋处理");
                        }
                        // 查询福袋信息
                        Long fsUserId = sopLogs.getFsUserId();

                        LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(setting.getLuckyBagId());
                        if(ObjectUtil.isNotEmpty(luckyBag)&&luckyBag.getDataStatus().equals("0")){
                            setSopLogsStatus(sopLogs, 5, 0, "福袋配置被禁用");
                        }else if (ObjectUtil.isNotEmpty(fsUserId)){

                            // 2. 获取系统配置
                            SysConfig luckyBagConfig = sysConfigMapper.selectConfigByConfigKey("luckyBag.config");
                            if (ObjectUtil.isEmpty(luckyBagConfig) || StringUtil.strIsNullOrEmpty(luckyBagConfig.getConfigValue())) {
//                                log.warn("福袋配置为空，设置发送状态为失败");
                                setSopLogsStatus(sopLogs, 5, 0, "福袋配置不存在");
                            }

                            // 3. 解析配置
                            JSONObject jsonObject = null;
                            try {
                                jsonObject = JSON.parseObject(luckyBagConfig.getConfigValue());
                            } catch (Exception e) {
//                                log.error("解析福袋配置JSON失败: {}", luckyBagConfig.getConfigValue(), e);
                                setSopLogsStatus(sopLogs, 5, 0, "福袋配置格式错误");
                            }

                            // 4. 获取周限制次数
                            Integer count = null;
                            try {
                                Object weekLimitObj = jsonObject.get("weekLimit");
                                if (weekLimitObj != null) {
                                    count = Integer.valueOf(weekLimitObj.toString());
                                }
                            } catch (NumberFormatException e) {
                                log.error("周限制次数格式错误: {}", jsonObject.get("weekLimit"), e);
                            }

                            if (count == null) {
//                                log.warn("周限制次数配置为空");
                                setSopLogsStatus(sopLogs, 5, 0, "周限制次数配置错误");
                            }


                            // 5. 查询用户福袋收集记录（带计数缓存）
                            LuckyBagCollectRecord luckyBagCollectRecord = new LuckyBagCollectRecord();
                            luckyBagCollectRecord.setUserId(fsUserId);
// 动态计算时间范围
                            LocalDate endDate = LocalDate.now();
                            LocalDate startDate = endDate.minusDays(6); // 包含今天

                            Map<String, Object> params = new HashMap<>();
                            params.put("beginSendTime", startDate.toString());
                            params.put("endSendTime", endDate.toString());
                            luckyBagCollectRecord.setParams(params);
                            luckyBagCollectRecord.setCollectType("1");

                            List<LuckyBagCollectRecord> luckyBagCollectRecords;
                            int recordCount;

                            try {
                                // 生成缓存key
                                String dateRangeKey = startDate.toString() + "_to_" + endDate.toString();
                                String countCacheKey = "luckyBag:user:" + fsUserId + ":recent7days:" + dateRangeKey + ":count";

                                // 1. 先尝试从计数缓存获取
                                Object cachedCount = redisCache.getCacheObject(countCacheKey);
                                if (cachedCount != null && cachedCount instanceof Integer) {
                                    recordCount = (Integer) cachedCount;
//                                    log.debug("福袋计数缓存命中，userId: {}，次数: {}", fsUserId, recordCount);

                                    // 如果只需要判断是否超限，且已超限，直接返回
                                    if (recordCount >= count) {
//                                        log.info("用户福袋次数已达上限（计数缓存）, userId: {}, 当前次数: {}, 限制次数: {}",
//                                                fsUserId, recordCount, count);
                                        setSopLogsStatus(sopLogs, 5, 0, "超过福袋发放次数");
                                        luckyBagCollectRecords = Collections.emptyList();
                                        // 可以直接返回，不需要查询完整记录
                                        // return; // 根据你的流程决定是否返回
                                    } else {
                                        // 未超限，查询完整记录
                                        String recordsCacheKey = "luckyBag:user:" + fsUserId + ":recent7days:" + dateRangeKey + ":records";
                                        luckyBagCollectRecords = getCachedOrQueryRecords(fsUserId, recordsCacheKey, luckyBagCollectRecord);
                                    }
                                } else {
                                    // 计数缓存未命中，查询完整记录
                                    String recordsCacheKey = "luckyBag:user:" + fsUserId + ":recent7days:" + dateRangeKey + ":records";
                                    luckyBagCollectRecords = getCachedOrQueryRecords(fsUserId, recordsCacheKey, luckyBagCollectRecord);
                                    recordCount = luckyBagCollectRecords != null ? luckyBagCollectRecords.size() : 0;

                                    // 更新计数缓存
                                    cacheUserCount(fsUserId, countCacheKey, recordCount);
                                }
                            } catch (Exception e) {
                                log.error("查询用户福袋记录失败, userId: {}", fsUserId, e);
                                luckyBagCollectRecords = Collections.emptyList();
                                recordCount = 0;
                            }


//                            // 5. 查询用户福袋收集记录
//                            LuckyBagCollectRecord luckyBagCollectRecord = new LuckyBagCollectRecord();
//                            luckyBagCollectRecord.setUserId(fsUserId);
//                            // 动态计算时间范围
//                            LocalDate endDate = LocalDate.now();
//                            LocalDate startDate = endDate.minusDays(6); // 包含今天
//
//                            Map<String, Object> params = new HashMap<>();
//                            params.put("beginSendTime", startDate.toString());
//                            params.put("endSendTime", endDate.toString());
//                            luckyBagCollectRecord.setParams(params);
//                            luckyBagCollectRecord.setCollectType("1");
//                            List<LuckyBagCollectRecord> luckyBagCollectRecords;
//                            try {
//                                luckyBagCollectRecords = luckyBagCollectRecordMapper.selectLuckyBagCollectRecordList(luckyBagCollectRecord);
//                            } catch (Exception e) {
//                                log.error("查询用户福袋记录失败, userId: {}", fsUserId, e);
//                                luckyBagCollectRecords = Collections.emptyList();
//                            }

                            // 6. 检查次数限制
                            if (recordCount >= count) {
//                                log.info("用户福袋次数已达上限, userId: {}, 当前次数: {}, 限制次数: {}", fsUserId, recordCount, count);
                                setSopLogsStatus(sopLogs, 5, 0, "超过福袋发放次数");
                            }
                        }

                        // 7. 生成活动链接
                        String link;

                        String luckyjson1 = configService.selectConfigByKey("luckyBag.config");
                        Map<String, Object> luckyBagConfig1 = JSON.parseObject(luckyjson1, Map.class);
                        String finalAppId = String.valueOf(luckyBagConfig1.get("appId"));
                    /*    try {
                            finalAppId = getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);
                            if (StringUtil.strIsNullOrEmpty(finalAppId)) {
                                finalAppId = miniAppId;
                            }
                        } catch (Exception e) {
                            log.error("获取小程序ID失败，使用默认值", e);
                            finalAppId = miniAppId;
                        }

                        if (StringUtil.strIsNullOrEmpty(finalAppId)) {
                            log.error("公司的小程序id为空，sopId: {}", sopLogs.getSopId());
                            setSopLogsStatus(sopLogs, 5L, 0L, "小程序配置错误");
                            return;
                        }*/

                        // 10. 设置小程序参数
                        setting.setMiniprogramAppid(finalAppId);
                        setting.setMiniprogramTitle("福袋发放");

//                        log.info("福袋配置成功，userId: {}, appId: {}", fsUserId, finalAppId);

                    } catch (Exception e) {
                        log.error("任务模板福袋发放失败", e);
                        // 确保在最终异常时也设置状态
                        if (sopLogs != null) {
                            setSopLogsStatus(sopLogs, 5, 0, "福袋发放系统异常");
                        }
                    }
                    break;
                //直播h5跳转卡片
                case "18":
                    //直播h5跳转短链
                case "19":
                    String corpId = logVo.getCorpId();
                    String shortH5Link = createH5LiveShortLink(setting, corpId,
                            qwUserId, companyUserId, companyId);
                    shortH5Link = shortH5Link.substring(0, shortH5Link.length() - 1);

                    sopLogs.setSendType(Integer.valueOf(setting.getContentType()));
                    clonedContent.setLiveId(setting.getLiveId());
                    json = configService.selectConfigByKey("his.config");
                    sysConfig= JSON.parseObject(json,FSSysConfig.class);

                    if(isGroupChat){
                        try{
                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
                                if (vo != null && vo.getId() != null) {
                                    sopLogs.setFsUserId(vo.getFsUserId());
                                    //写入直播待看课记录
                                    createLiveWatchLogAndEnQueue(companyId,companyUserId,vo.getId().toString(), setting.getLiveId(),sysConfig.getAppId(),2,qwUserId,logVo.getCorpId());
                                }
                            });
                            shortH5Link += ",\"chatId\":\"" + groupChat.getChatId() + "\"";
                        }catch(Exception e){
                            log.error("直播H5群聊新增报错，{}", e.getMessage(),e);
                        }
                    }else{
                        try{
                            createLiveWatchLogAndEnQueue(companyId,companyUserId,externalId, setting.getLiveId(),sysConfig.getAppId(),1,qwUserId,logVo.getCorpId());
                            shortH5Link += ",\"externalId\":\"" + externalId + "\"";
                        }catch(Exception e){
                            log.error("直播H5个人新增报错，{}", e.getMessage(),e);
                        }
                    }

                    shortH5Link += "}";
                    miniprogramLiveTitle = setting.getMiniprogramTitle();
                    maxLiveLength = 17;
                    setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
                    setting.setMiniprogramAppid(sysConfig.getAppId());
                    setting.setMiniprogramPage(shortH5Link);


                    try {
                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
                    } catch (Exception e) {
                        log.error("赋值-小程序封面地址失败-" + e);
                    }
                    break;
                    //TODO 其他消息类型继续添加case
                default:
                    break;
            }
        }
        sopLogs.setContentJson(JSON.toJSONString(clonedContent));
//        sopLogs.setContentJson(JSON.toJSONString(content));
        enqueueQwSopLogs(sopLogs);
    }

    private String createRegisteredLinkByMiniApp(QwSopTempSetting.Content.Setting setting, WxSopUserVo logVo, Date sendTime,
                                                  String qwUserId,
                                                 String companyUserId, String companyId, String externalId, Long fsUserId) {
        // 获取缓存的配置
        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!=null?Long.parseLong(qwUserId):null);
        link.setCompanyUserId(Long.parseLong(companyUserId));
        //link.setCorpId(logVo.getCorpId());
        link.setQwExternalId(Long.parseLong(externalId));
        link.setUNo(UUID.randomUUID().toString());
        link.setLinkType(3);

        String randomString = generateRandomStringWithLock();
        if (StringUtil.strIsNullOrEmpty(randomString)) {
            link.setLink(UUID.randomUUID().toString().replace("-", ""));
        } else {
            link.setLink(randomString);
        }

        link.setCreateTime(sendTime);

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

        String courseJson = JSON.toJSONString(courseMap);
        String realLinkFull = registeredRealLink + 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();
    }


    public String createRegisteredGroupLinkByMiniApp(QwSopTempSetting.Content.Setting setting, WxSopUserVo logVo, Date sendTime,
                                                      String qwUserId,
                                                     Long companyUserId, String companyId, String chatId) {
        // 获取缓存的配置
        CourseConfig config;
        synchronized (configLock) {
            config = cachedCourseConfig;
        }

        if (config == null) {
//            log.error("CourseConfig is not loaded.");
            return "";
        }
        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(Long.parseLong(companyId));
        link.setQwUserId(qwUserId!=null?Long.parseLong(qwUserId): null);
        link.setCompanyUserId(companyUserId);
//        link.setVideoId(null);
        //link.setCorpId(logVo.getCorpId());
//        link.setCourseId(null);
        link.setChatId(chatId);
        link.setIsRoom(1);
        link.setLinkType(3);
        link.setUNo(UUID.randomUUID().toString());

        String randomString = generateRandomStringWithLock();
        if (StringUtil.strIsNullOrEmpty(randomString)) {
            link.setLink(UUID.randomUUID().toString().replace("-", ""));
        } else {
            link.setLink(randomString);
        }

        link.setCreateTime(sendTime);

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

        String courseJson = JSON.toJSONString(courseMap);
        String realLinkFull = registeredRealLink + 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();
    }

    /**
     * 处理直播消息
     */
    public void handleLiveMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content, String companyUserId, String companyId,
                                  boolean isGroupChat, String qwUserId, QwGroupChat groupChat, String externalId, WxSopUserVo logVo, Long liveId){
        // 深拷贝 Content 对象，避免使用 JSON
        QwSopTempSetting.Content clonedContent = deepCopyContent(content);
        if (clonedContent == null) {
//            log.error("Failed to clone content, skipping handleCourseMessage.");
            return;
        }
        clonedContent.setLiveId(liveId);
        List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
        if (settings == null || settings.isEmpty()) {
//            log.error("Cloned content settings are empty, skipping.");
            return;
        }

        //直播发送类型
        sopLogs.setSendType(20);

        // 顺序处理每个 Setting，避免过多的并行导致线程开销
        for (QwSopTempSetting.Content.Setting setting : settings) {
            switch (setting.getContentType()) {
                //直播小程序单独
                case "12":
                    clonedContent.setLiveId(setting.getLiveId());
                    String sortLiveLink;
                    sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId() + "&corpId=" + logVo.getCorpId() + "&qwUserId=" + qwUserId;
                    String json = configService.selectConfigByKey("his.config");
                    FSSysConfig sysConfig = JSON.parseObject(json, FSSysConfig.class);
                    if (isGroupChat) {
                        try {
                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
                                if (vo != null && vo.getId() != null) {
                                    sopLogs.setFsUserId(vo.getFsUserId());
                                    //写入直播待看课记录
                                    createLiveWatchLogAndEnQueue(companyId, companyUserId, vo.getId().toString(), setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId, logVo.getCorpId());
                                }
                            });
                            sortLiveLink += "&chatId=" + groupChat.getChatId();
                        } catch (Exception e) {
                            log.error("直播小程序群聊新增报错，{}", e.getMessage(), e);
                        }
                    } else {
                        try {
                            createLiveWatchLogAndEnQueue(companyId, companyUserId, externalId, setting.getLiveId(), sysConfig.getAppId(), 1, qwUserId, logVo.getCorpId());
                            sortLiveLink += "&externalId=" + externalId;
                        } catch (Exception e) {
                            log.error("直播小程序个人新增报错，{}", e.getMessage(), e);
                        }
                    }

                    String miniprogramLiveTitle = setting.getMiniprogramTitle();
                    int maxLiveLength = 17;
                    setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
                    setting.setMiniprogramAppid(sysConfig.getAppId());
                    setting.setMiniprogramPage(sortLiveLink);
                    setting.setContentType("4");
                    try {
                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
                    } catch (Exception e) {
                        log.error("赋值-小程序封面地址失败-" + e);
                    }

                    break;
                //直播h5跳转卡片
                case "18":
                    //直播h5跳转短链
                case "19":
                    String corpId = logVo.getCorpId();
                    String shortH5Link = createH5LiveShortLink(setting, corpId,
                            qwUserId, companyUserId, companyId);

                    sopLogs.setSendType(Integer.valueOf(setting.getContentType()));
                    clonedContent.setLiveId(setting.getLiveId());
                    json = configService.selectConfigByKey("his.config");
                    sysConfig= JSON.parseObject(json,FSSysConfig.class);

                    if(isGroupChat){
                        try{
                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
                                if (vo != null && vo.getId() != null) {
                                    sopLogs.setFsUserId(vo.getFsUserId());
                                    //写入直播待看课记录
                                    createLiveWatchLogAndEnQueue(companyId,companyUserId,vo.getId().toString(), setting.getLiveId(),sysConfig.getAppId(),2,qwUserId,logVo.getCorpId());
                                }
                            });
                            shortH5Link += "&chatId=" + groupChat.getChatId();
                        }catch(Exception e){
                            log.error("直播小程序群聊新增报错，{}", e.getMessage(),e);
                        }
                    }else{
                        try{
                            createLiveWatchLogAndEnQueue(companyId,companyUserId,externalId, setting.getLiveId(),sysConfig.getAppId(),1,qwUserId,logVo.getCorpId());
                            shortH5Link += "&externalId=" + externalId;
                        }catch(Exception e){
                            log.error("直播小程序个人新增报错，{}", e.getMessage(),e);
                        }
                    }

                    miniprogramLiveTitle = setting.getMiniprogramTitle();
                    maxLiveLength = 17;
                    setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
                    setting.setMiniprogramAppid(sysConfig.getAppId());
                    setting.setMiniprogramPage(shortH5Link);

                    try {
                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
                    } catch (Exception e) {
                        log.error("赋值-小程序封面地址失败-" + e);
                    }
                    break;
                default:
                    break;
            }
        }
        sopLogs.setContentJson(JSON.toJSONString(clonedContent));

        enqueueQwSopLogs(sopLogs);
    }

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

    private void handleCourseMessage(WxSopLogs sopLogs, QwSopTempSetting.Content content,
                                     WxSopUserVo logVo, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId,
                                     String companyId, String externalId, String welcomeText, String qwUserName,
                                     Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat, CourseConfig config, Map<Long,
            Map<Integer, List<CompanyMiniapp>>> miniMap, Integer grade, Integer sendMsgType,
                                     List<Company> companies) {
        QwExternalContact contact = null;
        if (logVo.getExternalId() != null) {
            contact = qwExternalContactMapper.selectById(logVo.getExternalId());
        }
        // 深拷贝 Content 对象，避免使用 JSON
        QwSopTempSetting.Content clonedContent = deepCopyContent(content);
        if (clonedContent == null) {
//            log.error("Failed to clone content, skipping handleCourseMessage.");
            return;
        }
        String isOfficial = clonedContent.getIsOfficial();

        List<QwSopTempSetting.Content.Setting> settings = clonedContent.getSetting();
        if (settings == null || settings.isEmpty()) {return;}
        //如果是@所有人，就添加
        if (1 == content.getIsAtAll()) {
            QwSopTempSetting.Content.Setting atMsg = new QwSopTempSetting.Content.Setting();
            atMsg.setContentType("99");
            settings.add(atMsg);
        }
        // 顺序处理每个 Setting，避免过多的并行导致线程开销
        for (QwSopTempSetting.Content.Setting setting : settings) {
            switch (setting.getContentType()) {
                //文字和短链一起
                case "1":
                case "3":
                    if ("1".equals(setting.getContentType())) {
                        String defaultName = "同学";
                        if (contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())) {
                            defaultName = contact.getName();
                        }
                        setting.setValue(setting.getValue()
                                .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(welcomeText) ? "" : welcomeText)
                                .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? defaultName : contact.getStageStatus()));
                    }
//                    }
                    break;
                //小程序单独
                case "4":
                    if (isGroupChat) {
                        try {
                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
                                if (vo != null && vo.getId() != null) {
                                    sopLogs.setFsUserId(vo.getFsUserId());
                                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, vo.getId().toString(), logVo, 2);
                                }
                            });
                        } catch (Exception e) {
                            log.error("群聊创建看课记录失败！", e);
                        }
                    } else {
                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo, 2);
                    }

                    String sortLink = createLinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                            qwUserId, companyUserId, companyId, externalId, isOfficial, sopLogs.getFsUserId(), isGroupChat ? groupChat.getChatId() : null);

                    if (sopLogs.getSendType() == 1) {
                        setting.setMiniprogramAppid(miniAppId);
                    } else {
                        int miniType = getLevel(grade);
                        //算主备小程序
                        String finalAppId = getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);

                        if (StringUtil.strIsNullOrEmpty(finalAppId)) {
                            finalAppId = miniAppId;
                        }

                        setting.setMiniType(miniType);
                        if (!StringUtil.strIsNullOrEmpty(finalAppId)) {
                            setting.setMiniprogramAppid(finalAppId);
                        }

                    }

                    setting.setMiniprogramPage(sortLink.replaceAll("^[\\s\\u2005]+", ""));

                    try {
                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? config.getSidebarImageUrl() : setting.getMiniprogramPicUrl());
                    } catch (Exception e) {
                        log.error("赋值-小程序封面地址失败-" + e);
                    }

                    break;
                //app
                case "9":
                    break;
                //自定义小程序
                case "10":
                    addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo, 2);

                    Optional<Company> matchedCompany = companies.stream()
                            .filter(company -> String.valueOf(company.getCompanyId()).equals(companyId))
                            .findFirst();
                    if (matchedCompany.isPresent()) {
                        Company company = matchedCompany.get();

                        String customMiniAppId = company.getCustomMiniAppId();

                        if (customMiniAppId != null && !customMiniAppId.trim().isEmpty()) {
                            setting.setMiniprogramAppid(customMiniAppId);
                        } else {
                            setting.setMiniprogramAppid("该公司未配置自定义小程序:" + companyId);
                        }
                    } else {
                        setting.setMiniprogramAppid("未找到匹配的公司的自定义小程序:" + companyId);
                    }

                    break;
                //直播小程序单独
                case "12":
                    sopLogs.setSendType(20);
                    clonedContent.setLiveId(setting.getLiveId());
                    String sortLiveLink;
                    sortLiveLink = "/pages_course/living.html?companyId=" + companyId + "&companyUserId=" + companyUserId + "&liveId=" + setting.getLiveId() + "&corpId=" + logVo.getCorpId() + "&qwUserId=" + qwUserId;
                    String json = configService.selectConfigByKey("his.config");
                    FSSysConfig sysConfig = JSON.parseObject(json, FSSysConfig.class);
                    if (isGroupChat) {
                        try {
                            groupChat.getChatUserList().stream().filter(e -> e.getUserList() != null && !e.getUserList().isEmpty()).forEach(e -> {
                                Map<String, GroupUserExternalVo> userMap = PubFun.listToMapByGroupObject(e.getUserList(), GroupUserExternalVo::getUserId);
                                GroupUserExternalVo vo = userMap.get(groupChat.getOwner());
                                if (vo != null && vo.getId() != null) {
                                    sopLogs.setFsUserId(vo.getFsUserId());
                                    //写入直播待看课记录
                                    createLiveWatchLogAndEnQueue(companyId, companyUserId, vo.getId().toString(), setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId, logVo.getCorpId());
                                }
                            });
                            sortLiveLink += "&chatId=" + groupChat.getChatId();
                        } catch (Exception e) {
                            log.error("直播小程序群聊新增报错，{}", e.getMessage(), e);
                        }
                    } else {
                        try {
                            createLiveWatchLogAndEnQueue(companyId, companyUserId, externalId, setting.getLiveId(), sysConfig.getAppId(), 2, qwUserId, logVo.getCorpId());
                            sortLiveLink += "&externalId=" + externalId;
                        } catch (Exception e) {
                            log.error("直播小程序个人新增报错，{}", e.getMessage(), e);
                        }
                    }


                    String miniprogramLiveTitle = setting.getMiniprogramTitle();
                    int maxLiveLength = 17;
                    setting.setMiniprogramTitle(miniprogramLiveTitle.length() > maxLiveLength ? miniprogramLiveTitle.substring(0, maxLiveLength) + "..." : miniprogramLiveTitle);
                    setting.setMiniprogramAppid(sysConfig.getAppId());
                    setting.setMiniprogramPage(sortLiveLink);
                    setting.setContentType("4");
                    try {
                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());
                    } catch (Exception e) {
                        log.error("赋值-小程序封面地址失败-" + e);
                    }

                    break;
                case "14":
                case "15": //app文本
                case "16": //app语音
                    break;
                case "17":
                    try {
                        String sroth5link;
                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo,2);

                        sroth5link = createH5LinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
                                qwUserId, companyUserId, companyId, externalId, isOfficial, sopLogs.getFsUserId());

                        if(sopLogs.getSendType()==1){
                            setting.setMiniprogramAppid(miniAppId);
                        }else {
                            int miniType = getLevel(grade);
                            //算主备小程序
                            String finalAppId = getAppIdFromMiniMap(miniMap, companyId, sendMsgType, grade);

                            if (StringUtil.strIsNullOrEmpty(finalAppId)) {
                                finalAppId = miniAppId;
                            }

                            setting.setMiniType(miniType);
                            if (!StringUtil.strIsNullOrEmpty(finalAppId)) {
                                setting.setMiniprogramAppid(finalAppId);
                            } else {
                                log.error("公司的小程序id为空：采用了前端传的固定值" + sopLogs.getSopId());
                            }

                        }

                        setting.setMiniprogramTitle("邀请链接");
                        setting.setMiniprogramPage(sroth5link);

                        setting.setMiniprogramPicUrl(StringUtil.strIsNullOrEmpty(setting.getMiniprogramPicUrl()) ? "https://cos.his.cdwjyyh.com/fs/20250331/ec2b4e73be8048afbd526124a655ad56.png" : setting.getMiniprogramPicUrl());

                    } catch (Exception e) {
                        log.error("浏览器看课模板解析失败：" + e);
                    }

                    break;
                default:
                    break;
            }

        }
        clonedContent.getSetting().stream().filter(e -> "1".equals(e.getIsBindUrl())).forEach(e -> {
            e.setIsBindUrl("0");
//            e.setLinkDescribe(null);
            e.setLinkUrl(null);
//            e.setLinkImageUrl(null);
        });
        sopLogs.setContentJson(JSON.toJSONString(clonedContent));
        enqueueQwSopLogs(sopLogs);
    }

    /**
     * 创建福袋链接
     *
     * @param st
     * @param sopLogs
     * @param corpId
     * @param sendTime
     * @param courseId
     * @param videoId
     * @param qwUserId
     * @param companyUserId
     * @param companyId
     * @param config
     * @param chatId
     * @return
     */
    public String createActivityLinkByMiniApp(QwSopTempSetting.Content.Setting st, QwSopLogs sopLogs, String corpId, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId, String companyId, CourseConfig config, String chatId) {
        FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId.toString(),
                companyUserId, companyId, null, 3);
        link.setChatId(chatId);
        Date updateTime = createUpdateTime(st, sendTime, config);
        link.setUpdateTime(updateTime);
        FsCourseRealLink courseMap = new FsCourseRealLink();
        BeanUtils.copyProperties(link, courseMap);
        Long businessId = addLuckyBagCollectRecord(st, sopLogs, updateTime, companyUserId, companyId, chatId);
        courseMap.setBusinessId(String.valueOf(businessId));
        st.setBusinessId(String.valueOf(businessId));
        String realLinkFull = appActivitlLink + JSON.toJSONString(courseMap);
        link.setRealLink(realLinkFull);
//        log.error("存入fs_course_link:" + registeredRealLink);
//        log.error("QwSopCourseFinishTempSetting.Setting:{}", st);
        //存短链-
        fsCourseLinkMapper.insertFsCourseLink(link);
        return link.getRealLink();
    }

    /**
     * 增加福袋发放记录、领取记录
     *
     * @param content
     * @param qwSopLogs
     * @param sendTime
     * @param companyUserId
     * @param companyId
     * @param chatId
     */
    private Long addLuckyBagCollectRecord(QwSopTempSetting.Content.Setting content,
                                          QwSopLogs qwSopLogs,
                                          Date sendTime,
                                          String companyUserId,
                                          String companyId,
                                          String chatId) {
        try {
            // 参数校验
            if (content == null || qwSopLogs == null || sendTime == null) {
                log.warn("添加福袋记录失败：必要参数为空 [content:{}, qwSopLogs:{}, sendTime:{}]",
                        content, qwSopLogs, sendTime);
                return null;
            }

            if (StringUtils.isEmpty(companyId) || StringUtils.isEmpty(companyUserId)) {
                log.warn("公司ID或用户ID为空 [companyId:{}, companyUserId:{}]", companyId, companyUserId);
                return null;
            }

            // 验证福袋ID
            if (content.getLuckyBagId() == null) {
                log.warn("福袋ID为空");
                return null;
            }

            // 查询福袋信息
            LuckyBag luckyBag = luckyBagMapper.selectLuckyBagById(content.getLuckyBagId());
            if (luckyBag == null) {
                log.warn("未找到对应的福袋信息 [luckyBagId:{}]", content.getLuckyBagId());
                return null;
            }

            // 检查福袋状态
            if (luckyBag.getDataStatus() != null && luckyBag.getDataStatus().equals(0)) {
                log.warn("福袋被禁用 [luckyBagId:{}]", content.getLuckyBagId());
                return null;
            }

            // 查询公司信息
            Company company = companyMapper.selectCompanyById(Long.valueOf(companyId));
            if (company == null) {
                log.warn("未找到对应的公司信息 [companyId:{}]", companyId);
                return null;
            }

            // 构建福袋记录
            LuckyBagCollectRecord luckyBagCollectRecord = buildLuckyBagRecord(content, qwSopLogs, sendTime,
                    companyUserId, companyId, chatId, company, luckyBag);

            // 插入记录并返回ID
            int result = luckyBagCollectRecordMapper.insertLuckyBagCollectRecord(luckyBagCollectRecord);
            if (result <= 0) {
                log.warn("福袋记录插入失败 [luckyBagId:{}]", content.getLuckyBagId());
                return null;
            }

            // 返回新增记录的ID
            Long recordId = luckyBagCollectRecord.getId();
            if (recordId == null) {
                log.warn("福袋记录插入成功但未返回ID [luckyBagId:{}]", content.getLuckyBagId());
                return null;
            }

//            log.info("福袋记录添加成功 [recordId:{}, luckyBagId:{}]", recordId, content.getLuckyBagId());
            return recordId;

        } catch (NumberFormatException e) {
            log.error("ID转换失败 [companyId:{}, companyUserId:{}]", companyId, companyUserId, e);
            return null;
        } catch (Exception e) {
            log.error("ID:" + (content != null ? content.getLuckyBagId() : "unknown") + "-添加福袋记录失败", e);
            return null;
        }
    }

    /**
     * 构建福袋记录对象
     */
    private LuckyBagCollectRecord buildLuckyBagRecord(QwSopTempSetting.Content.Setting content,
                                                      QwSopLogs qwSopLogs,
                                                      Date sendTime,
                                                      String companyUserId,
                                                      String companyId,
                                                      String chatId,
                                                      Company company,
                                                      LuckyBag luckyBag) {
        LuckyBagCollectRecord record = new LuckyBagCollectRecord();

        record.setLuckyBagId(content.getLuckyBagId());
        record.setExpiryTime(sendTime);
        record.setCollectType("3");
        record.setCompanyId(Long.valueOf(companyId));
        record.setUserId(qwSopLogs.getFsUserId());
        if (ObjectUtil.isNotEmpty(qwSopLogs.getFsUserId())) {
            FsUser fsUser = fsUserMapper.selectFsUserByUserId(qwSopLogs.getFsUserId());
            record.setUserName(ObjectUtil.isNotEmpty(fsUser) ? fsUser.getNickName() : null);
        }
        record.setCompanyName(company.getCompanyName());
        record.setCompanyUserId(Long.valueOf(companyUserId));
        record.setSendLink(content.getMiniprogramPage());

        // 设置奖励类型和聊天信息
        if (StringUtils.isNotEmpty(chatId)) {
            record.setRewardType(1L);
            record.setChatId(chatId);
            record.setExternalUserName(qwSopLogs.getExternalUserName());
        } else {
            record.setRewardType(2L);
        }

        // 设置币种金额
        if (luckyBag.getRewardType() != null && luckyBag.getRewardType().equals("1")) {
            record.setCoinAmount(luckyBag.getAmount());
        }

        return record;
    }
    private String createH5LinkByMiniApp(QwSopTempSetting.Content.Setting setting, WxSopUserVo logVo, Date sendTime,
                                         Long courseId, Long videoId, String qwUserId,
                                         String companyUserId, String companyId, String externalId, String isOfficial, Long fsUserId) {
        // 获取缓存的配置
        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(Long.valueOf(qwUserId));
        link.setCompanyUserId(Long.parseLong(companyUserId));
        link.setVideoId(videoId.longValue());
        link.setCorpId(logVo.getCorpId());
        link.setCourseId(courseId.longValue());
        link.setQwExternalId(Long.parseLong(externalId));
        link.setUNo(UUID.randomUUID().toString());


        String randomString = generateRandomStringWithLock();
        if (StringUtil.strIsNullOrEmpty(randomString)) {
            link.setLink(UUID.randomUUID().toString().replace("-", ""));
        } else {
            link.setLink(randomString);
        }

        link.setCreateTime(sendTime);

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

        String courseJson = JSON.toJSONString(courseMap);
        String realLinkFull = h5miniappLink + 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();
    }

    private String createH5LiveShortLink(QwSopTempSetting.Content.Setting setting, String corpId, String qwUserId, String companyUserId, String companyId) {

        // 手动创建 FsCourseLink 对象，避免使用 BeanUtils.copyProperties
        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(Long.parseLong(companyId));
        link.setQwUserId(Long.valueOf(qwUserId));
        link.setCompanyUserId(Long.parseLong(companyUserId));
        link.setLiveId(setting.getLiveId());
        link.setCorpId(corpId);
        link.setUNo(UUID.randomUUID().toString());

        String randomString = generateRandomStringWithLock();
        if (StringUtil.strIsNullOrEmpty(randomString)) {
            link.setLink(UUID.randomUUID().toString().replace("-", ""));
        } else {
            link.setLink(randomString);
        }

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

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

        //存短链-
        enqueueCourseLink(link);
        return link.getRealLink();

    }
    private String getAppIdFromMiniMap(Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
                                       String companyId,
                                       int sendMsgType,
                                       Integer grade) {
        if (miniMap.isEmpty() || sendMsgType != 1) {
            return null;
        }

        Map<Integer, List<CompanyMiniapp>> gradeMap = miniMap.get(Long.valueOf(companyId));
        if (gradeMap == null) {
            return null;
        }

        int listIndex = getLevel(grade);
        List<CompanyMiniapp> miniapps = gradeMap.get(listIndex);

        if (miniapps == null || miniapps.isEmpty()) {
            return null;
        }

        CompanyMiniapp companyMiniapp = miniapps.get(0);
        return (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId()))
                ? companyMiniapp.getAppId()
                : null;
    }

    private static int getLevel(Integer grade) {
        int effectiveGrade = (grade == null) ? 5 : grade;
        int listIndex = (effectiveGrade == 1 || effectiveGrade == 2) ? 0 : 1;
        return listIndex;
    }

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

    private void handleOrderMessage(WxSopLogs 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, String isOfficial, Long fsUserId) {
        // 获取缓存的配置
        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(Long.parseLong(qwUserId));
        link.setCompanyUserId(Long.parseLong(companyUserId));
        link.setVideoId(videoId.longValue());
        link.setCorpId(logVo.getCorpId());
        link.setCourseId(courseId.longValue());
        link.setQwExternalId(Long.parseLong(externalId));

        if (StringUtil.strIsNullOrEmpty(isOfficial)) {
            link.setLinkType(0);
        } else {
            if (isOfficial.equals("1")) {
                if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
                    link.setLinkType(0);
                } else {
                    link.setLinkType(5);
                }
            } else if (isOfficial.equals("0")) {
                link.setLinkType(0);
            } else {
                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.setFsUserId(fsUserId);

        if (StringUtil.strIsNullOrEmpty(isOfficial)) {
            courseMap.setLinkType(0);
        } else {
            if (isOfficial.equals("1")) {
                if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
                    courseMap.setLinkType(0);
                } else {
                    courseMap.setLinkType(5);
                }
            } else if (isOfficial.equals("0")) {
                courseMap.setLinkType(0);
            } else {
                courseMap.setLinkType(0);
            }
        }


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

        String randomString = generateRandomStringWithLock();
        if (StringUtil.strIsNullOrEmpty(randomString)) {
            link.setLink(UUID.randomUUID().toString().replace("-", ""));
        } else {
            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 = APP_LINK_PREFIX + courseJson;

        if (CloudHostUtils.hasCloudHostName("木易华康")) {
            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(Long.parseLong(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();
        if (StringUtil.strIsNullOrEmpty(randomString)) {
            link.setLink(UUID.randomUUID().toString().replace("-", ""));
        } else {
            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, WxSopUserVo logVo, Date sendTime,
                                       Long courseId, Long videoId, String qwUserId,
                                       String companyUserId, String companyId, String externalId, String isOfficial, Long fsUserId, String chatId) {
        // 获取缓存的配置
        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(Long.parseLong(qwUserId));
        link.setCompanyUserId(Long.parseLong(companyUserId));
        link.setVideoId(videoId);
        link.setCorpId(logVo.getCorpId());
        link.setCourseId(courseId);
        if (StringUtils.isEmpty(chatId)) {
            link.setQwExternalId(Long.parseLong(externalId));
        }
        link.setProjectCode(cloudHostProper.getProjectCode());
        link.setChatId(chatId);

        if (StringUtil.strIsNullOrEmpty(isOfficial)) {
            link.setLinkType(3);
        } else {
            if (isOfficial.equals("1")) {
                if (fsUserId == null || Long.valueOf(0L).equals(fsUserId)) {
                    link.setLinkType(3);
                } else {
                    link.setLinkType(5);
                }
            } else if (isOfficial.equals("0")) {
                link.setLinkType(3);
            } else {
                link.setLinkType(3);
            }
        }

        String randomString = generateRandomStringWithLock();
        if (StringUtil.strIsNullOrEmpty(randomString)) {
            link.setLink(UUID.randomUUID().toString().replace("-", ""));
        } else {
            link.setLink(randomString);
        }

        link.setCreateTime(sendTime);

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

        String courseJson = JSON.toJSONString(courseMap);
        String realLinkFull = miniappRealLink + 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(WxSopLogs sopLogs, Long videoId, Long courseId,
                                     Date sendTime, String qwUserId, String companyUserId,
                                     String companyId, String externalId, WxSopUserVo logsVo, Integer watchType) {
        FsCourseWatchLog watchLog = new FsCourseWatchLog();
        watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
        watchLog.setQwExternalContactId(externalId != null ? Long.valueOf(externalId) : null);
        watchLog.setSendType(2);
        watchLog.setQwUserId(Long.parseLong(qwUserId));
        watchLog.setSopId(String.valueOf(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.setWatchType(watchType);
        watchLog.setCampPeriodTime(convertStringToDate(logsVo.getStartTime(), "yyyy-MM-dd"));
        enqueueWatchLog(watchLog);
    }

    /**
     * 直播看课记录处理
     *
     * @param companyId
     * @param companyUserId
     * @param externalId
     * @param liveId
     * @param appId
     * @param logSource
     * @param qwUserId
     * @param corpId
     */
    public void createLiveWatchLogAndEnQueue(String companyId, String companyUserId, String externalId, Long liveId, String appId, Integer logSource, String qwUserId, String corpId) {
        // 写入对应数据源的记录表
        LiveWatchLog itemLiveWatchLog = new LiveWatchLog();
        itemLiveWatchLog.setLiveId(liveId);
        itemLiveWatchLog.setLogType(3);
        itemLiveWatchLog.setSopCreateTime(new Date());
        itemLiveWatchLog.setCompanyId(Long.valueOf(companyId));
        itemLiveWatchLog.setCompanyUserId(Long.valueOf(companyUserId));
        itemLiveWatchLog.setSendAppId(appId);
        itemLiveWatchLog.setLogSource(logSource);
        itemLiveWatchLog.setQwUserId(qwUserId);
        itemLiveWatchLog.setExternalContactId(Long.valueOf(externalId));
        itemLiveWatchLog.setCorpId(corpId);
        enqueueZmLiveWatchLog(itemLiveWatchLog);
    }

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

    /**
     * 时间字符串转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(WxSopLogs sopLogs) {
        try {
            boolean offered = wxSopLogsQueue.offer(sopLogs, 5, TimeUnit.SECONDS);
            System.out.println(sopLogs.getSopId() + "插入队列结果: " + offered + "内容: " + JSON.toJSONString(sopLogs));
            if (!offered) {
                log.error("QwSopLogs 队列已满，无法添加日志: {}", JSON.toJSONString(sopLogs));
                // 处理队列已满的情况，例如记录到失败队列或持久化存储
            }
            //wxSopLogsQueue.clear();
        } 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<WxSopLogs> batch = new ArrayList<>(BATCH_SIZE);
        while (running || !wxSopLogsQueue.isEmpty()) {
            try {
                WxSopLogs log = wxSopLogsQueue.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);
        }
    }

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

        // 处理剩余的数据
        if (!batch.isEmpty()) {
            batchInsertLiveWatchLog(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<WxSopLogs> logsToInsert) {
        try {
//            qwSopLogsService.batchInsertQwSopLogs(logsToInsert);
            wxSopLogsService.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);
            // 可选：将失败的数据记录到失败队列或持久化存储以便后续重试
        }
    }

    /**
     * 批量插入 卓美直播看课记录
     */
    @Transactional
    @Retryable(
            value = {Exception.class},
            maxAttempts = 3,
            backoff = @Backoff(delay = 2000)
    )
    public void batchInsertLiveWatchLog(List<LiveWatchLog> liveWatchLogToInsert) {
        try {
            //更改为set 避免同一批生成的消息里面有重复数据 插入会报错
            Set<LiveWatchLog> lastInsertSet = new HashSet<>();
            //判断是否存在数据 liveId + his_qw_external_contact_id + qwUserId 唯一
            for (LiveWatchLog liveWatchLog : liveWatchLogToInsert) {
                //判断是否存在数据 存在的数据直接更新发送时间
                if (liveWatchLogMapper.updateLiveWatchLogCondition(liveWatchLog) > 0) {
                    continue;
                }
                lastInsertSet.add(liveWatchLog);
            }
            if (!lastInsertSet.isEmpty()) {
                liveWatchLogMapper.insertLiveWatchLogBatch(new ArrayList<>(lastInsertSet));
            }
//            log.info("批量插入 LiveWatchLog 完成，共插入 {} 条记录。", liveWatchLogToInsert.size());
        } catch (Exception e) {
            log.error("批量插入 LiveWatchLog 失败: {}", liveWatchLogToInsert, 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() {
        long startTime = System.currentTimeMillis();
        log.info("创建完课消息 - 定时任务开始 {}", startTime);

        // 线程池配置
        int threadPoolSize = 4;
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);

        // 用于收集所有处理结果的队列
        BlockingQueue<List<FsCourseWatchLog>> batchQueue = new LinkedBlockingQueue<>();

        try {
            // 查询当天日期范围
            LocalDate today = LocalDate.now();
            Date startDate = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
            Date endDate = Date.from(today.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());

            // 启动生产者线程 - 流式分批查询数据
            executorService.submit(() -> {
                try {
                    int batchSize = 1000;
                    long maxId = 0;
                    boolean hasMore = true;

                    while (hasMore) {
                        // 查询当前批次数据
                        List<FsCourseWatchLog> batch = fsCourseWatchLogMapper.selectFsCourseWatchLogFinishBatchByDate(
                                startDate, endDate, maxId, batchSize);

                        if (!batch.isEmpty()) {
                            // 将批次放入队列
                            batchQueue.put(batch);
                            // 更新maxId为当前批次的最后一个ID
                            maxId = batch.get(batch.size() - 1).getLogId();
                            log.debug("已生产批次数据，最后logId: {}, 数量: {}", maxId, batch.size());
                        }

                        if (batch.size() < batchSize) {
                            hasMore = false;
                            batchQueue.put(Collections.emptyList());// 结束标志
                            log.info("数据生产完成，最后logId: {}", maxId);
                        }
                    }
                } catch (Exception e) {
                    log.error("生产数据时出错", e);
                    try {
                        batchQueue.put(Collections.emptyList()); // 确保消费者能退出
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                    }
                }
            });

            // 消费者线程处理数据
            List<Future<?>> futures = new ArrayList<>();
            for (int i = 0; i < threadPoolSize; i++) {
                futures.add(executorService.submit(() -> {
                    try {
                        while (true) {
                            List<FsCourseWatchLog> batch = batchQueue.take();

                            // 空列表表示处理结束
                            if (batch.isEmpty()) {
                                batchQueue.put(Collections.emptyList()); // 传递给其他消费者
                                break;
                            }
                            log.info("开始处理批次数据");
                            processBatch(batch); // 处理批次数据
                        }
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        log.error("处理数据时被中断", e);
                    } catch (Exception e) {
                        log.error("处理数据时出错", e);
                    }
                }));
            }

            // 等待所有任务完成
            for (Future<?> future : futures) {
                try {
                    future.get();
                } catch (InterruptedException | ExecutionException e) {
                    log.error("等待任务完成时出错", e);
                    Thread.currentThread().interrupt();
                }
            }

            log.info("所有批次处理完成，总耗时: {}ms", System.currentTimeMillis() - startTime);

        } finally {
            executorService.shutdown();
            try {
                if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                executorService.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    }

    // 处理单个批次的方法
    private void processBatch(List<FsCourseWatchLog> batch) {
        List<FsCourseWatchLog> finishLogsToUpdate = new ArrayList<>();
        List<QwSopLogs> sopLogsToInsert = new ArrayList<>();
        log.info("开始执行处理批次方法-数量：{}", batch.size());
        for (FsCourseWatchLog finishLog : batch) {
            try {

                try {

                    asyncCourseWatchFinishService.executeCourseWatchFinish(finishLog);

                } catch (Exception e) {
                    log.error("添加完课打备注失败", e);
                }

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

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

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

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

                // 构建 sopLogs 对象
                QwSopLogs sopLogs = buildSopLogs(finishLog, externalContact, finishTemp);
                if (sopLogs == null) {
                    log.error("生成完课发送记录为空-:{}", finishLog.getQwExternalContactId());
                    continue;
                }

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

        // 批量更新和插入
        if (!finishLogsToUpdate.isEmpty()) {
            try {
                fsCourseWatchLogMapper.batchUpdateWatchLogSendMsg(finishLogsToUpdate);
                log.info("批量更新 finishLog 成功，数量: {}", finishLogsToUpdate.size());
            } catch (Exception e) {
                log.error("批量更新 finishLog 失败", e);
            }
        }

        if (!sopLogsToInsert.isEmpty()) {
            try {
                qwSopLogsService.batchInsertQwSopLogs(sopLogsToInsert);
                log.info("批量插入 sopLogs 成功，数量: {}", sopLogsToInsert.size());
            } catch (Exception e) {
                log.error("批量插入 sopLogs 失败", e);
            }
        }
        log.info("结束处理批次方法-数量：{}", batch.size());
    }

    /**
     * 构建 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(40000000);
        sopLogs.setCompanyId(finishLog.getCompanyId());
        sopLogs.setSopId(finishLog.getSopId());
        sopLogs.setExternalUserId(externalContact.getExternalUserId());
        sopLogs.setExternalUserName(externalContact.getName());
        sopLogs.setFsUserId(finishLog.getUserId() != null ? finishLog.getUserId() : null);
        sopLogs.setExternalId(finishLog.getQwExternalContactId());
        sopLogs.setUserLogsId("-");

        sopLogs.setQwUserKey(finishLog.getQwUserId() != null ? finishLog.getQwUserId() : null);

        // 解析模板设置
        List<QwSopCourseFinishTempSetting.Setting> settings = parseSettings(finishTemp.getSetting());
        if (settings == null) {
            return null;
        }
        //完课后若是小程序发送另外一堂课
        saveWacthLogOfCourseLink(settings, sopLogs, newTimeString, finishLog, finishTemp);
        // 处理音频内容
        for (QwSopCourseFinishTempSetting.Setting st : settings) {
            if (st.getContentType().equals("7")) {
                Long companyUserId = finishLog.getCompanyUserId();
                QwSopTempVoice qwSopTempVoice = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId, st.getValue());
                if (qwSopTempVoice != null && qwSopTempVoice.getVoiceUrl() != null && qwSopTempVoice.getRecordType() == 1) {
                    st.setVoiceUrl(qwSopTempVoice.getVoiceUrl());
                    st.setVoiceDuration(String.valueOf(qwSopTempVoice.getDuration()));
                } else if (qwSopTempVoice == null) {
                    if (companyUserId != null && st.getValue() != null) {
                        qwSopTempVoice = new QwSopTempVoice();
                        qwSopTempVoice.setCompanyUserId(companyUserId);
                        qwSopTempVoice.setVoiceTxt(st.getValue());
                        qwSopTempVoice.setRecordType(0);
                        sopTempVoiceService.insertQwSopTempVoice(qwSopTempVoice);
                    }
                }
            }
        }
//        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;
    }

    /**
     * 判定小程序的话新增创建看课记录，以及fsCourseLink
     *
     * @param settings
     */
    public void saveWacthLogOfCourseLink(List<QwSopCourseFinishTempSetting.Setting> settings, QwSopLogs sopLogs, String newTimeString, FsCourseWatchLog finishLog, FsCourseFinishTemp finishTemp) {
        String json = configService.selectConfigByKey("course.config");
        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
        Date dataTime = new Date();
        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));

        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(sopLogs.getCorpId());
        QwUser qwUser = qwExternalContactService.getQwUserByRedis(sopLogs.getCorpId(), sopLogs.getQwUserid());
        if (qwUser == null) {
            return;
        }
        for (QwSopCourseFinishTempSetting.Setting st : settings) {
            switch (st.getContentType()) {
                //小程序单独
                case "4":
                    addWatchLogIfNeeded(sopLogs.getSopId(), st.getVideoId().intValue(), st.getCourseId().intValue(), sopLogs.getFsUserId(), String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(),
                            sopLogs.getExternalId(), newTimeString.substring(0, 10), dataTime);

                    String linkByMiniApp = createLinkByMiniApp(st, sopLogs.getCorpId(), dataTime, finishTemp.getCourseId().intValue(), Integer.valueOf(st.getVideoId().toString()),
                            String.valueOf(qwUser.getId()), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), sopLogs.getExternalId(), config);


                    String miniAppId = null;
                    if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
                        Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(qwUser.getCompanyId()));
                        if (integerListMap != null) {
                            int listIndex = 0;
                            List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);

                            if (miniapps != null && !miniapps.isEmpty()) {
                                CompanyMiniapp companyMiniapp = miniapps.get(0);
                                if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
                                    miniAppId = companyMiniapp.getAppId();
                                }
                            }
                        }
                    }

                    if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
                        miniAppId = qwCompany.getMiniAppId();
                    }

                    if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
                        st.setMiniprogramAppid(miniAppId);
                    } else {
                        log.error("企业未配置小程序-" + sopLogs.getCorpId());
                    }

                    String miniprogramTitle = st.getMiniprogramTitle();
                    int maxLength = 17;
                    st.setMiniprogramTitle(miniprogramTitle.length() > maxLength ? miniprogramTitle.substring(0, maxLength) + "..." : miniprogramTitle);
                    st.setMiniprogramPage(linkByMiniApp);
                    break;
                default:
                    break;

            }
        }
    }

    private Date processDate(String sendTimeParam) {
        // 1. 获取当前日期（年月日）
        LocalDate currentDate = LocalDate.now();

        // 2. 解析传入的时分（支持 "HH:mm" 或 "H:mm"）
        LocalTime sendTime = LocalTime.parse(sendTimeParam);

        // 3. 合并为 LocalDateTime
        LocalDateTime dateTime = LocalDateTime.of(currentDate, sendTime);

        // 4. 转换为 Date（需通过 Instant 和系统默认时区）
        Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());

        return date;
    }

    /**
     * 新增courseLink
     *
     * @param setting
     * @param corpId
     * @param sendTime
     * @param courseId
     * @param videoId
     * @param qwUserId
     * @param companyUserId
     * @param companyId
     * @param externalId
     * @param config
     * @return
     */
    private String createLinkByMiniApp(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
                                       Integer courseId, Integer videoId, String qwUserId,
                                       String companyUserId, String companyId, Long externalId, CourseConfig config) {

        FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
                companyUserId, companyId, externalId, 3, null);

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

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

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

        link.setUpdateTime(updateTime);
        //存短链-
        fsCourseLinkMapper.insertFsCourseLink(link);
        return link.getRealLink();
    }

    /**
     * 创建courselink
     *
     * @param corpId
     * @param sendTime
     * @param courseId
     * @param videoId
     * @param qwUserId
     * @param companyUserId
     * @param companyId
     * @param externalId
     * @param type
     * @param chatId
     * @return
     */
    public FsCourseLink createFsCourseLink(String corpId, Date sendTime, Integer courseId, Integer videoId, String qwUserId,
                                           String companyUserId, String companyId, Long externalId, Integer type, String chatId) {
        // 手动创建 FsCourseLink 对象，避免使用 BeanUtils.copyProperties
        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(Long.parseLong(companyId));
        link.setQwUserId(Long.valueOf(qwUserId));
        link.setCompanyUserId(Long.parseLong(companyUserId));
        link.setVideoId(videoId.longValue());
        link.setCorpId(corpId);
        link.setCourseId(courseId.longValue());
        link.setChatId(chatId);
        link.setQwExternalId(externalId);
        link.setLinkType(type); //小程序
        link.setUNo(UUID.randomUUID().toString());
        link.setProjectCode(cloudHostProper.getProjectCode());
        String randomString = generateRandomStringWithLock();
        if (StringUtil.strIsNullOrEmpty(randomString)) {
            link.setLink(UUID.randomUUID().toString().replace("-", ""));
        } else {
            link.setLink(randomString);
        }

        link.setCreateTime(sendTime);

        return link;
    }


    /**
     * 计算过期时间
     *
     * @param setting
     * @param sendTime
     * @param config
     * @return
     */
    private Date createUpdateTime(QwSopCourseFinishTempSetting.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;
    }

    /**
     * 增加看课记录
     *
     * @param sopId
     * @param videoId
     * @param courseId
     * @param fsUserId
     * @param qwUserId
     * @param companyUserId
     * @param companyId
     * @param externalId
     * @param startTime
     * @param createTime
     * @return
     */
    private Long addWatchLogIfNeeded(String sopId, Integer videoId, Integer courseId,
                                     Long fsUserId, String qwUserId, String companyUserId,
                                     String companyId, Long externalId, String startTime, Date createTime) {

        try {
            FsCourseWatchLog watchLog = new FsCourseWatchLog();
            watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
            watchLog.setQwExternalContactId(externalId);
            watchLog.setSendType(2);
            watchLog.setQwUserId(Long.valueOf(qwUserId));
            watchLog.setSopId(sopId);
            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(createTime);
            watchLog.setUpdateTime(createTime);
            watchLog.setLogType(3);
            watchLog.setUserId(fsUserId);
            watchLog.setCampPeriodTime(convertStringToDate(startTime, "yyyy-MM-dd"));

            //存看课记录
            int i = fsCourseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
            return watchLog.getLogId();
        } catch (Exception e) {
            log.error("插入观看记录失败：" + e.getMessage());
            return null;
        }
    }


    /**
     * 解析模板设置
     */
    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;
    }

    /**
     * 设置SOP日志状态的辅助方法
     */
    private void setSopLogsStatus(WxSopLogs sopLogs, Integer sendStatus, Integer receivingStatus, String remark) {
        if (sopLogs != null) {
            sopLogs.setSendStatus(sendStatus);
            sopLogs.setReceivingStatus(receivingStatus);
            sopLogs.setRemark(remark);
        }
    }

    /**
     * 查询并缓存用户记录
     *
     * @param userId
     * @param cacheKey
     * @param query
     * @return
     */
    // 获取缓存或查询记录
    private List<LuckyBagCollectRecord> getCachedOrQueryRecords(Long userId, String cacheKey,
                                                                LuckyBagCollectRecord query) {
        Object cachedData = redisCache.getCacheObject(cacheKey);
        if (cachedData != null && cachedData instanceof List) {
            log.debug("福袋记录缓存命中，userId: {}", userId);
            return (List<LuckyBagCollectRecord>) cachedData;
        }

        // 缓存未命中，查询数据库
        log.debug("福袋记录缓存未命中，查询数据库，userId: {}", userId);
        List<LuckyBagCollectRecord> records = luckyBagCollectRecordMapper.selectLuckyBagCollectRecordList(query);
        cacheUserRecords(userId, cacheKey, records);
        return records != null ? records : Collections.emptyList();
    }

    /**
     * 缓存用户福袋记录
     *
     * @param userId
     * @param cacheKey
     * @param records
     */
    private void cacheUserRecords(Long userId, String cacheKey, List<LuckyBagCollectRecord> records) {
        if (records != null && !records.isEmpty()) {
            try {
                // 计算到明天凌晨的剩余时间（秒）
                LocalDateTime now = LocalDateTime.now();
                LocalDateTime tomorrowStart = LocalDate.now().plusDays(1).atStartOfDay();
                long secondsUntilTomorrow = Duration.between(now, tomorrowStart).getSeconds();

                // 设置缓存，过期时间到明天凌晨
                int ttlSeconds = (int) Math.max(60, secondsUntilTomorrow); // 至少缓存1分钟
                redisCache.setCacheObject(cacheKey, records, ttlSeconds, TimeUnit.SECONDS);

                log.debug("缓存用户福袋记录，userId: {}，记录数: {}，过期时间: {}秒",
                        userId, records.size(), ttlSeconds);
            } catch (Exception e) {
                log.error("缓存用户福袋记录失败，userId: {}", userId, e);
                // 缓存失败不影响主流程
            }
        }
    }

    /**
     * 缓存用户计数
     * @param userId
     * @param cacheKey
     * @param count
     */
    private void cacheUserCount(Long userId, String cacheKey, int count) {
        try {
            // 计算到明天凌晨的剩余时间
            LocalDateTime now = LocalDateTime.now();
            LocalDateTime tomorrowStart = LocalDate.now().plusDays(1).atStartOfDay();
            long secondsUntilTomorrow = Duration.between(now, tomorrowStart).getSeconds();

            int ttlSeconds = (int) Math.max(60, secondsUntilTomorrow);
            redisCache.setCacheObject(cacheKey, count, ttlSeconds, TimeUnit.SECONDS);

            log.debug("缓存用户福袋计数，userId: {}，次数: {}，过期时间: {}秒", userId, count, ttlSeconds);
        } catch (Exception e) {
            log.error("缓存用户计数失败，userId: {}", userId, e);
        }
    }
}
