package com.ningdatech.pmapi.leave.manage;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
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.PagePo;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.file.entity.vo.result.AttachFileVo;
import com.ningdatech.file.service.FileService;
import com.ningdatech.pmapi.common.model.FileBasicInfo;
import com.ningdatech.pmapi.common.model.entity.KeyValDTO;
import com.ningdatech.pmapi.common.util.BizUtils;
import com.ningdatech.pmapi.common.util.StrUtils;
import com.ningdatech.pmapi.expert.constant.ExpertApplyStatusEnum;
import com.ningdatech.pmapi.expert.constant.ExpertApplyTypeEnum;
import com.ningdatech.pmapi.expert.entity.ExpertMetaApply;
import com.ningdatech.pmapi.expert.entity.ExpertUserFullInfo;
import com.ningdatech.pmapi.expert.model.dto.ModifyApplyExtraInfoDTO;
import com.ningdatech.pmapi.expert.service.IExpertMetaApplyService;
import com.ningdatech.pmapi.expert.service.IExpertUserFullInfoService;
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeave;
import com.ningdatech.pmapi.leave.entity.domain.ExpertLeaveDetail;
import com.ningdatech.pmapi.leave.entity.enumeration.LeaveStatusEnum;
import com.ningdatech.pmapi.leave.entity.enumeration.LeaveTypeEnum;
import com.ningdatech.pmapi.leave.entity.po.LeaveCreateReq;
import com.ningdatech.pmapi.leave.entity.po.LeaveListByCreatorReq;
import com.ningdatech.pmapi.leave.entity.vo.LeaveDetailVO;
import com.ningdatech.pmapi.leave.entity.vo.LeaveListItemVO;
import com.ningdatech.pmapi.leave.service.IExpertLeaveDetailService;
import com.ningdatech.pmapi.leave.service.IExpertLeaveService;
import com.ningdatech.pmapi.meeting.entity.domain.Meeting;
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert;
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.helper.MeetingCallOrMsgHelper;
import com.ningdatech.pmapi.meeting.service.IMeetingExpertService;
import com.ningdatech.pmapi.meeting.service.IMeetingService;
import com.ningdatech.pmapi.meeting.task.ExpertRandomInviteTask;
import com.ningdatech.pmapi.sms.utils.DateUtil;
import com.ningdatech.pmapi.user.util.LoginUserUtil;
import lombok.AllArgsConstructor;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;


/**
 * <p>
 * LeaveManage
 * </p>
 *
 * @author WendyYang
 * @since 10:38 2022/8/11
 */
@Component
@AllArgsConstructor
public class LeaveManage {

    private final ExpertRandomInviteTask inviteTask;
    private final IMeetingService meetingService;
    private final IMeetingExpertService meetingExpertService;
    private final IExpertLeaveService leaveService;
    private final IExpertLeaveDetailService leaveDetailService;
    private final IExpertMetaApplyService metaApplyService;
    private final FileService fileService;
    private final IExpertUserFullInfoService userFullInfoService;
    private final MeetingCallOrMsgHelper meetingCallOrMsgHelper;

    private static final int HOURS_BEFORE_MEETING = 2;

    public void cancelMeetingExpertByLeave(Long meetingExpertId, ExpertAttendStatusEnum status) {
        MeetingExpert update = buildMeetingExpertUpdate(meetingExpertId, status);
        meetingExpertService.updateById(update);
    }

    private MeetingExpert buildMeetingExpertUpdate(Long meetingExpertId, ExpertAttendStatusEnum status) {
        MeetingExpert expert = new MeetingExpert();
        expert.setId(meetingExpertId);
        expert.setStatus(status.getCode());
        return expert;
    }

