package com.fs.course.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fs.common.core.domain.R;
import com.fs.common.utils.DateUtils;
import com.fs.common.utils.PubFun;
import com.fs.common.utils.StringUtils;
import com.fs.common.utils.date.DateUtil;
import com.fs.company.mapper.CompanyUserMapper;
import com.fs.config.cloud.CloudHostProper;
import com.fs.core.config.WxMaConfig;
import com.fs.core.config.WxMaConfiguration;
import com.fs.course.config.CourseConfig;
import com.fs.course.config.CourseMaConfig;
import com.fs.course.domain.*;
import com.fs.course.mapper.FsCourseDomainNameMapper;
import com.fs.course.mapper.FsCourseLinkMapper;
import com.fs.course.mapper.FsCourseWatchLogMapper;
import com.fs.course.mapper.FsUserCourseVideoMapper;
import com.fs.course.param.FsCourseLinkCreateParam;
import com.fs.course.param.FsCourseLinkRoomParam;
import com.fs.course.service.IFsCourseLinkService;
import com.fs.course.service.IFsCoursePlaySourceConfigService;
import com.fs.course.utils.FsCoursePlaySourceConfigQuery;
import com.fs.course.service.IFsUserCourseService;
import com.fs.course.utils.FsCourseWatchLogProjectFiller;
import com.fs.his.config.FsSysConfig;
import com.fs.his.utils.ConfigUtil;
import com.fs.his.utils.HttpUtil;
import com.fs.qw.domain.QwGroupChat;
import com.fs.qw.domain.QwGroupChatUser;
import com.fs.qw.domain.QwUser;
import com.fs.qw.mapper.QwExternalContactMapper;
import com.fs.qw.mapper.QwUserMapper;
import com.fs.qw.service.IQwGroupChatService;
import com.fs.qw.service.IQwGroupChatUserService;
import com.fs.qw.vo.GroupUserExternalVo;
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.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import okhttp3.*;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

import static com.fs.course.utils.LinkUtil.generateRandomStringWithLock;
import static com.fs.sop.service.impl.SopUserLogsInfoServiceImpl.convertStringToDate;

/**
 * 短链Service业务层处理
 *
 * @author fs
 * @date 2024-09-11
 */
@Service
@Slf4j
public class FsCourseLinkServiceImpl implements IFsCourseLinkService
{
    @Autowired
    private FsCourseLinkMapper fsCourseLinkMapper;
    @Autowired
    private ISysConfigService configService;
    @Autowired
    private IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;

    @Autowired
    private FsCourseDomainNameMapper fsCourseDomainNameMapper;

    @Autowired
    private FsCourseWatchLogMapper fsCourseWatchLogMapper;

    @Autowired
    private FsCourseWatchLogProjectFiller fsCourseWatchLogProjectFiller;

    @Autowired
    private QwExternalContactMapper qwExternalContactMapper;

    @Autowired
    private IFsUserCourseService fsUserCourseService;

    @Autowired
    private IQwGroupChatService qwGroupChatService;

    @Autowired
    private IQwGroupChatUserService qwGroupChatUserService;
    @Autowired
    private QwUserMapper qwUserMapper;
    @Autowired
    private FsUserCourseVideoMapper courseVideoMapper;
    @Autowired
    private CompanyUserMapper companyUserMapper;

    @Autowired
    private ConfigUtil configUtil;

    @Autowired
    CloudHostProper cloudHostProper;

    @Autowired
    private SysConfigMapper sysConfigMapper;

    private static String TOKEN_VALID_CODE = "40001";

    private volatile Integer version = 0;


    /**
     * 查询短链
     *
     * @param linkId 短链主键
     * @return 短链
     */
    @Override
    public FsCourseLink selectFsCourseLinkByLinkId(Long linkId)
    {
        return fsCourseLinkMapper.selectFsCourseLinkByLinkId(linkId);
    }

    /**
     * 查询短链列表
     *
     * @param fsCourseLink 短链
     * @return 短链
     */
    @Override
    public List<FsCourseLink> selectFsCourseLinkList(FsCourseLink fsCourseLink)
    {
        return fsCourseLinkMapper.selectFsCourseLinkList(fsCourseLink);
    }

    /**
     * 新增短链
     *
     * @param fsCourseLink 短链
     * @return 结果
     */
    @Override
    public int insertFsCourseLink(FsCourseLink fsCourseLink)
    {
        fsCourseLink.setCreateTime(DateUtils.getNowDate());
        return fsCourseLinkMapper.insertFsCourseLink(fsCourseLink);
    }

