package org.xyzh.workcase.service;

import com.alibaba.fastjson2.JSONObject;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.xyzh.api.auth.service.AuthService;
import org.xyzh.api.workcase.dto.TbChatRoomMemberDTO;
import org.xyzh.api.workcase.dto.TbChatRoomMessageDTO;
import org.xyzh.api.workcase.dto.TbVideoMeetingDTO;
import org.xyzh.api.workcase.service.ChatRoomService;
import org.xyzh.api.workcase.service.JitsiTokenService;
import org.xyzh.api.workcase.service.VideoMeetingService;
import org.xyzh.api.workcase.vo.ChatMemberVO;
import org.xyzh.api.workcase.vo.VideoMeetingVO;
import org.xyzh.common.auth.utils.LoginUtil;
import org.xyzh.common.core.domain.LoginDomain;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.utils.id.IdUtil;
import org.xyzh.workcase.config.JitsiProperties;
import org.xyzh.workcase.mapper.TbChatRoomMemberMapper;
import org.xyzh.workcase.mapper.TbVideoMeetingMapper;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @description    视频会议服务实现类
 * @filename    VideoMeetingServiceImpl.java
 * @author    claude
 * @copyright xyzh
 * @since 2025-12-25
 */
@DubboService(version = "1.0.0", group = "workcase", timeout = 30000, retries = 0)
public class VideoMeetingServiceImpl implements VideoMeetingService {
    private static final Logger logger = LoggerFactory.getLogger(VideoMeetingServiceImpl.class);

    @Autowired
    private TbVideoMeetingMapper videoMeetingMapper;

    @Autowired
    private TbChatRoomMemberMapper chatRoomMemberMapper;

    @Autowired
    private JitsiTokenService jitsiTokenService;

    @Autowired
    private ChatRoomService chatRoomService;

    @Autowired
    private JitsiProperties jitsiProperties;

    @DubboReference(version = "1.0.0", group = "auth", timeout = 30000, retries = 0)
    private AuthService authService;

    // 会议创建锁映射表：每个meetingId对应一个ReentrantLock
    private final ConcurrentHashMap<String, ReentrantLock> meetingLocks = new ConcurrentHashMap<>();