    private List<MeetingExpert> listMeetingExpert(Long expertId, List<KeyValDTO<LocalDateTime, LocalDateTime>> leaveTimes) {
        // 先查询专家参与的所有会议
        LambdaQueryWrapper<MeetingExpert> meetingExpertQuery = Wrappers.lambdaQuery(MeetingExpert.class)
                .eq(MeetingExpert::getExpertId, expertId)
                .in(MeetingExpert::getStatus, Arrays.asList(ExpertAttendStatusEnum.NOTICING.getCode(),
                        ExpertAttendStatusEnum.AGREED.getCode()));
        List<MeetingExpert> meetingExperts = meetingExpertService.list(meetingExpertQuery);
        if (meetingExperts.isEmpty()) {
            return Collections.emptyList();
        }
        // 只需要查询待确认的或者是同意参加的
        Map<Long, MeetingExpert> expertMap = meetingExperts.stream()
                .collect(Collectors.groupingBy(MeetingExpert::getMeetingId,
                        Collectors.collectingAndThen(Collectors.toList(), w -> {
                            w.sort(Comparator.comparing(MeetingExpert::getUpdateOn).reversed());
                            return w.get(0);
                        })));
        List<Long> meetingIds = CollUtils.fieldList(expertMap.values(), MeetingExpert::getMeetingId);
        LocalDateTime startTime = leaveTimes.get(0).getKey(), endTime = leaveTimes.get(leaveTimes.size() - 1).getValue();
        LambdaQueryWrapper<Meeting> meetingQuery = Wrappers.lambdaQuery(Meeting.class)
                .in(Meeting::getId, meetingIds)
                .ne(Meeting::getStatus, MeetingStatusEnum.CANCELED.getCode())
                .and(w -> w.between(Meeting::getStartTime, startTime, endTime)
                        .or(or -> or.between(Meeting::getEndTime, startTime, endTime)));
        List<Meeting> meetingList = meetingService.list(meetingQuery);
        List<Long> matchMeetingIds = meetingList.stream()
                .filter(w -> leaveTimes.stream()
                        .anyMatch(tp -> DateUtil.intersect(w.getStartTime(), w.getEndTime(), tp.getKey(), tp.getValue())))
                .map(Meeting::getId).collect(Collectors.toList());
        if (matchMeetingIds.isEmpty()) {
            return Collections.emptyList();
        }
        return matchMeetingIds.stream().map(expertMap::get).collect(Collectors.toList());
    }

    @Transactional(rollbackFor = Exception.class)
    public IdVo<Long> askForLeave(LeaveCreateReq po) {
        Long leaveUserId, applyUserId = Objects.requireNonNull(LoginUserUtil.getUserId());
        leaveUserId = po.getExpertId() != null ? po.getExpertId() : applyUserId;
        boolean leaveForSelf = leaveUserId.equals(applyUserId);
        if (!leaveForSelf) {
            Assert.isFalse(leaveService.existsToBeReviewed(leaveUserId), "该专家存在待审核请假申请，请及时审核");
        }
        LeaveTypeEnum type = LeaveTypeEnum.getByCode(po.getType());
        // 校验参数是否合法
        po.paramCheck(type);
        ExpertLeave leave = new ExpertLeave();
        if (po.getAttachments() != null) {
            leave.setAttachment(CollUtils.joinByComma(po.getAttachments()));
        }
        leave.setType(po.getType());
        leave.setRemark(po.getPostscript());
        leave.setLeaveUserId(leaveUserId);
        leave.setCreator(LoginUserUtil.getUsername());
        List<KeyValDTO<LocalDateTime, LocalDateTime>> leaveDetailTimes = new ArrayList<>();
        if (type.equals(LeaveTypeEnum.FIXED_TERM) || type.equals(LeaveTypeEnum.LONG_TERM)) {
            leaveProcessByDateRange(po, leaveUserId, leave, leaveDetailTimes);
        } else if (LeaveTypeEnum.TEMPORARY.equals(type)) {
            leaveProcessByTemporary(po, leaveUserId, leave, leaveDetailTimes);
        }
        if (type != LeaveTypeEnum.TEMPORARY) {
            ExpertMetaApply apply = getExpertMetaApply(po, leaveUserId, applyUserId);
            leave.setAuditId(apply.getId());
            if (!leaveForSelf) {
                leave.setStatus(LeaveStatusEnum.PASSED.getCode());
            }
        }
        leave.setStartTime(po.getStartTime());
        leave.setEndTime(po.getEndTime());
        leave.setCancelTime(po.getEndTime());
        leaveService.save(leave);
        List<ExpertLeaveDetail> detailList = CollUtils.convert(leaveDetailTimes, w -> new ExpertLeaveDetail(leave.getId(), w.getKey(), w.getValue()));
        leaveDetailService.saveBatch(detailList);
        return IdVo.of(leave.getId());
    }