    /**
     * 修改短链
     *
     * @param fsCourseLink 短链
     * @return 结果
     */
    @Override
    public int updateFsCourseLink(FsCourseLink fsCourseLink)
    {
        return fsCourseLinkMapper.updateFsCourseLink(fsCourseLink);
    }

    /**
     * 批量删除短链
     *
     * @param linkIds 需要删除的短链主键
     * @return 结果
     */
    @Override
    public int deleteFsCourseLinkByLinkIds(Long[] linkIds)
    {
        return fsCourseLinkMapper.deleteFsCourseLinkByLinkIds(linkIds);
    }

    /**
     * 删除短链信息
     *
     * @param linkId 短链主键
     * @return 结果
     */
    @Override
    public int deleteFsCourseLinkByLinkId(Long linkId)
    {
        return fsCourseLinkMapper.deleteFsCourseLinkByLinkId(linkId);
    }

    private static final String realLink = "/courseH5/pages/course/learning?course=";
    private static final String shortLink = "/courseH5/pages/course/learning?s=";
    private static final String miniappRealLink = "/pages_course/video?course=";


    /**
     *创建应急短链
     * @param param
     * @return
     */
    @Transactional
    @Synchronized
    @Override
    public R createLinkUrl(FsCourseLinkCreateParam param)  {
        String json = configService.selectConfigByKey("course.config");
        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(param.getCompanyId());
        link.setCompanyUserId(param.getCompanyUserId());
        link.setVideoId(param.getVideoId());
        link.setCourseId(param.getCourseId());
        link.setLinkType(1);//创建应急短链
//        link.setLinkUuid(UUID.randomUUID().toString());


        String randomString = generateRandomString();
        link.setLink(randomString);
        link.setCreateTime(new Date());

        Integer expireDays = (param.getDays() == null || param.getDays() == 0)
                ? config.getVideoLinkExpireDate()
                : param.getDays();

        // 使用 Java 8 时间 API 计算过期时间
        LocalDateTime sendDateTime = new Date().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);

        // 设置过期时间
        link.setUpdateTime(updateTime);