    @Override
    @Transactional
    public ResultDomain<VideoMeetingVO> createMeeting(TbVideoMeetingDTO meetingDTO) {
        // 获取当前用户ID
        LoginDomain loginDomain = LoginUtil.getCurrentLogin();
        String userId = loginDomain.getUser().getUserId();
        logger.info("创建视频会议预约: roomId={}, workcaseId={}, userId={}, startTime={}, endTime={}",
            meetingDTO.getRoomId(), meetingDTO.getWorkcaseId(), userId,
            meetingDTO.getStartTime(), meetingDTO.getEndTime());

        try {
            // 1. 验证用户是否为聊天室成员
            if (!isMemberOfRoom(meetingDTO.getRoomId(), userId)) {
                logger.warn("用户不是聊天室成员，无法创建会议: roomId={}, userId={}",
                    meetingDTO.getRoomId(), userId);
                return ResultDomain.failure("您不是聊天室成员，无法创建会议");
            }

            // 2. 检查聊天室是否已有时间冲突的会议
            TbVideoMeetingDTO conflictFilter = new TbVideoMeetingDTO();
            conflictFilter.setRoomId(meetingDTO.getRoomId());
            conflictFilter.setStatus("scheduled"); // 只检查已安排的会议
            List<VideoMeetingVO> existingMeetings = videoMeetingMapper.selectVideoMeetingList(conflictFilter);

            if (existingMeetings != null && !existingMeetings.isEmpty()) {
                for (VideoMeetingVO existing : existingMeetings) {
                    // 检查时间是否冲突
                    if (isTimeConflict(meetingDTO.getStartTime(), meetingDTO.getEndTime(),
                            existing.getStartTime(), existing.getEndTime())) {
                        logger.warn("会议时间冲突: roomId={}, existingMeetingId={}",
                            meetingDTO.getRoomId(), existing.getMeetingId());
                        return ResultDomain.failure("该时间段已有会议安排，请选择其他时间");
                    }
                }
            }

            // 3. 生成会议ID和房间名（房间名暂时生成，真正创建Jitsi时会重新生成）
            String meetingId = IdUtil.generateUUID();
            String jitsiRoomName = jitsiTokenService.generateRoomName(meetingDTO.getWorkcaseId());

            // 4. 获取用户信息（从聊天室成员表）
            TbChatRoomMemberDTO memberFilter = new TbChatRoomMemberDTO();
            memberFilter.setRoomId(meetingDTO.getRoomId());
            memberFilter.setUserId(userId);
            List<ChatMemberVO> members = chatRoomMemberMapper.selectChatRoomMemberList(memberFilter);

            String userName = loginDomain.getUserInfo().getUsername();
            String userType = "guest".equals(loginDomain.getUser().getStatus())?"guest":"user";
            if (members != null && !members.isEmpty()) {
                ChatMemberVO member = members.get(0);
                userName = member.getUserName();
                userType = member.getUserType();
            }

            // 5. 填充会议预约信息（不生成JWT Token和iframe URL）
            meetingDTO.setMeetingId(meetingId);
            meetingDTO.setJitsiRoomName(jitsiRoomName);
            meetingDTO.setJwtToken(null); // 预约阶段不生成token
            meetingDTO.setIframeUrl(null); // 预约阶段不生成URL
            meetingDTO.setStatus("scheduled"); // 状态为已安排
            meetingDTO.setCreator(userId);
            meetingDTO.setCreatorType(userType);
            meetingDTO.setCreatorName(userName);

            // 【调试日志】打印会议创建者信息
            logger.info("【创建会议】meetingId={}, 创建者userId=[{}](类型:{}), creatorName={}, creatorType={}",
                meetingId, userId, userId.getClass().getSimpleName(), userName, userType);

            meetingDTO.setParticipantCount(0);
            meetingDTO.setOptsn(IdUtil.getOptsn());

            if (meetingDTO.getMaxParticipants() == null) {
                meetingDTO.setMaxParticipants(10);
            }

            if (meetingDTO.getAdvance() == null) {
                meetingDTO.setAdvance(5); // 默认提前5分钟可入会
            }
            meetingDTO.setJitsiServerUrl(jitsiProperties.getServer().getUrl());
            // 6. 插入数据库
            int rows = videoMeetingMapper.insertVideoMeeting(meetingDTO);
            if (rows > 0) {
                logger.info("视频会议预约创建成功: meetingId={}, jitsiRoomName={}, startTime={}, endTime={}",
                    meetingId, jitsiRoomName, meetingDTO.getStartTime(), meetingDTO.getEndTime());

                // 7. 发送会议通知消息到聊天室（内容为meetingId而非URL）
                sendMeetingNotification(meetingDTO, userName);

                // 8. 返回VO
                VideoMeetingVO meetingVO = new VideoMeetingVO();
                BeanUtils.copyProperties(meetingDTO, meetingVO);
                return ResultDomain.success("创建会议预约成功", meetingVO);
            } else {
                logger.error("插入会议记录失败: meetingId={}", meetingId);
                return ResultDomain.failure("创建会议预约失败");
            }
        } catch (Exception e) {
            logger.error("创建视频会议预约异常: roomId={}, error={}",
                meetingDTO.getRoomId(), e.getMessage(), e);
            return ResultDomain.failure("创建会议预约失败: " + e.getMessage());
        }
    }