    private void leaveProcessByTemporary(LeaveCreateReq po, Long leaveUserId, ExpertLeave leave, List<KeyValDTO<LocalDateTime, LocalDateTime>> leaveDetailTimes) {
        // 临时请假
        Meeting meeting = meetingService.getById(po.getMeetingId());
        if (MeetingStatusEnum.CANCELED.eq(meeting.getStatus())) {
            throw BizException.wrap("该会议已取消");
        }
        po.setStartTime(meeting.getStartTime());
        po.setEndTime(meeting.getEndTime());
        if (LocalDateTime.now().plusHours(HOURS_BEFORE_MEETING).isAfter(po.getStartTime())) {
            throw BizException.wrap("会议开始前" + HOURS_BEFORE_MEETING + "小时，不能在线请假，请电话该会议的联系人。");
        }
        MeetingExpert expert = meetingExpertService.getByMeetingIdAndExpertId(po.getMeetingId(), leaveUserId);
        if (!ExpertAttendStatusEnum.AGREED.eq(expert.getStatus())) {
            // 非确认参加状态无法临时请假
            throw BizException.wrap("未同意参加该会议，无法临时请假");
        }
        LocalDateTime tempStart = po.getStartTime();
        while (!tempStart.isAfter(po.getEndTime())) {
            LocalDateTime tempEnd = tempStart.toLocalDate().atTime(DateUtil.LOCAL_TIME_3D);
            leaveDetailTimes.add(KeyValDTO.of(tempStart, DateUtil.min(tempEnd, po.getEndTime())));
            tempStart = tempStart.plusDays(1).toLocalDate().atStartOfDay();
        }
        LeaveManage proxy = (LeaveManage) AopContext.currentProxy();
        proxy.cancelMeetingExpertByLeave(expert.getId(), ExpertAttendStatusEnum.ON_LEAVE);
        if (ExpertInviteTypeEnum.RANDOM.eq(meeting.getInviteType())) {
            inviteTask.notifyInviteTask(meeting.getId(), Boolean.FALSE);
        }
        if (meeting.getConfirmedRoster()) {
            LambdaUpdateWrapper<Meeting> mUpdate = Wrappers.lambdaUpdate(Meeting.class)
                    .set(Meeting::getConfirmedRoster, Boolean.FALSE)
                    .eq(Meeting::getId, meeting.getId());
            meetingService.update(mUpdate);
        }
        meetingCallOrMsgHelper.sendExpertLeaveMsg(expert, meeting);
        // 临时请假无需审核
        leave.setAuditId(0L);
        leave.setStatus(LeaveStatusEnum.PASSED.getCode());
    }

    private void leaveProcessByDateRange(LeaveCreateReq req, Long leaveUserId, ExpertLeave leave, List<KeyValDTO<LocalDateTime, LocalDateTime>> leaveDetailTimes) {
        boolean fixedTerm = CollUtil.isNotEmpty(req.getFixedType());
        if (fixedTerm) {
            // 固定时段请假
            leave.setFixedType(CollUtils.joinByComma(req.getFixedType()));
        }
        LocalDateTime tempStart = req.getStartTime();
        while (!tempStart.isAfter(req.getEndTime())) {
            boolean add = true;
            if (fixedTerm) {
                int weekday = tempStart.getDayOfWeek().getValue();
                add = req.getFixedType().contains(weekday);
            }
            if (add) {
                LocalDateTime tempEnd = tempStart.toLocalDate().atTime(DateUtil.LOCAL_TIME_3D);
                leaveDetailTimes.add(KeyValDTO.of(tempStart, DateUtil.min(tempEnd, req.getEndTime())));
            }
            tempStart = tempStart.plusDays(1).toLocalDate().atStartOfDay();
        }
        if (leaveDetailTimes.isEmpty()) {
            throw BizException.wrap("请假时间无效");
        }
        if (leaveDetailService.existsLeaveByUserIdAndTime(leaveUserId, leaveDetailTimes)) {
            throw BizException.wrap("该请假时段内已存在请假");
        }
        List<MeetingExpert> meetingExperts = listMeetingExpert(leaveUserId, leaveDetailTimes);
        if (!meetingExperts.isEmpty()) {
            if (meetingExperts.stream().anyMatch(w -> ExpertAttendStatusEnum.AGREED.eq(w.getStatus()))) {
                throw BizException.wrap("有待参加会议，请先处理完会议，再发起请假");
            }
            List<MeetingExpert> expertsUpdate = meetingExperts.stream()
                    .map(w -> buildMeetingExpertUpdate(w.getExpertId(), ExpertAttendStatusEnum.ON_LEAVE))
                    .collect(Collectors.toList());
            meetingExpertService.updateBatchById(expertsUpdate);
        }
        leave.setStatus(LeaveStatusEnum.APPLYING.getCode());
    }

