package com.ningdatech.pmapi.meeting.manage;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.model.IdVo;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.basic.util.ValidUtil;
import com.ningdatech.cache.lock.DistributedLock;
import com.ningdatech.pmapi.common.helper.RegionCacheHelper;
import com.ningdatech.pmapi.expert.entity.ExpertUserFullInfo;
import com.ningdatech.pmapi.expert.helper.PermissionCheckHelper;
import com.ningdatech.pmapi.expert.service.IExpertUserFullInfoService;
import com.ningdatech.pmapi.meeting.builder.ExpertInviteBuilder;
import com.ningdatech.pmapi.meeting.entity.domain.*;
import com.ningdatech.pmapi.meeting.entity.dto.*;
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum;
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertInviteTypeEnum;
import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingStatusEnum;
import com.ningdatech.pmapi.meeting.entity.req.*;
import com.ningdatech.pmapi.meeting.entity.vo.*;
import com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper;
import com.ningdatech.pmapi.meeting.helper.MeetingManageHelper;
import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper;
import com.ningdatech.pmapi.meeting.service.*;
import com.ningdatech.pmapi.meeting.task.ExpertInviteTask;
import com.ningdatech.pmapi.meta.helper.DictionaryCache;
import com.ningdatech.pmapi.meta.helper.TagCache;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.projectlib.service.IProjectService;
import com.ningdatech.pmapi.sys.model.dto.RegionDTO;
import com.ningdatech.pmapi.user.security.auth.model.UserInfoDetails;
import com.ningdatech.pmapi.user.util.LoginUserUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper.getExpertInviteRule;

/**
 * <p>
 * MeetingManage
 * </p>
 *
 * @author WendyYang
 * @since 13:35 2022/7/26
 */
@Component
@RequiredArgsConstructor
public class MeetingManage {

    private final RegionCacheHelper regionCache;
    private final IMeetingService meetingService;
    private final IExpertInviteAvoidRuleService inviteAvoidRuleService;
    private final IExpertInviteRuleService inviteRuleService;
    private final IExpertUserFullInfoService expertUserFullInfoService;
    private final TagCache tagCache;
    private final DictionaryCache dictionaryCache;
    private final IMeetingExpertService meetingExpertService;
    private final ExpertInviteManage expertInviteManage;
    private final ExpertInviteTask expertInviteTask;
    private final MeetingManageHelper meetingManageHelper;
    private final YxtCallOrSmsHelper yxtCallOrSmsHelper;
    private final DistributedLock distributedLock;
    private final PermissionCheckHelper permissionCheckHelper;
    private final IProjectService projectService;
    private final IMeetingInnerProjectService meetingInnerProjectService;
    private final IMeetingOuterProjectService meetingOuterProjectService;

    private final ExpertInviteHelper expertInviteHelper;
    private static final String INVITED_RULE_CREATE = "INVITED_RULE_CREATE:";

    private static final String MEETING_CREATE_KEY = "MEETING_CREATE:";

    /**
     * 锁定时间10 * 60秒
     */
    public static final int RETRY_TIMES = 3;