    @Override
    public ResultDomain<VideoMeetingVO> getMeetingInfo(String meetingId, String userId) {
        logger.info("获取会议信息: meetingId={}, userId={}", meetingId, userId);

        try {
            TbVideoMeetingDTO filter = new TbVideoMeetingDTO();
            filter.setMeetingId(meetingId);
            List<VideoMeetingVO> meetings = videoMeetingMapper.selectVideoMeetingList(filter);

            if (meetings == null || meetings.isEmpty()) {
                logger.warn("会议不存在: meetingId={}", meetingId);
                return ResultDomain.failure("会议不存在");
            }

            VideoMeetingVO meeting = meetings.get(0);

            // 验证访问权限
            ResultDomain<Boolean> accessCheck = validateMeetingAccess(meetingId, userId);
            if (!accessCheck.getSuccess() || !accessCheck.getData()) {
                logger.warn("用户无权访问会议: meetingId={}, userId={}", meetingId, userId);
                return ResultDomain.failure("您无权访问此会议");
            }

            logger.info("获取会议信息成功: meetingId={}", meetingId);
            return ResultDomain.success("获取会议信息成功", meeting);
        } catch (Exception e) {
            logger.error("获取会议信息异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
            return ResultDomain.failure("获取会议信息失败: " + e.getMessage());
        }
    }

    @Override
    public ResultDomain<Boolean> validateMeetingAccess(String meetingId, String userId) {
        logger.info("验证会议访问权限: meetingId={}, userId={}", meetingId, userId);

        try {
            // 1. 获取会议信息
            TbVideoMeetingDTO filter = new TbVideoMeetingDTO();
            filter.setMeetingId(meetingId);
            List<VideoMeetingVO> meetings = videoMeetingMapper.selectVideoMeetingList(filter);

            if (meetings == null || meetings.isEmpty()) {
                logger.warn("会议不存在: meetingId={}", meetingId);
                return ResultDomain.success("会议不存在", false);
            }

            VideoMeetingVO meeting = meetings.get(0);

            // 2. 检查用户是否为聊天室成员
            boolean isMember = isMemberOfRoom(meeting.getRoomId(), userId);

            logger.info("会议访问权限验证结果: meetingId={}, userId={}, hasAccess={}",
                meetingId, userId, isMember);
            return ResultDomain.success("会议访问权限验证成功", isMember);
        } catch (Exception e) {
            logger.error("验证会议访问权限异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
            return ResultDomain.failure("验证访问权限失败: " + e.getMessage());
        }
    }

    @Override
    @Transactional
    public ResultDomain<VideoMeetingVO> generateUserMeetingUrl(String meetingId, String userId) {
        logger.info("生成用户专属会议URL: meetingId={}, userId={}", meetingId, userId);

        try {
            // 1. 获取会议信息
            TbVideoMeetingDTO filter = new TbVideoMeetingDTO();
            filter.setMeetingId(meetingId);
            List<VideoMeetingVO> meetings = videoMeetingMapper.selectVideoMeetingList(filter);

            if (meetings == null || meetings.isEmpty()) {
                logger.warn("会议不存在: meetingId={}", meetingId);
                return ResultDomain.failure("会议不存在");
            }

            VideoMeetingVO meeting = meetings.get(0);

            // 2. 验证访问权限
            ResultDomain<Boolean> accessCheck = validateMeetingAccess(meetingId, userId);
            if (!accessCheck.getSuccess() || !accessCheck.getData()) {
                logger.warn("用户无权访问会议: meetingId={}, userId={}", meetingId, userId);
                return ResultDomain.failure("您无权访问此会议");
            }

            // 3. 检查会议时间窗口（仅对scheduled状态的会议检查）
            if ("scheduled".equals(meeting.getStatus())) {
                Date now = new Date();
                
                // 计算提前入会时间点（开始时间 - advance 分钟）
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(meeting.getStartTime());
                calendar.add(Calendar.MINUTE, -meeting.getAdvance());
                Date advanceTime = calendar.getTime();

                // 检查当前时间是否在允许入会的时间窗口内
                if (now.before(advanceTime)) {
                    logger.warn("会议未到入会时间: meetingId={}, 当前时间={}, 提前入会时间={}",
                        meetingId, now, advanceTime);
                    return ResultDomain.failure("会议未到入会时间，请在 " + advanceTime + " 之后加入");
                }

                if (now.after(meeting.getEndTime())) {
                    logger.warn("会议已结束: meetingId={}, 当前时间={}, 结束时间={}",
                        meetingId, now, meeting.getEndTime());
                    return ResultDomain.failure("会议已结束");
                }

                // 4. 使用ReentrantLock进行双检锁：首次用户入会时创建Jitsi会议室
                // 获取或创建该会议的锁对象
                ReentrantLock lock = meetingLocks.computeIfAbsent(meetingId, k -> new ReentrantLock());

                logger.info("尝试获取会议创建锁: meetingId={}", meetingId);
                lock.lock(); // 阻塞等待获取锁
                try {
                    logger.info("成功获取会议创建锁: meetingId={}, userId={}", meetingId, userId);

                    // 双重检查：再次查询数据库确认会议状态（防止其他线程已创建）
                    List<VideoMeetingVO> recheck = videoMeetingMapper.selectVideoMeetingList(filter);
                    if (recheck != null && !recheck.isEmpty()) {
                        VideoMeetingVO recheckMeeting = recheck.get(0);

                        if ("scheduled".equals(recheckMeeting.getStatus())) {
                            logger.info("首次创建Jitsi会议室: meetingId={}", meetingId);

                            // 更新会议状态为进行中
                            TbVideoMeetingDTO updateDTO = new TbVideoMeetingDTO();
                            updateDTO.setMeetingId(meetingId);
                            updateDTO.setStatus("ongoing");
                            updateDTO.setActualStartTime(new Date());

                            int rows = videoMeetingMapper.updateVideoMeeting(updateDTO);
                            if (rows > 0) {
                                logger.info("Jitsi会议室创建成功，会议状态已更新: meetingId={}", meetingId);
                                meeting.setStatus("ongoing");
                                meeting.setActualStartTime(new Date());
                            } else {
                                logger.error("更新会议状态失败: meetingId={}", meetingId);
                                return ResultDomain.failure("创建会议失败");
                            }
                        } else {
                            logger.info("会议已被其他用户创建: meetingId={}, status={}",
                                meetingId, recheckMeeting.getStatus());
                            meeting.setStatus(recheckMeeting.getStatus());
                            meeting.setActualStartTime(recheckMeeting.getActualStartTime());
                        }
                    }
                } finally {
                    lock.unlock();
                    logger.info("释放会议创建锁: meetingId={}", meetingId);

                    // 清理锁对象：如果没有其他线程在等待，则移除锁
                    if (!lock.hasQueuedThreads()) {
                        meetingLocks.remove(meetingId);
                        logger.debug("清理会议锁对象: meetingId={}", meetingId);
                    }
                }
            }

            // 5. 获取用户信息
            TbChatRoomMemberDTO memberFilter = new TbChatRoomMemberDTO();
            memberFilter.setRoomId(meeting.getRoomId());
            memberFilter.setUserId(userId);
            List<ChatMemberVO> members = chatRoomMemberMapper.selectChatRoomMemberList(memberFilter);

            String userName = "用户";
            // 会议创建人才是主持人
            boolean isModerator = userId.equals(meeting.getCreator());

            // 【调试日志】打印主持人判断详情
            logger.info("【主持人判断】meetingId={}, 当前用户ID=[{}](类型:{}), 创建者ID=[{}](类型:{}), isModerator={}",
                meetingId,
                userId, userId.getClass().getSimpleName(),
                meeting.getCreator(), meeting.getCreator() != null ? meeting.getCreator().getClass().getSimpleName() : "null",
                isModerator);

            if (members != null && !members.isEmpty()) {
                ChatMemberVO member = members.get(0);
                userName = member.getUserName();
            }

            // 6. 生成用户专属JWT Token（用于Jitsi内部认证）
            String userJwtToken = jitsiTokenService.generateJwtToken(
                meeting.getJitsiRoomName(),
                userId,
                userName,
                isModerator
            );

            logger.info("【JWT Token生成】meetingId={}, userId={}, userName={}, isModerator={}",
                meetingId, userId, userName, isModerator);

            // 7. 构建真正的Jitsi iframe URL
            String jitsiIframeUrl = jitsiTokenService.buildIframeUrl(
                meeting.getJitsiRoomName(),
                userJwtToken,
                meeting.getConfig()
            );

            // 8. 构建会议页面URL（用于Web端路由跳转和小程序外部访问）
            // 获取当前用户的登录token（用于页面token认证）
            String userToken = LoginUtil.getToken();
            // 注意：URL不包含/workcase前缀，因为workcase应用的路由base已经是/workcase
            String meetingPageUrl = "/meeting?meetingId=" + meetingId +
                "&token=" + (userToken != null ? userToken : "");

            // 9. 更新VO
            meeting.setJwtToken(userJwtToken);
            meeting.setJitsiIframeUrl(jitsiIframeUrl);  // 真正的Jitsi URL
            meeting.setIframeUrl(meetingPageUrl);       // 会议页面URL（用于router跳转）

            logger.info("生成用户专属会议URL成功: meetingId={}, userId={}, status={}",
                meetingId, userId, meeting.getStatus());
            return ResultDomain.success("生成用户专属会议URL成功", meeting);
        } catch (Exception e) {
            logger.error("生成用户专属会议URL异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
            return ResultDomain.failure("生成会议URL失败: " + e.getMessage());
        }
    }

    @Override
    @Transactional
    public ResultDomain<Boolean> startMeeting(String meetingId) {
        logger.info("开始会议: meetingId={}", meetingId);

        try {
            TbVideoMeetingDTO updateDTO = new TbVideoMeetingDTO();
            updateDTO.setMeetingId(meetingId);
            updateDTO.setStatus("ongoing");
            updateDTO.setActualStartTime(new Date());

            int rows = videoMeetingMapper.updateVideoMeeting(updateDTO);
            if (rows > 0) {
                logger.info("会议开始成功: meetingId={}", meetingId);
                return ResultDomain.success("会议开始成功", true);
            } else {
                logger.warn("会议开始失败（可能不存在）: meetingId={}", meetingId);
                return ResultDomain.failure("会议不存在或已开始");
            }
        } catch (Exception e) {
            logger.error("开始会议异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
            return ResultDomain.failure("开始会议失败: " + e.getMessage());
        }
    }

    @Override
    @Transactional
    public ResultDomain<VideoMeetingVO> endMeeting(String meetingId) {
        logger.info("结束会议: meetingId={}", meetingId);

        try {
            // 1. 获取会议信息
            TbVideoMeetingDTO filter = new TbVideoMeetingDTO();
            filter.setMeetingId(meetingId);
            List<VideoMeetingVO> meetings = videoMeetingMapper.selectVideoMeetingList(filter);

            if (meetings == null || meetings.isEmpty()) {
                logger.warn("会议不存在: meetingId={}", meetingId);
                return ResultDomain.failure("会议不存在");
            }

            VideoMeetingVO meeting = meetings.get(0);

            // 2. 计算会议时长
            Integer durationSeconds = null;
            if (meeting.getActualStartTime() != null) {
                long duration = (System.currentTimeMillis() - meeting.getActualStartTime().getTime()) / 1000;
                durationSeconds = (int) duration;
            }

            // 3. 更新会议状态
            TbVideoMeetingDTO updateDTO = new TbVideoMeetingDTO();
            updateDTO.setMeetingId(meetingId);
            updateDTO.setStatus("ended");
            updateDTO.setActualEndTime(new Date());
            updateDTO.setDurationSeconds(durationSeconds);

            int rows = videoMeetingMapper.updateVideoMeeting(updateDTO);
            if (rows > 0) {
                // 4. 更新VO
                meeting.setStatus("ended");
                meeting.setActualEndTime(new Date());
                meeting.setDurationSeconds(durationSeconds);

                logger.info("会议结束成功: meetingId={}, duration={}秒", meetingId, durationSeconds);
                return ResultDomain.success("会议结束成功", meeting);
            } else {
                logger.warn("会议结束失败: meetingId={}", meetingId);
                return ResultDomain.failure("结束会议失败");
            }
        } catch (Exception e) {
            logger.error("结束会议异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
            return ResultDomain.failure("结束会议失败: " + e.getMessage());
        }
    }

    @Override
    public ResultDomain<VideoMeetingVO> getActiveMeetingByRoom(String roomId) {
        logger.info("获取聊天室活跃会议: roomId={}", roomId);

        try {
            TbVideoMeetingDTO filter = new TbVideoMeetingDTO();
            filter.setRoomId(roomId);
            filter.setStatus("ongoing");
            List<VideoMeetingVO> meetings = videoMeetingMapper.selectVideoMeetingList(filter);

            if (meetings == null || meetings.isEmpty()) {
                logger.info("聊天室无活跃会议: roomId={}", roomId);
                return ResultDomain.failure("无活跃会议");
            }

            VideoMeetingVO meeting = meetings.get(0);
            logger.info("找到活跃会议: roomId={}, meetingId={}", roomId, meeting.getMeetingId());
            return ResultDomain.success("找到活跃会议", meeting);
        } catch (Exception e) {
            logger.error("获取活跃会议异常: roomId={}, error={}", roomId, e.getMessage(), e);
            return ResultDomain.failure("获取活跃会议失败: " + e.getMessage());
        }
    }

    @Override
    public boolean isMemberOfRoom(String roomId, String userId) {
        logger.debug("检查用户是否为聊天室成员: roomId={}, userId={}", roomId, userId);

        try {
            TbChatRoomMemberDTO filter = new TbChatRoomMemberDTO();
            filter.setRoomId(roomId);
            filter.setUserId(userId);
            filter.setStatus("active");

            List<ChatMemberVO> members = chatRoomMemberMapper.selectChatRoomMemberList(filter);
            boolean isMember = members != null && !members.isEmpty();

            logger.debug("用户成员检查结果: roomId={}, userId={}, isMember={}",
                roomId, userId, isMember);
            return isMember;
        } catch (Exception e) {
            logger.error("检查用户成员身份异常: roomId={}, userId={}, error={}",
                roomId, userId, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 发送会议通知消息到聊天室
     * @param meetingDTO 会议信息
     * @param creatorName 创建者名称
     */
    private void sendMeetingNotification(TbVideoMeetingDTO meetingDTO, String creatorName) {
        logger.info("发送会议通知消息: roomId={}, meetingId={}",
            meetingDTO.getRoomId(), meetingDTO.getMeetingId());

        // 构建消息内容
        TbChatRoomMessageDTO message = new TbChatRoomMessageDTO();
        message.setMessageId(IdUtil.generateUUID());
        message.setRoomId(meetingDTO.getRoomId());
        message.setSenderId(meetingDTO.getCreator());
        message.setSenderType(meetingDTO.getCreatorType());
        message.setSenderName(creatorName);
        message.setMessageType("meet"); // 会议类型消息
        message.setContent(meetingDTO.getMeetingId()); // 会议ID作为内容（前端根据ID查询会议详情）
        message.setStatus("sent");
        message.setReadCount(0);
        message.setSendTime(new Date());

        // 构建扩展信息（会议详情）
        JSONObject contentExtra = new JSONObject();
        contentExtra.put("meetingId", meetingDTO.getMeetingId());
        contentExtra.put("meetingName", meetingDTO.getMeetingName());
        contentExtra.put("jitsiRoomName", meetingDTO.getJitsiRoomName());
        contentExtra.put("startTime", meetingDTO.getStartTime());
        contentExtra.put("endTime", meetingDTO.getEndTime());
        contentExtra.put("advance", meetingDTO.getAdvance());
        contentExtra.put("maxParticipants", meetingDTO.getMaxParticipants());
        contentExtra.put("creatorName", creatorName);
        contentExtra.put("workcaseId", meetingDTO.getWorkcaseId());
        contentExtra.put("status", meetingDTO.getStatus());
        message.setContentExtra(contentExtra);

        // 发送消息
        ResultDomain<TbChatRoomMessageDTO> sendResult = chatRoomService.sendMessage(message);
        if (sendResult.getSuccess()) {
            logger.info("会议通知消息发送成功: messageId={}", message.getMessageId());
        } else {
            logger.warn("会议通知消息发送失败: {}", sendResult.getMessage());
        }
    }

    /**
     * 检查时间是否冲突
     * @param start1 时间段1开始时间
     * @param end1 时间段1结束时间
     * @param start2 时间段2开始时间
     * @param end2 时间段2结束时间
     * @return 是否冲突
     */
    private boolean isTimeConflict(Date start1, Date end1, Date start2, Date end2) {
        // 时间段1的结束时间 > 时间段2的开始时间 AND 时间段1的开始时间 < 时间段2的结束时间
        return end1.after(start2) && start1.before(end2);
    }

    @Override
    @Transactional
    public ResultDomain<VideoMeetingVO> getMeetingEntryByToken(String meetingId, String token) {
        logger.info("通过token获取会议入口: meetingId={}", meetingId);

        try {
            // 1. 验证token并获取用户信息
            if (token == null || token.trim().isEmpty()) {
                logger.warn("token为空: meetingId={}", meetingId);
                return ResultDomain.failure("认证token不能为空");
            }

            // 去除Bearer前缀（如果有）
            if (token.startsWith("Bearer ")) {
                token = token.substring(7);
            }

            ResultDomain<LoginDomain> loginResult = authService.getLoginByToken(token);
            if (!loginResult.getSuccess() || loginResult.getData() == null) {
                logger.warn("token验证失败: meetingId={}, error={}", meetingId, loginResult.getMessage());
                return ResultDomain.failure("认证失败: " + loginResult.getMessage());
            }

            LoginDomain loginDomain = loginResult.getData();
            String userId = loginDomain.getUser().getUserId();
            logger.info("token验证成功: meetingId={}, userId={}", meetingId, userId);

            // 2. 调用现有的generateUserMeetingUrl方法生成会议URL
            // 但需要先设置当前登录上下文（因为generateUserMeetingUrl可能依赖LoginUtil）
            // 这里直接复用generateUserMeetingUrl的核心逻辑

            // 获取会议信息
            TbVideoMeetingDTO filter = new TbVideoMeetingDTO();
            filter.setMeetingId(meetingId);
            List<VideoMeetingVO> meetings = videoMeetingMapper.selectVideoMeetingList(filter);

            if (meetings == null || meetings.isEmpty()) {
                logger.warn("会议不存在: meetingId={}", meetingId);
                return ResultDomain.failure("会议不存在");
            }

            VideoMeetingVO meeting = meetings.get(0);

            // 3. 验证访问权限（用户必须是聊天室成员）
            if (!isMemberOfRoom(meeting.getRoomId(), userId)) {
                logger.warn("用户无权访问会议: meetingId={}, userId={}", meetingId, userId);
                return ResultDomain.failure("您无权访问此会议");
            }

            // 4. 检查会议状态和时间窗口
            if ("ended".equals(meeting.getStatus())) {
                logger.warn("会议已结束: meetingId={}", meetingId);
                return ResultDomain.failure("会议已结束");
            }

            if ("scheduled".equals(meeting.getStatus())) {
                Date now = new Date();
                
                // 计算提前入会时间点
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(meeting.getStartTime());
                calendar.add(Calendar.MINUTE, -meeting.getAdvance());
                Date advanceTime = calendar.getTime();

                if (now.before(advanceTime)) {
                    logger.warn("会议未到入会时间: meetingId={}", meetingId);
                    return ResultDomain.failure("会议未到入会时间，请在 " + advanceTime + " 之后加入");
                }

                if (now.after(meeting.getEndTime())) {
                    logger.warn("会议已过期: meetingId={}", meetingId);
                    return ResultDomain.failure("会议已结束");
                }

                // 首次入会时更新会议状态
                ReentrantLock lock = meetingLocks.computeIfAbsent(meetingId, k -> new ReentrantLock());
                lock.lock();
                try {
                    List<VideoMeetingVO> recheck = videoMeetingMapper.selectVideoMeetingList(filter);
                    if (recheck != null && !recheck.isEmpty() && "scheduled".equals(recheck.get(0).getStatus())) {
                        TbVideoMeetingDTO updateDTO = new TbVideoMeetingDTO();
                        updateDTO.setMeetingId(meetingId);
                        updateDTO.setStatus("ongoing");
                        updateDTO.setActualStartTime(new Date());
                        videoMeetingMapper.updateVideoMeeting(updateDTO);
                        meeting.setStatus("ongoing");
                        meeting.setActualStartTime(new Date());
                        logger.info("会议状态已更新为进行中: meetingId={}", meetingId);
                    }
                } finally {
                    lock.unlock();
                    if (!lock.hasQueuedThreads()) {
                        meetingLocks.remove(meetingId);
                    }
                }
            }

            // 5. 获取用户信息
            TbChatRoomMemberDTO memberFilter = new TbChatRoomMemberDTO();
            memberFilter.setRoomId(meeting.getRoomId());
            memberFilter.setUserId(userId);
            List<ChatMemberVO> members = chatRoomMemberMapper.selectChatRoomMemberList(memberFilter);

            String userName = loginDomain.getUserInfo().getUsername();
            boolean isModerator = userId.equals(meeting.getCreator());

            if (members != null && !members.isEmpty()) {
                userName = members.get(0).getUserName();
            }

            // 6. 生成用户专属JWT Token
            String userJwtToken = jitsiTokenService.generateJwtToken(
                meeting.getJitsiRoomName(),
                userId,
                userName,
                isModerator
            );

            // 7. 构建真正的Jitsi iframe URL
            String jitsiIframeUrl = jitsiTokenService.buildIframeUrl(
                meeting.getJitsiRoomName(),
                userJwtToken,
                meeting.getConfig()
            );

            // 8. 构建会议页面URL（用于Web端路由跳转和小程序外部访问）
            // 注意：使用提供的token参数而非LoginUtil.getToken()
            String meetingPageUrl = "/meeting?meetingId=" + meetingId +
                "&token=" + token;

            // 9. 更新VO并返回
            meeting.setJwtToken(userJwtToken);
            meeting.setJitsiIframeUrl(jitsiIframeUrl);  // 真正的Jitsi URL
            meeting.setIframeUrl(meetingPageUrl);       // 会议页面URL（用于router跳转）

            logger.info("通过token获取会议入口成功: meetingId={}, userId={}, isModerator={}",
                meetingId, userId, isModerator);
            return ResultDomain.success("获取会议入口成功", meeting);

        } catch (Exception e) {
            logger.error("通过token获取会议入口异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
            return ResultDomain.failure("获取会议入口失败: " + e.getMessage());
        }
    }

    @Override
    public ResultDomain<String> generateMeetingEntryUrl(String meetingId, String baseUrl) {
        logger.info("生成会议入口URL: meetingId={}, baseUrl={}", meetingId, baseUrl);

        try {
            // 获取当前用户token
            LoginDomain loginDomain = LoginUtil.getCurrentLogin();
            if (loginDomain == null || loginDomain.getToken() == null) {
                logger.warn("无法获取当前用户token: meetingId={}", meetingId);
                return ResultDomain.failure("无法获取当前用户认证信息");
            }

            // 构建完整URL: {baseUrl}/meeting/{meetingId}?token={token}
            String entryUrl = String.format("%s/meeting/%s?token=%s",
                baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl,
                meetingId,
                loginDomain.getToken()
            );

            logger.info("会议入口URL生成成功: meetingId={}", meetingId);
            return ResultDomain.success("生成会议入口URL成功", entryUrl);

        } catch (Exception e) {
            logger.error("生成会议入口URL异常: meetingId={}, error={}", meetingId, e.getMessage(), e);
            return ResultDomain.failure("生成会议入口URL失败: " + e.getMessage());
        }
    }
}