    private ExpertMetaApply getExpertMetaApply(LeaveCreateReq po, Long leaveUserId, Long applyUserId) {
        // 非临时请假需要审批
        LocalDateTime now = LocalDateTime.now();
        ExpertMetaApply apply = new ExpertMetaApply();
        apply.setCreateOn(now);
        apply.setUpdateOn(now);
        apply.setApplyType(ExpertApplyTypeEnum.LONG_TERM_LEAVE.getKey());
        if (applyUserId.equals(leaveUserId)) {
            apply.setApplyStatus(ExpertApplyStatusEnum.PENDING_REVIEW.getKey());
        } else {
            // 专家管理员发起请假自动通过
            apply.setReviewTime(now);
            apply.setApprover(LoginUserUtil.getUsername());
            apply.setApproverUserId(applyUserId);
            apply.setApplyStatus(ExpertApplyStatusEnum.PASSED.getKey());
        }
        apply.setUserId(leaveUserId);
        apply.setApplicantId(applyUserId);
        ExpertUserFullInfo userInfo = userFullInfoService.getByUserId(apply.getUserId());
        apply.setRegionCode(userInfo.getRegionCode());
        apply.setRegionLevel(userInfo.getRegionLevel());
        ModifyApplyExtraInfoDTO extraInfo = new ModifyApplyExtraInfoDTO();
        extraInfo.setFactSheet(po.getPostscript());
        if (po.getAttachments() != null) {
            List<FileBasicInfo> infoList = CollUtils.convert(po.getAttachments(), w -> {
                FileBasicInfo info = new FileBasicInfo();
                info.setFileId(w);
                return info;
            });
            extraInfo.setEvidenceList(infoList);
        }
        apply.setExtraMaterial(JSONUtil.toJsonStr(extraInfo));
        metaApplyService.save(apply);
        return apply;
    }