    @Transactional(rollbackFor = Exception.class)
    public IdVo<Long> meetingCreateAndInviteExpert(MeetingCreateReq req) {
        MeetingBasicDTO meetingBasic = req.getMeetingBasicInfo();
        meetingManageHelper.checkReviewProject(meetingBasic);
        String md5ByParam = SecureUtil.md5(meetingBasic.toString());
        String key = MEETING_CREATE_KEY + md5ByParam;
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("会议正在创建中");
        }
        try {
            ExpertInviteReq inviteRule = req.getExpertInviteRule();
            // 保存会议基本信息
            Meeting meeting = BeanUtil.copyProperties(meetingBasic, Meeting.class);
            meeting.setStatus(MeetingStatusEnum.NORMAL.getCode());
            UserInfoDetails userDetail = LoginUserUtil.loginUserDetail();
            meeting.setHoldOrg(userDetail.getOrganizationName());
            meeting.setHoldOrgCode(userDetail.getOrganizationCode());
            meeting.setRegionCode(userDetail.getRegionCode());
            meeting.setCreator(userDetail.getUsername());
            meeting.setInviteStatus(Boolean.FALSE);
            meeting.setConfirmedRoster(Boolean.FALSE);
            meeting.setInviteType(inviteRule.getInviteType());
            meetingService.save(meeting);
            if (meetingBasic.getIsInnerProject()) {
                List<MeetingInnerProject> projects = meetingBasic.getProjectIdList().stream().map(w -> {
                    MeetingInnerProject project = new MeetingInnerProject();
                    project.setMeetingId(meeting.getId());
                    project.setProjectId(w);
                    return project;
                }).collect(Collectors.toList());
                meetingInnerProjectService.saveBatch(projects);
            } else {
                List<MeetingOuterProject> projects = meetingBasic.getProjects().stream().map(w -> {
                    MeetingOuterProject project = BeanUtil.copyProperties(w, MeetingOuterProject.class);
                    project.setMeetingId(meeting.getId());
                    return project;
                }).collect(Collectors.toList());
                meetingOuterProjectService.saveBatch(projects);
            }
            // 抽取专家
            req.getExpertInviteRule().setMeetingId(meeting.getId());
            expertInviteByCreate(inviteRule);
            return IdVo.of(meeting.getId());
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void continueInvite(Long meetingId) {
        String key = "CONTINUE_INVITE:" + meetingId;
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("已进行续抽，请勿重复点击");
        }
        try {
            Meeting meeting = meetingService.getById(meetingId);
            if (!meeting.getInviteStatus()) {
                throw BizException.wrap("该会议正在抽取专家，暂无法续抽");
            }
            if (!MeetingStatusEnum.NORMAL.eq(meeting.getStatus())) {
                throw BizException.wrap("续抽失败，请刷新后重试");
            }
            boolean invitedContinue = meetingManageHelper.checkCouldBeInvitedContinue(meetingId);
            if (!invitedContinue) {
                throw BizException.wrap("抽取人员数量已满足抽取规则");
            }
            expertInviteTask.notifyInviteTask(meetingId);
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void convertToAppoint(Long meetingId) {
        String key = "CONVERT_TO_APPOINT:" + meetingId;
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("已进行转换，请勿重复点击");
        }
        try {
            Meeting meeting = meetingService.getById(meetingId);
            if (!MeetingStatusEnum.NORMAL.eq(meeting.getStatus())) {
                throw BizException.wrap("转换失败，请刷新后重试");
            }
            expertInviteTask.cancelByMeetingId(meetingId);
            LambdaUpdateWrapper<Meeting> meetingUpdate = Wrappers.lambdaUpdate(Meeting.class)
                    .set(Meeting::getInviteType, ExpertInviteTypeEnum.APPOINT.getCode())
                    .eq(Meeting::getId, meetingId);
            meetingService.update(meetingUpdate);
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void expertInviteByCreate(ExpertInviteReq req) {
        String key = INVITED_RULE_CREATE + req.getMeetingId();
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("不可重复进行专家抽取");
        }
        try {
            Meeting meeting = meetingService.getById(req.getMeetingId());
            if (ExpertInviteTypeEnum.RANDOM.eq(req.getInviteType())) {
                List<RandomInviteRuleDTO> randomRules = req.getRandomRules();
                Assert.notEmpty(randomRules, "随机抽取规则不能为空");
                AvoidRuleDTO avoidInfo = req.getAvoidRule();
                Assert.notNull(avoidInfo, "回避信息不能为空");
                // 随机抽取的话则需进行抽取数量校验
                ExpertCountOnChangeVO countOnChange = expertCountOnChange(req);
                if (!countOnChange.getStatus()) {
                    throw BizException.wrap(countOnChange.getMessage());
                }
                for (int i = 0; i < randomRules.size(); i++) {
                    Integer checkCount = countOnChange.getCountList().get(i);
                    Integer inviteCount = randomRules.get(i).getCount();
                    Assert.isTrue(checkCount >= inviteCount, "可供抽取的专家数量不足");
                }
                expertInviteManage.expertInviteByMeetingCreate(meeting, randomRules, avoidInfo);
                expertInviteTask.addInviteExpertTaskByMeetingCreate(meeting.getId(), 5);
                LambdaUpdateWrapper<Meeting> update = Wrappers.lambdaUpdate(Meeting.class);
                update.set(Meeting::getInviteStatus, false);
                update.eq(Meeting::getId, meeting.getId());
                meetingService.update(update);
                // 回避规则
                ExpertInviteAvoidRule avoidRule = new ExpertInviteAvoidRule();
                avoidRule.setMeetingId(meeting.getId());
                // 未传值时设置为0 表示不限制周参与次数
                avoidRule.setWeekInviteCount(ObjectUtil.defaultIfNull(avoidRule.getWeekInviteCount(), 0));
                avoidRule.setAvoidOrgIds(CollUtils.joinByComma(avoidInfo.getAvoidOrgIdList()));
                avoidRule.setAvoidUnitIds(CollUtils.joinByComma(avoidInfo.getAvoidUnitIdList()));
                avoidRule.setAvoidExpertIds(CollUtils.joinByComma(avoidInfo.getExpertIds()));
                inviteAvoidRuleService.save(avoidRule);
            } else {
                // 指定邀请
                AppointInviteRuleDTO appointRule = req.getAppointRule();
                Assert.notNull(appointRule, "指定邀请规则不能为空");
                Map<Long, ExpertUserFullInfo> usersMap = expertInviteHelper.checkAppointExpert(appointRule);
                // 处理指定抽取规则
                ExpertInviteRule appoint = getExpertInviteRule(appointRule, meeting.getId());
                inviteRuleService.save(appoint);
                Long ruleId = appoint.getId();
                List<MeetingExpert> experts = appointRule.getExpertIdList().stream().map(w -> {
                    ExpertUserFullInfo info = usersMap.get(w);
                    MeetingExpert expert = ExpertInviteBuilder.getExpertByAppoint(meeting.getId(), info, ruleId);
                    expert.setStatus(ExpertAttendStatusEnum.NOTICING.getCode());
                    return expert;
                }).collect(Collectors.toList());
                meetingExpertService.saveBatch(experts);
            }
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    /**
     * 抽取数量校验
     *
     * @param req 抽取参数
     * @return {@link ExpertCountOnChangeVO}
     * @author WendyYang
     **/
    public ExpertCountOnChangeVO expertCountOnChange(ExpertInviteReq req) {
        // 未传递指定邀请专家直接忽略
        expertInviteHelper.checkAppointExpert(req.getAppointRule());
        Meeting meeting = meetingService.getById(req.getMeetingId());
        ExpertCountOnChangeVO resultCount = new ExpertCountOnChangeVO();
        resultCount.setStatus(true);
        resultCount.setCountList(new ArrayList<>());
        // 参数校验
        String errMsg = ValidUtil.validFast(req, AbstractInviteRule.CountCheck.class);
        if (errMsg != null) {
            resultCount.setStatus(false);
            resultCount.setMessage(errMsg);
            return resultCount;
        }
        List<Long> expertIdsChoose = new ArrayList<>();
        for (RandomInviteRuleDTO randomRule : req.getRandomRules()) {
            ExpertChooseDTO chooseExpert = expertInviteManage.expertInviteByRandomRule(req.getAvoidRule(),
                    randomRule, expertIdsChoose, meeting.getStartTime(), meeting.getEndTime());
            resultCount.addCountList(chooseExpert.getTotal());
        }
        return resultCount;
    }

    /**
     * 专家会议列表
     *
     * @param req 查询参数
     * @return 会议列表
     * @author WendyYang
     **/
    public PageVo<MeetingByManagerVO> meetingListByExpert(MeetingListReq req) {
        Long expertId = req.getExpertId() != null ? req.getExpertId() : LoginUserUtil.getUserId();
        List<MeetingAndAttendStatusDTO> expertDtoList = meetingExpertService.listByExpertIdAndStatus(expertId, req.getStatus(), null);
        if (expertDtoList.isEmpty()) {
            return PageVo.empty();
        }
        Map<Long, MeetingAndAttendStatusDTO> mapByMeetingId = new HashMap<>(16);
        expertDtoList.forEach(w -> mapByMeetingId.put(w.getMeetingId(), w));
        LambdaQueryWrapper<Meeting> query = new LambdaQueryWrapper<Meeting>()
                .orderByDesc(Meeting::getCreateOn)
                .in(Meeting::getId, mapByMeetingId.keySet())
                .ne(Meeting::getStatus, MeetingStatusEnum.CANCELED.getCode());
        if (req.getExpertId() == null) {
            meetingManageHelper.buildMeetingQuery(query, req);
        }
        Page<Meeting> page = meetingService.page(req.page(), query);
        if (page.getTotal() == 0) {
            return PageVo.empty();
        }
        PageVo<MeetingByManagerVO> result = new PageVo<>(new ArrayList<>(), page.getTotal());
        page.getRecords().forEach(meeting -> {
            MeetingByManagerVO item = meetingManageHelper.buildByMeeting(meeting);
            MeetingAndAttendStatusDTO info = mapByMeetingId.get(meeting.getId());
            item.setAttendStatus(meetingManageHelper.getExpertAttendStatus(info));
            result.getRecords().add(item);
        });
        return result;
    }

    /**
     * 管理员会议列表
     *
     * @param req 查询参数
     * @return 会议列表
     * @author WendyYang
     */
    public PageVo<MeetingByManagerVO> meetingListByManager(MeetingListReq req) {
        LambdaQueryWrapper<Meeting> query = new LambdaQueryWrapper<Meeting>()
                .orderByDesc(Meeting::getCreateOn);
        // 补充逻辑 如果拥有超级管理员权限可以看到所有事务
        if (!permissionCheckHelper.isSuperAdmin()) {
            query.eq(Meeting::getCreateBy, LoginUserUtil.getUserId());
        }
        query.eq(req.getStatus() != null, Meeting::getStatus, req.getStatus());
        meetingManageHelper.buildMeetingQuery(query, req);
        Page<Meeting> page = meetingService.page(req.page(), query);
        if (page.getTotal() == 0) {
            return PageVo.empty();
        }
        PageVo<MeetingByManagerVO> result = new PageVo<>(new ArrayList<>(), page.getTotal());
        page.getRecords().forEach(meeting -> {
            MeetingByManagerVO item = meetingManageHelper.buildByMeeting(meeting);
            result.getRecords().add(item);
        });
        return result;
    }

    public MeetingDetailBasicVO getMeetingDetail(Long meetingId) {
        Meeting meeting = meetingService.getById(meetingId);
        Assert.notNull(meeting, "会议不存在");
        MeetingDetailBasicVO detail = MeetingDetailBasicVO.builder()
                .meetingId(meeting.getId())
                .meetingName(meeting.getName())
                .meetingType(meeting.getType())
                .meetingAddress(meeting.getMeetingAddress())
                .typeName(dictionaryCache.getByCode(meeting.getType()).getName())
                .startTime(meeting.getStartTime())
                .judgesAttendanceTime(meeting.getJudgesAttendanceTime())
                .meetingUsageTime(meeting.getMeetingUsageTime())
                .contact(meeting.getContact())
                .connecter(meeting.getConnecter())
                .status(meeting.getStatus())
                .holdOrg(meeting.getHoldOrg())
                .createOn(meeting.getCreateOn())
                .creator(meeting.getCreator())
                .createBy(meeting.getCreator())
                .build();
        if (meeting.getIsInnerProject()) {
            List<MeetingInnerProject> innerProjects = meetingInnerProjectService.listByMeetingId(meetingId);
            List<Project> projects = projectService.listByIds(CollUtils.fieldList(innerProjects, MeetingInnerProject::getProjectId));
            List<MeetingReviewProjectDTO> convert = CollUtils.convert(projects, w -> {
                MeetingReviewProjectDTO mrp = new MeetingReviewProjectDTO();
                mrp.setBuildOrg(w.getBuildOrgName());
                mrp.setProjectName(w.getProjectName());
                mrp.setProjectType(w.getProjectType().toString());
                mrp.setProjectYear(w.getProjectYear());
                mrp.setDeclareAmount(w.getDeclareAmount());
                return mrp;
            });
            detail.setProjects(convert);
        } else {
            List<MeetingOuterProject> outerProjects = meetingOuterProjectService.listByMeetingId(meetingId);
            detail.setProjects(BeanUtil.copyToList(outerProjects, MeetingReviewProjectDTO.class));
        }
        detail.setInviteRule(inviteRuleDetail(meetingId));
        return detail;
    }

    public ExpertInviteDetailVO inviteExpertList(Long meetingId) {
        Meeting meeting = meetingService.getById(meetingId);
        if (meeting == null) {
            throw BizException.wrap("该会议信息不存在");
        }
        ExpertInviteDetailVO result = new ExpertInviteDetailVO();
        result.setInvitedStopped(meeting.getInviteStatus());
        result.setConfirmedRoster(meeting.getConfirmedRoster());
        List<MeetingExpert> experts = meetingExpertService.listByMeetingId(meetingId);
        if (experts.isEmpty()) {
            return result;
        }
        List<Long> expertIds = CollUtils.fieldList(experts, MeetingExpert::getExpertId);
        Map<Long, ExpertBasicInfoVO> expertMap = meetingManageHelper.getExpertBasicInfo(expertIds);
        Function<MeetingExpert, InviteExpertListItemVO> mapping = sme -> {
            ExpertBasicInfoVO expert = expertMap.get(sme.getExpertId());
            InviteExpertListItemVO item = BeanUtil.copyProperties(expert, InviteExpertListItemVO.class);
            item.setExpertMeetingId(sme.getId());
            item.setMeetingId(sme.getMeetingId());
            item.setStatus(sme.getStatus());
            item.setNoticeTime(sme.getCreateOn());
            item.setRuleId(sme.getRuleId());
            if (ExpertAttendStatusEnum.NOTICING.eq(sme.getStatus())) {
                item.setNoticeStatus("通知中");
            } else {
                item.setNoticeStatus("已通知");
            }
            return item;
        };
        boolean isRandom = ExpertInviteTypeEnum.RANDOM.eq(meeting.getInviteType());
        if (!isRandom) {
            result.setInviteStatistics(new ArrayList<>());
            Map<Long, List<MeetingExpert>> groupByRule = CollUtils.group(experts, MeetingExpert::getRuleId);
            List<ExpertInviteRule> inviteRules = inviteRuleService.listByIds(groupByRule.keySet());
            Map<Long, ExpertInviteRule> ruleMap = CollUtils.listToMap(inviteRules, ExpertInviteRule::getId);
            groupByRule.forEach((ruleId, expertList) -> {
                InviteStatisticsByRuleVO statistics = InviteStatisticsByRuleVO.init(ruleId);
                expertList.forEach((expert) -> {
                    Integer status = expert.getStatus();
                    if (ExpertAttendStatusEnum.AGREED.eq(status)) {
                        statistics.incrAgreeCnt();
                    }
                    statistics.setInviteCnt(ruleMap.get(ruleId).getInviteCount());
                    statistics.incrNoticedCnt();
                });
                result.getInviteStatistics().add(statistics);
            });
        }
        List<InviteExpertListItemVO> converts = CollUtils.convert(experts, mapping);
        result.setInviteExpertList(converts);
        // 确定参加列表
        return result;
    }

    /**
     * 会议基本信息修改
     *
     * @param po 修改参数
     **/
    public void meetingBasicInfoModify(MeetingBasicInfoModifyReq po) {
        Meeting meeting = new Meeting();
        BeanUtil.copyProperties(po, meeting);
        LocalDateTime now = LocalDateTime.now();
        Meeting old = meetingService.getById(po.getId());
        List<MeetingExpert> meList = meetingExpertService.listAgreedExperts(Collections.singletonList(po.getId()));
        if (!meList.isEmpty() && old.getStartTime().isAfter(now)) {
            // TODO
            /*String meetingType = dictionaryCache.getByCode(old.getType()).getName();
              List<SendSmsContext> contexts = YxtSmsContextBuilder.smsToExpertByMeetingChange(old, meeting, meList, meetingType);
              yxtCallOrSmsHelper.sendSms(contexts);*/
        }
        meetingService.updateById(meeting);
    }

    /**
     * 抽取规则详情
     *
     * @param meetingId 会议ID
     * @return {@link InviteRuleDetailVO}
     * @author WendyYang
     **/
    public InviteRuleDetailVO inviteRuleDetail(Long meetingId) {
        InviteRuleDetailVO result = new InviteRuleDetailVO();
        List<ExpertInviteRule> inviteRules = inviteRuleService.listByMeetingId(meetingId);
        result.setRandomRules(new ArrayList<>());
        Map<ExpertInviteTypeEnum, List<ExpertInviteRule>> groupByType = CollUtils.group(inviteRules, w -> ExpertInviteTypeEnum.getByCode(w.getInviteType()));
        List<ExpertInviteRule> randoms = groupByType.get(ExpertInviteTypeEnum.RANDOM);
        if (CollUtil.isNotEmpty(randoms)) {
            randoms.forEach(random -> {
                RandomInviteRuleVO randomRule = JSON.parseObject(random.getInviteRule(), RandomInviteRuleVO.class);
                if (randomRule.getExpertTags() != null) {
                    randomRule.getExpertTags().forEach(w -> {
                        List<String> tagNames = CollUtils.convert(w.getTagCodes(), tagCode -> tagCache.getByTagCode(tagCode).getTagName());
                        w.setTagCodes(tagNames);
                    });
                }
                if (randomRule.getExpertDicts() != null) {
                    randomRule.getExpertDicts().forEach(w -> {
                        List<String> dictNames = CollUtils.convert(w.getDictCodes(), dictCode -> dictionaryCache.getByCode(dictCode).getName());
                        w.setDictCodes(dictNames);
                    });
                }
                if (StrUtil.isNotEmpty(randomRule.getIntentionRegionCode())) {
                    List<RegionDTO> intentionRegions = regionCache.listParents(randomRule.getIntentionRegionCode(), randomRule.getIntentionRegionLevel());
                    randomRule.setIntentionRegions(intentionRegions);
                }
                if (StrUtil.isNotEmpty(randomRule.getExpertRegionCode())) {
                    List<RegionDTO> expertRegions = regionCache.listParents(randomRule.getExpertRegionCode(), randomRule.getExpertRegionLevel());
                    randomRule.setExpertRegions(expertRegions);
                }
                result.getRandomRules().add(randomRule);
            });
            AvoidRuleDTO avoidInfo = inviteAvoidRuleService.getAvoidInfoDto(meetingId);
            AvoidInfoVO vo = new AvoidInfoVO();
            vo.setAvoidOrgIds(avoidInfo.getAvoidOrgIdList());
            vo.setAvoidUnitIds(avoidInfo.getAvoidUnitIdList());
            if (CollUtil.isNotEmpty(avoidInfo.getExpertIds())) {
                vo.setExperts(new ArrayList<>(meetingManageHelper.getExpertBasicInfo(avoidInfo.getExpertIds()).values()));
            }
            result.setAvoidInfo(vo);
        } else {
            List<ExpertInviteRule> appoints = groupByType.get(ExpertInviteTypeEnum.APPOINT);
            ExpertInviteRule appoint = appoints.get(0);
            AppointInviteRuleDTO appointRule = JSON.parseObject(appoint.getInviteRule(), AppointInviteRuleDTO.class);
            AppointRuleVO vo = new AppointRuleVO();
            vo.setInviteDesc(appointRule.getInviteDesc());
            vo.setExperts(new ArrayList<>(meetingManageHelper.getExpertBasicInfo(appointRule.getExpertIdList()).values()));
            result.setAppointRule(vo);
        }
        return result;
    }

    public void batchAppointExperts(BatchAppointExpertsReq req) {
        Long meetingId = req.getMeetingId();
        String key = "BATCH_APPOINT_EXPERT:" + meetingId;
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("补充专家中，请勿重复点击");
        }
        try {
            Meeting meeting = meetingService.getById(meetingId);
            if (!ExpertInviteTypeEnum.APPOINT.eq(meeting.getInviteType())) {
                throw BizException.wrap("该会议不能指定邀请专家");
            }
            if (meeting.getConfirmedRoster()) {
                throw BizException.wrap("补充专家失败，已确认过专家名单");
            }
            if (!MeetingStatusEnum.NORMAL.eq(meeting.getStatus())) {
                throw BizException.wrap("补充专家失败");
            }
            AppointInviteRuleDTO rule = inviteRuleService.appointRuleByMeetingId(meetingId);
            List<ExpertUserFullInfo> userInfos = meetingManageHelper.appointExpertCheck(meetingId, req.getExpertIdList());
            List<MeetingExpert> expertList = CollUtils.convert(userInfos, w -> {
                MeetingExpert me = ExpertInviteBuilder.getExpertByAppoint(meetingId, w, rule.getId());
                me.setStatus(ExpertAttendStatusEnum.NOTICING.getCode());
                return me;
            });
            meetingExpertService.saveBatch(expertList);
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    public void stopRandomInvite(Long meetingId) {
        expertInviteTask.cancelByMeetingId(meetingId);
    }

    @Transactional(rollbackFor = Exception.class)
    public void cancelMeeting(MeetingCancelReq req) {
        Long meetingId = req.getMeetingId();
        String key = "CANCEL_MEETING:" + meetingId;
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("正在取消会议，请刷新后重试");
        }
        try {
            Meeting meeting = meetingService.getById(meetingId);
            if (MeetingStatusEnum.CANCELED.eq(meeting.getStatus())) {
                throw BizException.wrap("会议已取消");
            }
            if (meeting.getStartTime().isBefore(LocalDateTime.now())) {
                throw BizException.wrap("会议已开始，暂时无法取消");
            }
            LambdaUpdateWrapper<Meeting> meetingUpdate = Wrappers.lambdaUpdate(Meeting.class)
                    .set(Meeting::getStatus, MeetingStatusEnum.CANCELED.getCode())
                    .eq(Meeting::getId, meetingId);
            meetingService.update(meetingUpdate);
            expertInviteTask.cancelByMeetingId(meetingId);
            // 发送通知给专家
            List<MeetingExpert> experts = meetingExpertService.listAgreedExperts(meetingId);
            if (!experts.isEmpty()) {
                // TODO 取消会议后发送短信通知
                // meeting.setCancelRemark(po.getCancelRemark());
                // String meetingType = dictionaryCache.getByCode(meeting.getType()).getName();
                // List<SendSmsContext> contexts = YxtSmsContextBuilder.smsToExpertByCancelMeeting(meeting, experts, meetingType);
                // yxtCallOrSmsHelper.sendSms(contexts);
            }
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    public ExpertInvitationDetailVO expertInvitationDetail(Long meetingId, Long expertId) {
        Long userId = expertId == null ? LoginUserUtil.getUserId() : expertId;
        MeetingExpert me = meetingExpertService.getByMeetingIdAndExpertId(meetingId, userId);
        if (me == null) {
            throw BizException.wrap("未被邀请参加此会议");
        }
        if (ExpertAttendStatusEnum.AGREED.eq(me.getStatus())) {
            throw BizException.wrap("未确认参加此会议");
        }
        ExpertUserFullInfo userInfo = expertUserFullInfoService.getByUserId(userId);
        Meeting meeting = meetingService.getById(meetingId);
        return ExpertInvitationDetailVO.builder()
                .expertName(userInfo.getExpertName())
                .holdOrg(meeting.getHoldOrg())
                .meetingName(meeting.getName())
                .startTime(meeting.getStartTime())
                .endTime(meeting.getEndTime())
                .inviteTime(me.getCreateOn())
                .connecter(meeting.getConnecter())
                .contact(meeting.getContact())
                .build();
    }

    public void confirmAttendByManager(ExpertConfirmReq req) {
        String key = "CONFIRM_ATTEND:" + req.getExpertMeetingId();
        String msgPrefix = req.getAgreed() ? "确认参加" : "拒绝参加";
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("%s失败，请重试！", msgPrefix);
        }
        try {
            MeetingExpert me = meetingExpertService.getById(req.getExpertMeetingId());
            if (!ExpertAttendStatusEnum.NOTICING.eq(me.getStatus())) {
                throw BizException.wrap("%s失败，请重试！", msgPrefix);
            }
            LambdaUpdateWrapper<MeetingExpert> update = Wrappers.lambdaUpdate(MeetingExpert.class)
                    .set(MeetingExpert::getStatus, (req.getAgreed() ? ExpertAttendStatusEnum.AGREED : ExpertAttendStatusEnum.REFUSED).getCode())
                    .eq(MeetingExpert::getId, req.getExpertMeetingId());
            meetingExpertService.update(update);
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    public void releaseExperts(MeetingCancelReq req) {
        String key = "EXPERT_RELEASE:" + req.getMeetingId();
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("释放专家失败，请重试！");
        }
        try {
            Meeting meeting = meetingService.getById(req.getMeetingId());
            if (MeetingStatusEnum.CANCELED.eq(meeting.getStatus())) {
                throw BizException.wrap("会议已取消");
            }
            LambdaUpdateWrapper<MeetingExpert> update = Wrappers.lambdaUpdate(MeetingExpert.class)
                    .set(MeetingExpert::getStatus, ExpertAttendStatusEnum.RELEASED.getCode())
                    .eq(MeetingExpert::getMeetingId, req.getMeetingId());
            meetingExpertService.update(update);
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void setUpHeadman(ExpertConfirmReq req) {
        String key = "SETUP_HEADMAN:" + req.getMeetingId();
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("设置专家组长失败，请重试！");
        }
        try {
            Meeting meeting = meetingService.getById(req.getMeetingId());
            if (MeetingStatusEnum.CANCELED.eq(meeting.getStatus())) {
                throw BizException.wrap("会议已取消");
            }
            LambdaUpdateWrapper<MeetingExpert> cancel = Wrappers.lambdaUpdate(MeetingExpert.class)
                    .set(MeetingExpert::getIsHeadman, Boolean.FALSE)
                    .eq(MeetingExpert::getIsHeadman, Boolean.TRUE)
                    .eq(MeetingExpert::getMeetingId, req.getMeetingId());
            meetingExpertService.update(cancel);
            MeetingExpert headman = meetingExpertService.getById(req.getExpertMeetingId());
            if (!ExpertAttendStatusEnum.AGREED.eq(headman.getStatus())) {
                ExpertAttendStatusEnum status = ExpertAttendStatusEnum.getByCode(headman.getStatus());
                throw BizException.wrap("该专家处于：%s状态，不能被设置为专家组长！", status.getValue());
            }
            LambdaUpdateWrapper<MeetingExpert> setup = Wrappers.lambdaUpdate(MeetingExpert.class)
                    .set(MeetingExpert::getIsHeadman, Boolean.TRUE)
                    .eq(MeetingExpert::getId, req.getExpertMeetingId());
            meetingExpertService.update(setup);
        } finally {
            distributedLock.releaseLock(key);
        }
    }

    public void confirmedRoster(Long meetingId) {
        String key = "MEETING_RESEND_SMS:" + meetingId;
        if (!distributedLock.lock(key, RETRY_TIMES)) {
            throw BizException.wrap("请刷新后重试！");
        }
        try {
            Meeting meeting = meetingService.getById(meetingId);
            if (!MeetingStatusEnum.NORMAL.eq(meeting.getStatus())) {
                throw BizException.wrap("请刷新后重试！");
            }
            if (!meeting.getConfirmedRoster()) {
                // 首次确认的
                LambdaUpdateWrapper<Meeting> update = Wrappers.lambdaUpdate(Meeting.class);
                update.set(Meeting::getConfirmedRoster, Boolean.TRUE);
                update.eq(Meeting::getId, meetingId);
                meetingService.update(update);
            }
            List<MeetingExpert> experts = meetingExpertService.listAgreedExperts(meetingId);
            // TODO 发送会议通知
        } finally {
            distributedLock.releaseLock(key);
        }
    }

}