        if (fsCourseLinkMapper.insertFsCourseLink(link)>0){
            FsCourseRealLink courseMap = new FsCourseRealLink();
            BeanUtils.copyProperties(link,courseMap);
            // 将实体类对象转换为 JSON 字符串
            String course = JSON.toJSONString(courseMap);
            link.setRealLink(realLink+course);
            if (fsCourseLinkMapper.updateFsCourseLink(link)>0){
                String domainName = getDomainName(param.getCompanyUserId(), config);
                String sortLink = domainName + shortLink + link.getLink();
                return R.ok("短链生成").put("url",sortLink);
            }
        }
        return R.error("短链生成失败！");
    }

    @Override
    public R  createRoomLinkUrl(FsCourseLinkCreateParam param) {
//        QwUser qwUser;
//        if(param.getQwUserId() != null){
//            qwUser = qwUserMapper.selectById(param.getQwUserIdLong());
//        }
//        if(qwUser == null){
//            return R.error("未找到企微账号");
//        }
        String json = configService.selectConfigByKey("course.config");
        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
        FsCourseLink link = new FsCourseLink();
        BeanUtils.copyProperties(param,link);
        link.setLinkType(0);
        link.setIsRoom(1);
        String randomString = generateRandomString();
        link.setLink(randomString);
        link.setChatId(param.getChatId());

        FsCourseRealLink courseMap = new FsCourseRealLink();
        BeanUtils.copyProperties(link,courseMap);
        // 将实体类对象转换为 JSON 字符串
        String course = JSON.toJSONString(courseMap);
        link.setRealLink(realLink+course);
        link.setCreateTime(new Date());
        Integer expireDays = 0;
        if (param.getDays() == null || param.getDays() == 0){
            expireDays = config.getVideoLinkExpireDate();
        }else {
            expireDays = param.getDays();
        }
        // 设置过期时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(link.getCreateTime());
        calendar.add(Calendar.DAY_OF_MONTH, expireDays);
        link.setUpdateTime(calendar.getTime());
        if (fsCourseLinkMapper.insertFsCourseLink(link)>0){
            String domainName = getDomainName(param.getCompanyUserId(), config);
            String sortLink = domainName + shortLink + link.getLink();

            return R.ok("短链生成").put("url",sortLink);
        }
        return R.error("短链生成失败！");
    }

    @Override
    public R createLinkUrlWc(FsCourseLinkCreateParam param) {

        String json = configService.selectConfigByKey("course.config");
        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);

        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(param.getCompanyId());
        link.setQwUserId(Long.valueOf(param.getQwUserId()));
        link.setCompanyUserId(param.getCompanyUserId());
        link.setVideoId(param.getVideoId());
        link.setCorpId(param.getCorpId());
        link.setCourseId(param.getCourseId());
        link.setQwExternalId(param.getQwExternalId());
        link.setLinkType(0);
        FsCourseRealLink courseMap = new FsCourseRealLink();
        BeanUtils.copyProperties(link,courseMap);
        // 将实体类对象转换为 JSON 字符串
        String course = JSON.toJSONString(courseMap);
        link.setRealLink(realLink+course );
        String randomString = generateRandomString();
        link.setLink(randomString);
        link.setCreateTime(param.getSendTime());
        Integer expireDays = 0;
        if (param.getDays() == null || param.getDays() == 0){
            expireDays = config.getVideoLinkExpireDate();
        }else {
            expireDays = param.getDays();
        }
        // 设置过期时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(param.getSendTime());
        calendar.add(Calendar.DAY_OF_MONTH, expireDays);
        link.setUpdateTime(calendar.getTime());
        if (fsCourseLinkMapper.insertFsCourseLink(link)>0){
            String domainName = getDomainName(param.getCompanyUserId(), config);
            String sortLink = domainName + shortLink + link.getLink();
            return R.ok("短链生成").put("url",sortLink);
        }
        return R.error("短链生成失败！");
    }

    /**
     * 欢迎语飞书（授权）：对齐 SOP 飞书 NEW（contentType=18）——
     * 短链入库 feishuNeedAuth=1，发出 {realLinkDomainName}/?uuid={link}&appid={feishuMpAppId}，
     * 领取时再走 getFeishuCourseNewLink，发送瞬间不建飞书文档。
     */
    @Override
    public R createFeishuAuthLinkUrlWc(FsCourseLinkCreateParam param) {
        String json = configService.selectConfigByKey("course.config");
        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
        if (config == null || StringUtils.isEmpty(config.getRealLinkDomainName())) {
            return R.error("飞书授权链接生成失败：未配置通用看课域名");
        }
        if (StringUtils.isEmpty(config.getFeishuMpAppId())) {
            return R.error("飞书授权链接生成失败：未配置飞书服务号AppID");
        }
        if (param.getVideoId() == null || param.getCourseId() == null) {
            return R.error("飞书授权链接生成失败：课程或视频ID为空");
        }
        Date sendTime = param.getSendTime() != null ? param.getSendTime() : new Date();
        FsCourseLink link = createFsCourseLink(param.getCorpId(), sendTime, param.getCourseId(), param.getVideoId(),
                param.getQwUserId(), param.getCompanyUserId(), param.getCompanyId(), param.getQwExternalId(), 0);
        // 固定需授权，对齐飞书授权看课链路
        link.setFeishuNeedAuth(1);

        FsCourseRealLink courseMap = new FsCourseRealLink();
        BeanUtils.copyProperties(link, courseMap);
        String courseJson = JSON.toJSONString(courseMap);
        link.setRealLink(realLink + courseJson);
        link.setUpdateTime(createUpdateTime(param.getDays(), sendTime, config));
        if (fsCourseLinkMapper.insertFsCourseLink(link) <= 0) {
            return R.error("飞书授权链接生成失败：短链入库失败");
        }
        String domain = config.getRealLinkDomainName().trim();
        if (!domain.endsWith("/")) {
            domain = domain + "/";
        }
        String url = domain + "?uuid=" + link.getLink() + "&appid=" + config.getFeishuMpAppId().trim();
        return R.ok("飞书授权链接生成").put("url", url);
    }

    @Override
    public String createLinkUrlWcmini(FsCourseLinkCreateParam param) {
        String json = configService.selectConfigByKey("course.config");
        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);


        FsCourseLink link = createFsCourseLink(param.getCorpId(), param.getSendTime(), param.getCourseId(), param.getVideoId(), param.getQwUserId(),
                param.getCompanyUserId(), param.getCompanyId(), param.getQwExternalId(),3);

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

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

        Date updateTime = createUpdateTime(param.getDays(), param.getSendTime(), config);

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

        return link.getRealLink();
    }


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

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

        link.setCreateTime(sendTime);

        return link;
    }

    private String getDomainName(Long companyUserId,CourseConfig config){
        String domainName = companyUserMapper.selectDomainByUserId(companyUserId);
        if (StringUtils.isEmpty(domainName)){
            domainName = config.getRealLinkDomainName();
        }
        return domainName;
    }

    private Date createUpdateTime(Integer expiresDays, Date sendTime, CourseConfig config){

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

//         使用 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;
    }

    // 生成随机的八位数字字母大小写的字符串（改为生成随机不重复的雪花id）
    public static String generateRandomString() {
//        // 可以在这里添加允许的符号字符，比如 "!@#$%^&*"
//        String symbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
//        SecureRandom random = new SecureRandom();
//        StringBuilder sb = new StringBuilder(8);
//
//        for (int i = 0; i < 8; i++) {
//            int index = random.nextInt(symbols.length());
//            sb.append(symbols.charAt(index));
//        }
//        return sb.toString();
        return UUID.randomUUID().toString().replace("-", "");
    }

    @Override
    public R getRealLink(String link) {
        try {
            // URL解码
            String sLink = URLDecoder.decode(link, "UTF-8");

            // 从数据库中查找短链对应的真实链接
            FsCourseLink courseLink = fsCourseLinkMapper.selectFsCourseLinkByLink(sLink);
            if (courseLink != null) {
                // 获取当前时间
                Date currentTime = new Date();
                // 获取链接的更新时间（假设这个字段代表过期时间）
                Date updateTime = courseLink.getUpdateTime();

                // 判断是否过期
                if (currentTime.after(updateTime)) {
                    // 链接已过期
                    log.info("链接已过期: {}", sLink);
                    return R.error("链接已过期").put("realLink", sLink);
                } else {
                    // 链接仍然有效，返回真实链接
                    if (courseLink.getRealLink() == null) {
                        log.error("真实链接不存在, link: {}", sLink);
                        return R.error("真实链接不存在").put("realLink", sLink);
                    }
                    log.info("链接仍然有效: {}", sLink);
                    String json = configService.selectConfigByKey("course.config");
                    CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
                    String domainName = companyUserMapper.selectDomainByUserId(courseLink.getCompanyUserId());
                    if (StringUtils.isEmpty(domainName)){
                        domainName = config.getRealLinkDomainName();
                    }
                    String realLink = domainName+courseLink.getRealLink();
                    return R.ok().put("realLink",realLink ).put("config",config).put("headerImg",cloudHostProper.getHeaderImg());
                }
            } else {
                // 链接不存在或解析失败
                log.warn("URL解析错误或链接不存在: {}", sLink);
                return R.error("URL 解析错误或链接不存在").put("realLink", sLink);
            }

        } catch (UnsupportedEncodingException e) {
            // URL解码异常
            log.error("URL 解码失败: {}", e.getMessage(), e);
            return R.error("URL 解码失败").put("realLink", link);
        } catch (Exception e) {
            // 捕获其他异常并记录
            log.error("发生未知错误: {}", e.getMessage(), e);
            return R.error("发生未知错误，请稍后再试").put("realLink", link);
        }
    }


    @Override
    public void delCourseExpireLink() {
        List<Long> linkIds  = fsCourseLinkMapper.selectExpireLink();
        processDelCourseLink(linkIds);
    }

    private void processDelCourseLink(List<Long> linkIds) {
        // 定义批量删除的大小
        int batchSize = 1000;

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

            int endIndex = Math.min(i + batchSize, linkIds.size());
            List<Long> batchList = linkIds.subList(i, endIndex);  // 获取当前批次的子集
            Long[] batchArray = batchList.toArray(new Long[0]);

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

    @Override
    public void updateLinks() {
        List<FsCourseLink> links = fsCourseLinkMapper.selectFsCourseLinkListUpdate();
        for (FsCourseLink link : links){
            String newUrl = link.getRealLink().replace("https://h5.cdwjyyh.com","");
            link.setRealLink(newUrl);
            fsCourseLinkMapper.updateFsCourseLink(link);
        }
    }

    @Override
    public R createLinkByMiniApp(FsCourseLinkCreateParam param) {
        String json = configService.selectConfigByKey("course.config");
        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);

        FsCourseLink link = new FsCourseLink();
        link.setCompanyId(param.getCompanyId());
        link.setQwUserId(Long.valueOf(param.getQwUserId()));
        link.setCompanyUserId(param.getCompanyUserId());
        link.setVideoId(param.getVideoId());
        link.setCorpId(param.getCorpId());
        link.setCourseId(param.getCourseId());
        link.setQwExternalId(param.getQwExternalId());
        link.setLinkType(3);
        String randomString = generateRandomString();
        link.setLink(randomString);

        //组装参数json
        FsCourseRealLink courseMap = new FsCourseRealLink();
        BeanUtils.copyProperties(link,courseMap);
        // 将实体类对象转换为 JSON 字符串
        String course = JSON.toJSONString(courseMap);
        link.setRealLink(miniappRealLink+course );

        link.setCreateTime(param.getSendTime());
        Integer expireDays = 0;
        if (param.getDays() == null || param.getDays() == 0){
            expireDays = config.getVideoLinkExpireDate();
        }else {
            expireDays = param.getDays();
        }
        // 设置过期时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(param.getSendTime());
        calendar.add(Calendar.DAY_OF_MONTH, expireDays);
        link.setUpdateTime(calendar.getTime());
        if (fsCourseLinkMapper.insertFsCourseLink(link)>0){
            return R.ok("短链生成").put("url",link.getRealLink());
        }
        return R.error("短链生成失败！");
    }

    @Override
    public R getRealLinkH5(String link) {
        try {
            // URL解码
            String sLink = URLDecoder.decode(link, "UTF-8");

            // 从数据库中查找短链对应的真实链接
            FsCourseLink courseLink = fsCourseLinkMapper.selectFsCourseLinkByLink(sLink);
            if (courseLink != null) {
                // 获取当前时间
                Date currentTime = new Date();
                // 获取链接的更新时间（假设这个字段代表过期时间）
                Date updateTime = courseLink.getUpdateTime();

                // 判断是否过期
                if (currentTime.after(updateTime)) {
                    // 链接已过期
                    log.info("链接已过期: {}", sLink);
                    return R.error("链接已过期").put("realLink", sLink);
                } else {
                    // 链接仍然有效，返回真实链接
                    if (courseLink.getRealLink() == null) {
                        log.error("真实链接不存在, link: {}", sLink);
                        return R.error("真实链接不存在").put("realLink", sLink);
                    }
                    log.info("链接仍然有效: {}", sLink);
                    String json = configService.selectConfigByKey("course.config");
                    CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
                    String domainName = companyUserMapper.selectDomainByUserId(courseLink.getCompanyUserId());
                    if (StringUtils.isEmpty(domainName)){
                        domainName = config.getRealLinkDomainName();
                    }
                    String realLink = domainName+courseLink.getRealLink();
                    String imgUrl = cloudHostProper.getHeaderImg();
                    return R.ok().put("realLink",realLink ).put("config",config).put("headerImg",imgUrl);
                }
            } else {
                // 链接不存在或解析失败
                log.warn("URL解析错误或链接不存在: {}", sLink);
                return R.error("URL 解析错误或链接不存在").put("realLink", sLink);
            }

        } catch (UnsupportedEncodingException e) {
            // URL解码异常
            log.error("URL 解码失败: {}", e.getMessage(), e);
            return R.error("URL 解码失败").put("realLink", link);
        } catch (Exception e) {
            // 捕获其他异常并记录
            log.error("发生未知错误: {}", e.getMessage(), e);
            return R.error("发生未知错误，请稍后再试").put("realLink", link);
        }
    }

    @Override
    public R createRoomLink(FsCourseLinkRoomParam param, QwUser qwUser) {
        FsCourseLinkCreateParam createParam = new FsCourseLinkCreateParam();
        createParam.setCourseId(param.getCourseId());
        createParam.setVideoId(param.getVideoId());
        createParam.setCorpId(param.getCorpId());
        createParam.setCompanyUserId(qwUser.getCompanyUserId());
        createParam.setCompanyId(qwUser.getCompanyId());
        createParam.setQwUserId(qwUser.getId());
        createParam.setChatId(param.getChatId());
        String linkUrl;
        R createLink = createRoomLinkUrl(createParam);
        if (createLink.get("code").equals(500)){
            return R.error("链接生成失败！");
        }

        QwGroupChat groupChat = qwGroupChatService.selectQwGroupChatByChatId(param.getChatId());
        List<QwGroupChatUser> qwGroupChatUserList = qwGroupChatUserService.selectQwGroupChatUserByChatIds(new String[]{param.getChatId()});
        List<String> groupChatUserIds = PubFun.listToNewList(qwGroupChatUserList, QwGroupChatUser::getUserId);
        if(!groupChatUserIds.isEmpty()){
            List<GroupUserExternalVo> userList = qwExternalContactMapper.selectByGroupUser(groupChatUserIds);
            Map<String, List<GroupUserExternalVo>> userMap = PubFun.listToMapByGroupList(userList, GroupUserExternalVo::getExternalUserId);
            qwGroupChatUserList.forEach(e -> {
                e.setUserList(userMap.getOrDefault(e.getUserId(), Collections.emptyList()));
            });
        }
        try {
            qwGroupChatUserList.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) {
                    addWatchLogIfNeeded(param.getVideoId(), param.getCourseId(), vo.getFsUserId(), qwUser.getId(), qwUser.getCompanyUserId(), qwUser.getCompanyId(), vo.getId(), new Date(), new Date());
                }
            });
        } catch (Exception e) {
            log.error("群聊创建看课记录失败！", e);
        }

        linkUrl = (String) createLink.get("url");

        FsUserCourse course = fsUserCourseService.selectFsUserCourseByCourseId(param.getCourseId());

        JSONObject news = new JSONObject(true); // true 表示保持字段顺序
        news.put("link", linkUrl);
        news.put("title", course.getCourseName());
        news.put("desc", param.getTitle()==null?course.getCourseName():param.getTitle());
        news.put("imgUrl", course.getImgUrl());
        return R.ok().put("news",news);
    }


    private void addWatchLogIfNeeded(Long videoId, Long courseId,
                                     Long fsUserId, Long qwUserId, Long companyUserId,
                                     Long companyId, Long externalId, Date startTime,Date createTime) {

        try {
            FsCourseWatchLog watchLog = new FsCourseWatchLog();
            watchLog.setVideoId(videoId);
            watchLog.setQwExternalContactId(externalId);
            watchLog.setSendType(2);
            watchLog.setQwUserId(qwUserId);
            watchLog.setDuration(0L);
            watchLog.setCourseId(courseId);
            watchLog.setCompanyUserId(companyUserId);
            watchLog.setCompanyId(companyId);
            watchLog.setCreateTime(createTime);
            watchLog.setUpdateTime(createTime);
            watchLog.setLogType(3);
            watchLog.setUserId(fsUserId);
            watchLog.setCampPeriodTime(startTime);
            // 企微看课补齐 project，避免按项目完播统计丢失
            fsCourseWatchLogProjectFiller.fillIfAbsent(watchLog);
            //存看课记录
            fsCourseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
        }catch (Exception e){
            log.error("一键群发失败-插入观看记录失败："+e.getMessage());
        }

    }


    /**
     *
     * 生成跳转微信小程序的链接 base
     *
     * @param appid 微信appid
     * @param path  跳转页面路径
     * @param query 需要传参的String
     * @return
     */
    public String getWxAppLink(String appid, String path, String query) {

        StringBuilder wxAppLink = new StringBuilder("weixin://dl/business/?");
        try {
            wxAppLink.append("appid=").append(appid);
            wxAppLink.append("&path=").append(path);
            String encodedQuery =  URLEncoder.encode(URLEncoder.encode(query, StandardCharsets.UTF_8.toString()));
            wxAppLink.append("&query=").append(encodedQuery);
//            wxAppLink.append("&env_version=trial");
//            if(StringUtils.isNotBlank(envVersion)){
//                wxAppLink.append("&env_version=").append(envVersion);
//            }

        } catch (UnsupportedEncodingException e) {
            log.error("生成跳转微信小程序的链接失败" + e.getMessage());
            return "生成失败";
        }
        return wxAppLink.toString();

    }

    @Override
    public String testGetAppLink() {
        String pageLink = "/pages_course/video.html?course\\u003d{\\\"companyId\\\":100,\\\"companyUserId\\\":2020,\\\"corpId\\\":\\\"ww51717e2b71d5e2d3\\\",\\\"courseId\\\":63,\\\"link\\\":\\\"1943203055923101696\\\",\\\"linkType\\\":3,\\\"qwExternalId\\\":20812637,\\\"qwUserId\\\":\\\"9112\\\",\\\"videoId\\\":400}";
        String miniprogramAppid = "wx76cb55db092a41ae";
        if (StringUtils.isNotBlank(pageLink)) {

            //解析pageLink
            String[] split = pageLink.split("\\?");
            if (split.length == 2 && split[0].length() > 0 && split[1].length() > 0) {
                //处理页面路径
                String pageUrl =split[0];
                if(pageUrl.startsWith("/")){
                    pageUrl = pageUrl.substring(1);
                }
                if(pageUrl.contains(".html")){
                    pageUrl = pageUrl.replace(".html", "");
                }
                //处理参数
                String query = split[1];
                query = query.replace("\\u003d", "=");
                String wxAppLink = getWxAppLink(miniprogramAppid, pageUrl, query);
                return wxAppLink;
            } else {
                return "页面链接错误，获取失败";
            }
        }
        return pageLink;

    }


    /**
     * 获取跳转微信小程序的链接地址
     * @param linkStr
     * @return
     */
    private static final String BASE_URL = "https://api.weixin.qq.com/wxa/genwxashortlink?access_token=";
    private final OkHttpClient client = new OkHttpClient();
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String generateShortLinkFull(String appId,String pageUrl, boolean isPermanent) throws WxErrorException, IOException {

        try {

        final WxMaService wxService = WxMaConfiguration.getMaService(appId);
        String accessToken = wxService.getAccessToken();

            pageUrl = pageUrl.replaceFirst("^/", "");

            String url = BASE_URL + accessToken;

            Map<String, Object> body = new HashMap<>();
            body.put("page_url", pageUrl);
            body.put("is_permanent", isPermanent);

            String jsonBody = objectMapper.writeValueAsString(body);

            Request request = new Request.Builder()
                    .url(url)
                    .post(RequestBody.create(MediaType.parse("application/json"), jsonBody))
                    .build();

            try (Response response = client.newCall(request).execute()) {
                String responseBody = response.body().string();

                Map<String, Object> result = objectMapper.readValue(responseBody, Map.class);

                Integer errcode = (Integer) result.get("errcode");


                if(TOKEN_VALID_CODE.equals(String.valueOf(result.get("errcode")))){
                    Integer curVersion = version;
                    synchronized (TOKEN_VALID_CODE){
                        if(curVersion.equals(version)){
                            wxService.getAccessToken(true);
                            version = version.equals(Integer.MAX_VALUE) ? 0 : curVersion + 1;
                        }
                        return generateShortLinkFull(appId,pageUrl,isPermanent);
                    }
                }

                if (errcode != null && errcode != 0) {
                    log.error("微信小程序的链接地址失败:"+result+"--pageUrl:"+pageUrl);
                    throw new IOException("微信API错误: " + errcode + " - " + result.get("errmsg"));
                }

                return (String) result.get("link");
            }


        } catch (WxErrorException | IOException e) {
            throw new RuntimeException(e);
        }

    }

    private String normalizeWxUrlLink(String urlLink) {
        if (StringUtils.isBlank(urlLink)) {
            return urlLink;
        }
        return urlLink.replace("wxmpurl.cn", "wxaurl.cn");
    }

    /**
     * 获取跳转微信小程序的链接地址
     * @param linkStr
     * @return
     */
    @Override
    public String getGotoWxAppLink(String linkStr,String appId) {
        CloseableHttpClient client = null;
        try {
            client = HttpClients.createDefault();
            String[] split = linkStr.split("\\?");
            if (split.length == 2 && split[0].length() > 0 && split[1].length() > 0) {
                //处理页面路径
                String pageUrl = split[0];
                if (pageUrl.startsWith("/")) {
                    pageUrl = pageUrl.substring(1);
                }
                //处理参数
                String query = split[1];
                query = URLEncoder.encode(query, StandardCharsets.UTF_8.toString());
//                String json = configService.selectConfigByKey("course.config");
//                CourseConfig config = JSON.parseObject(json, CourseConfig.class);
//                String miniprogramAppid = config.getMiniprogramAppid();
//                if (StringUtils.isBlank(miniprogramAppid)) {
//                    return "未配置点播小程序id";
//                }
                //获取微信token
                final WxMaService wxService = WxMaConfiguration.getMaService(appId);
                String token = wxService.getAccessToken();
                log.info("小程序TOKEN值-------->刷新前TOKEN：{}", token);
                HttpPost httpPost = new HttpPost("https://api.weixin.qq.com/wxa/generate_urllink?access_token=" + token);
                JSONObject bodyObj = new JSONObject();
                bodyObj.put("path", pageUrl);
                bodyObj.put("query", query);
                log.info("微信小程序请求参数打印：{}", bodyObj.toJSONString());
                StringEntity entity = new StringEntity(bodyObj.toJSONString(),"UTF-8");
                httpPost.setEntity(entity);
                httpPost.setHeader("Content-type", "application/json");
                httpPost.setHeader("cache-control","max-age=0");
                HttpEntity response = client.execute(httpPost).getEntity();
                String responseString = EntityUtils.toString(response);
                log.info("微信小程序接口响应数据:{}", responseString);
                JSONObject jsonObject = JSONObject.parseObject(responseString);

                if(TOKEN_VALID_CODE.equals(jsonObject.getString("errcode"))){
                    Integer curVersion =  Integer.valueOf(version);
                    synchronized (TOKEN_VALID_CODE){
                        if(curVersion.equals(version)){
                            log.info("小程序TOKEN：40001进入强制刷新-------->刷新前TOKEN：{}", token);
                            wxService.getAccessToken(true);
                            version = version.equals(Integer.MAX_VALUE) ? 0 : curVersion + 1;
                            log.info("小程序TOKEN：40001进入强制刷新-------->刷新后TOKEN：{}", wxService.getAccessToken());
                        }
                        return getGotoWxAppLink(linkStr,appId);
                    }
                }

                if(!jsonObject.isEmpty() && jsonObject.containsKey("url_link")){
                    return normalizeWxUrlLink(jsonObject.getString("url_link"));
                }
            } else {
                return "页面链接错误，获取失败";
            }

        } catch (WxErrorException | IOException e) {
            throw new RuntimeException(e);
        }
        return "系统错误";
    }

    @Override
    public R getWxaCodeGenerateScheme(String linkStr,String appId) {
        CloseableHttpClient client = null;
        try {
            client = HttpClients.createDefault();
            String[] split = linkStr.split("\\?");
            log.info("分享参数：{}", Arrays.asList(split));
            if (split.length == 2 && !split[0].isEmpty() && !split[1].isEmpty()) {
                //处理页面路径
                String pageUrl = split[0];
                if (pageUrl.startsWith("/")) {
//                    pageUrl = pageUrl.substring(1);
                    pageUrl = "pages_course/video";
                }
                //处理参数
                String query = split[1];
                query = URLEncoder.encode(query, StandardCharsets.UTF_8.toString());


                QueryWrapper<FsCoursePlaySourceConfig> miniQueryWrapper = new QueryWrapper<FsCoursePlaySourceConfig>()
                        .ne("type", 2)
                        .eq("is_del", 0);
                FsCoursePlaySourceConfigQuery.excludeBanned(miniQueryWrapper);
                List<FsCoursePlaySourceConfig> list = fsCoursePlaySourceConfigService.list(miniQueryWrapper);
//                SysConfig sysConfig3 = sysConfigMapper.selectConfigByConfigKey("courseMa.config");
//                List<CourseMaConfig> courseMaConfigs = JSON.parseArray(sysConfig3.getConfigValue(), CourseMaConfig.class);
                if (list!=null&& !list.isEmpty()){
                    for (FsCoursePlaySourceConfig courseMaConfig : list) {
                        if (appId.equals(courseMaConfig.getAppid())) {

                            //获取微信token

                            String url="https://api.weixin.qq.com/cgi-bin/stable_token";
                            HashMap<String, String> map = new HashMap<>();
                            map.put("grant_type","client_credential");
                            map.put("appid",courseMaConfig.getAppid());
                            map.put("secret",courseMaConfig.getSecret());


                            String accessToken = HttpUtil.endApi(url, null, map);

                            Map<String, String> accessTokenMap  = new Gson().fromJson(accessToken, new TypeToken<Map<String, Object>>(){}.getType());


                            HttpPost httpPost = new HttpPost("https://api.weixin.qq.com/wxa/generate_urllink?access_token=" + accessTokenMap.get("access_token"));
                            JSONObject bodyObj = new JSONObject();
                            bodyObj.put("path", pageUrl);
                            bodyObj.put("query", query);
                            bodyObj.put("env_version", "release");

                            StringEntity entity = new StringEntity(bodyObj.toJSONString(),"UTF-8");
                            httpPost.setEntity(entity);
                            httpPost.setHeader("Content-type", "application/json");
                            httpPost.setHeader("cache-control","max-age=0");
                            HttpEntity response = client.execute(httpPost).getEntity();
                            String responseString = EntityUtils.toString(response);

                            JSONObject jsonObject = JSONObject.parseObject(responseString);



                            if(null != jsonObject && !jsonObject.isEmpty() && jsonObject.containsKey("url_link")){
                                return R.ok().put("urlLink", normalizeWxUrlLink(jsonObject.getString("url_link")));
                            }else {
                                return R.error("获取失败:"+jsonObject.getString("errmsg"));
                            }
                        }
                    }
                }
            } else {
                return R.error("页面链接错误，获取失败");
            }

        } catch (ClientProtocolException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return R.ok();
    }

    @Override
    public R getProjectCode() {
        String code = cloudHostProper.getProjectCode();
        if (StringUtils.isEmpty(code)){
            return R.error("项目编号配置不存在！");
        }
        return R.ok().put("code",code);
    }
}