    public PageVo<LeaveListItemVO> leaveListByCreator(LeaveListByCreatorReq po) {
        if (po.getExpertId() == null) {
            po.setExpertId(LoginUserUtil.getUserId());
        }
        LambdaQueryWrapper<ExpertLeave> query = Wrappers.lambdaQuery(ExpertLeave.class)
                .orderByDesc(ExpertLeave::getCreateOn);
        if (po.getLeaveType() != null && po.getLeaveType().equals(LeaveTypeEnum.TEMPORARY.getCode())) {
            // 临时请假
            if (po.getAuditStatus() != null && !po.getAuditStatus().equals(ExpertApplyStatusEnum.PASSED.getKey())) {
                return PageVo.empty();
            }
            query.eq(ExpertLeave::getAuditId, 0L);
            query.eq(ExpertLeave::getLeaveUserId, po.getExpertId());
        } else {
            if (StrUtils.isNotBlank(po.getAuditStatus())) {
                LambdaQueryWrapper<ExpertMetaApply> queryApply = Wrappers.lambdaQuery(ExpertMetaApply.class)
                        .select(ExpertMetaApply::getId)
                        .eq(ExpertMetaApply::getUserId, po.getExpertId())
                        .eq(ExpertMetaApply::getApplyStatus, po.getAuditStatus());
                List<ExpertMetaApply> applyList = metaApplyService.list(queryApply);
                List<Long> auditIds = CollUtils.fieldList(applyList, ExpertMetaApply::getId);
                if (po.getAuditStatus().equals(ExpertApplyStatusEnum.PASSED.getKey())) {
                    query.eq(ExpertLeave::getLeaveUserId, po.getExpertId());
                    auditIds.add(0L);
                }
                if (auditIds.isEmpty()) {
                    return PageVo.empty();
                }
                query.in(ExpertLeave::getAuditId, auditIds);
            } else {
                query.eq(ExpertLeave::getLeaveUserId, po.getExpertId());
            }
            query.eq(po.getLeaveType() != null, ExpertLeave::getType, po.getLeaveType());
        }
        if (po.getStartTime() != null && po.getEndTime() != null) {
            query.and(wrapper -> wrapper.between(ExpertLeave::getStartTime, po.getStartTime(), po.getEndTime())
                    .or(or -> or.between(ExpertLeave::getEndTime, po.getStartTime(), po.getEndTime())));
        }
        Page<ExpertLeave> page = leaveService.page(po.page(), query);
        if (page.getTotal() == 0) {
            return PageVo.empty();
        }
        List<Long> meetingIds = new ArrayList<>(), auditIds = new ArrayList<>(), fileIds = new ArrayList<>();
        page.getRecords().forEach(w -> {
            if (w.getMeetingId() != 0) {
                meetingIds.add(w.getMeetingId());
            }
            if (w.getAuditId() != 0) {
                auditIds.add(w.getAuditId());
            }
            if (!w.getAttachment().isEmpty()) {
                List<Long> tempFileIds = BizUtils.splitToLong(w.getAttachment());
                fileIds.addAll(tempFileIds);
            }
        });
        Map<Long, Meeting> meetingMap = new HashMap<>(16);
        if (!meetingIds.isEmpty()) {
            meetingMap.putAll(CollUtils.listToMap(meetingService.listByIds(meetingIds), Meeting::getId));
        }
        Map<Long, ExpertMetaApply> applyMap = new HashMap<>(16);
        if (!auditIds.isEmpty()) {
            applyMap.putAll(CollUtils.listToMap(metaApplyService.listByIds(auditIds), ExpertMetaApply::getId));
        }
        Map<Long, AttachFileVo> fileMap = new HashMap<>(fileIds.size());
        if (!fileIds.isEmpty()) {
            fileMap.putAll(CollUtils.listToMap(fileService.getByIds(fileIds), AttachFileVo::getFileId));
        }
        List<LeaveListItemVO> dataList = CollUtils.convert(page.getRecords(), w -> {
            LeaveListItemVO item = new LeaveListItemVO();
            item.setLeaveType(w.getType());
            item.setPostscript(w.getRemark());
            item.setAuditId(w.getAuditId());
            item.setLeaveId(w.getId());
            item.setStartTime(w.getStartTime());
            item.setEndTime(w.getEndTime());
            ExpertMetaApply apply = applyMap.get(w.getAuditId());
            item.setAuditOption(apply != null ? apply.getAuditOpinion() : StrUtil.EMPTY);
            item.setStatus(w.getStatus());
            item.setAuditStatus(apply != null ? apply.getApplyStatus() : ExpertApplyStatusEnum.PASSED.getKey());
            item.setActualEndTime(w.getCancelTime());
            Meeting meeting = meetingMap.get(w.getMeetingId());
            item.setMeetingName(meeting != null ? meeting.getName() : StrUtil.EMPTY);
            item.setAttachments(new ArrayList<>());
            if (!w.getAttachment().isEmpty()) {
                Arrays.stream(w.getAttachment().split(",")).forEach(fileId -> item.getAttachments().add(fileMap.get(Long.parseLong(fileId))));
            }
            return item;
        });
        return PageVo.of(dataList, page.getTotal());
    }

    public void endLeave(Long leaveId) {
        ExpertLeave leave = leaveService.getById(leaveId);
        if (LeaveTypeEnum.TEMPORARY.eq(leave.getType())) {
            throw BizException.wrap("临时请假不支持销假");
        }
        LeaveStatusEnum status = LeaveStatusEnum.getByCode(leave.getStatus());
        if (!status.equals(LeaveStatusEnum.PASSED) || LocalDateTime.now().isAfter(leave.getEndTime())) {
            throw BizException.wrap("该请假不支持销假");
        }
        LocalDateTime actualEndTime = LocalDateTime.now();
        LambdaUpdateWrapper<ExpertLeave> updater = Wrappers.lambdaUpdate(ExpertLeave.class)
                .set(ExpertLeave::getCancelTime, actualEndTime)
                .set(ExpertLeave::getUpdateOn, actualEndTime)
                .set(ExpertLeave::getUpdateBy, LoginUserUtil.getUserId())
                .set(ExpertLeave::getStatus, LeaveStatusEnum.PASSED_END.getCode())
                .eq(ExpertLeave::getId, leave.getId());
        leaveService.update(updater);
        LambdaQueryWrapper<ExpertLeaveDetail> deleter = Wrappers.lambdaQuery(ExpertLeaveDetail.class)
                .ge(ExpertLeaveDetail::getStartTime, actualEndTime.toLocalDate())
                .eq(ExpertLeaveDetail::getExpertLeaveId, leaveId);
        leaveDetailService.remove(deleter);
    }

    public void cancel(Long leaveId) {
        ExpertLeave leave = leaveService.getById(leaveId);
        if (LeaveTypeEnum.TEMPORARY.eq(leave.getType())) {
            throw BizException.wrap("临时请假不支持撤销");
        }
        if (LeaveStatusEnum.APPLYING.eq(leave.getStatus())) {
            throw BizException.wrap("该请假不支持撤销");
        }
        LocalDateTime actualEndTime = LocalDateTime.now();
        // 修改请假状态
        LambdaUpdateWrapper<ExpertLeave> updater = Wrappers.lambdaUpdate(ExpertLeave.class)
                .set(ExpertLeave::getUpdateOn, actualEndTime)
                .set(ExpertLeave::getUpdateBy, LoginUserUtil.getUserId())
                .set(ExpertLeave::getStatus, LeaveStatusEnum.CANCELED.getCode())
                .eq(ExpertLeave::getId, leave.getId());
        leaveService.update(updater);
        // 删除请假详情
        LambdaQueryWrapper<ExpertLeaveDetail> deleter = Wrappers.lambdaQuery(ExpertLeaveDetail.class)
                .eq(ExpertLeaveDetail::getExpertLeaveId, leaveId);
        leaveDetailService.remove(deleter);
        // 修改审核表
        LambdaUpdateWrapper<ExpertMetaApply> updaterApply = Wrappers.lambdaUpdate(ExpertMetaApply.class)
                .set(ExpertMetaApply::getApplyStatus, ExpertApplyStatusEnum.REVOKED.getKey())
                .set(ExpertMetaApply::getUpdateOn, LocalDateTime.now())
                .set(ExpertMetaApply::getAuditOpinion, "发起人撤销")
                .eq(ExpertMetaApply::getId, leave.getAuditId());
        metaApplyService.update(updaterApply);
    }

    public LeaveDetailVO leaveDetail(Long leaveId, Long auditId) {
        ExpertLeave leave;
        if (leaveId != null) {
            leave = leaveService.getById(leaveId);
        } else {
            leave = leaveService.getByAuditId(auditId);
        }
        LeaveDetailVO detail = LeaveDetailVO.builder()
                .createOn(leave.getCreateOn())
                .createBy(leave.getCreator())
                .postscript(leave.getRemark())
                .id(leave.getId())
                .status(leave.getStatus())
                .startTime(leave.getStartTime())
                .endTime(leave.getEndTime())
                .attachments(new ArrayList<>())
                .type(leave.getType())
                .actualEndTime(leave.getCancelTime())
                .auditId(leave.getAuditId())
                .build();
        if (LeaveTypeEnum.FIXED_TERM.eq(leave.getType())) {
            detail.setFixedType(BizUtils.splitToNum(leave.getFixedType(), Integer.class));
        }
        if (StrUtils.isNotBlank(leave.getAttachment())) {
            List<Long> fileIds = BizUtils.splitToLong(leave.getAttachment());
            List<AttachFileVo> files = fileService.getByIds(fileIds);
            detail.getAttachments().addAll(files);
        }
        return detail;
    }

    public List<Long> listLeaveUserIdByTime(LocalDateTime start, LocalDateTime end) {
        LambdaQueryWrapper<ExpertLeaveDetail> leaveDetailQuery = Wrappers.lambdaQuery(ExpertLeaveDetail.class)
                .select(ExpertLeaveDetail::getExpertLeaveId)
                .and(wrapper -> wrapper.between(ExpertLeaveDetail::getStartTime, start, end)
                        .or(wrapper1 -> wrapper1.between(ExpertLeaveDetail::getEndTime, start, end)));
        List<ExpertLeaveDetail> detailList = leaveDetailService.list(leaveDetailQuery);
        if (detailList.isEmpty()) {
            return new ArrayList<>();
        }
        List<Long> leaveIdList = CollUtils.fieldList(detailList, ExpertLeaveDetail::getExpertLeaveId);
        LambdaQueryWrapper<ExpertLeave> leaveQuery = Wrappers.lambdaQuery(ExpertLeave.class)
                .select(ExpertLeave::getCreateBy)
                .in(ExpertLeave::getId, leaveIdList)
                .in(ExpertLeave::getStatus,
                        LeaveStatusEnum.APPLYING.getCode(),
                        LeaveStatusEnum.PASSED.getCode(),
                        LeaveStatusEnum.PASSED_END.getCode());
        List<ExpertLeave> leaveList = leaveService.list(leaveQuery);
        return CollUtils.fieldList(leaveList, ExpertLeave::getCreateBy);
    }

    @Transactional(rollbackFor = Exception.class)
    public void leaveAuditCallback(boolean status, Long applyId) {
        LambdaUpdateWrapper<ExpertLeave> update = Wrappers.lambdaUpdate(ExpertLeave.class)
                .set(ExpertLeave::getUpdateBy, LoginUserUtil.getUserId())
                .set(ExpertLeave::getUpdateOn, LocalDateTime.now())
                .eq(ExpertLeave::getAuditId, applyId);
        ExpertLeave leave = leaveService.getByAuditId(applyId);
        ExpertUserFullInfo leaveUser = userFullInfoService.getByUserId(leave.getLeaveUserId());
        if (status) {
            update.set(ExpertLeave::getStatus, LeaveStatusEnum.PASSED.getCode());
        } else {
            ExpertMetaApply metaApply = metaApplyService.getById(applyId);
            update.set(ExpertLeave::getStatus, LeaveStatusEnum.UN_PASSED.getCode());
            LambdaQueryWrapper<ExpertLeaveDetail> delete = Wrappers.lambdaQuery(ExpertLeaveDetail.class)
                    .eq(ExpertLeaveDetail::getExpertLeaveId, leave.getId());
            leaveDetailService.remove(delete);
        }
        leaveService.update(update);
    }

    public Map<LocalDate, Set<Long>> listLeaveIdByDate(LocalDate startDate, LocalDate endDate, Long expertId) {
        List<ExpertLeaveDetail> leaveDetails = leaveDetailService.listByDateAndLeaveUserId(startDate, endDate, expertId);
        return leaveDetails.stream().collect(Collectors.groupingBy(w -> w.getStartTime().toLocalDate(),
                Collectors.mapping(ExpertLeaveDetail::getExpertLeaveId, Collectors.toSet())));
    }

    public PageVo<LeaveListItemVO> listLeaveApplyingAndCouldBeStop(PagePo po) {
        LambdaQueryWrapper<ExpertLeave> query = Wrappers.lambdaQuery(ExpertLeave.class)
                .eq(ExpertLeave::getLeaveUserId, LoginUserUtil.getUserId())
                .and(wrapper -> wrapper.eq(ExpertLeave::getStatus, LeaveStatusEnum.APPLYING.getCode())
                        .or(wrapper1 -> wrapper1.eq(ExpertLeave::getStatus, LeaveStatusEnum.PASSED.getCode())
                                .gt(ExpertLeave::getCancelTime, LocalDateTime.now())));
        Page<ExpertLeave> page = leaveService.page(new Page<>(po.getPageNumber(), po.getPageSize()), query);
        if (page.getTotal() == 0) {
            return PageVo.empty();
        }
        List<LeaveListItemVO> result = CollUtils.convert(page.getRecords(), w -> {
            LeaveListItemVO item = new LeaveListItemVO();
            item.setStartTime(w.getStartTime());
            item.setEndTime(w.getEndTime());
            item.setStatus(w.getStatus());
            item.setLeaveType(w.getType());
            item.setLeaveId(w.getId());
            return item;
        });
        return PageVo.of(result, page.getTotal());
    }

}
