package com.hz.pm.api.expert.manage;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hz.pm.api.expert.constant.ReviewResultEnum;
import com.hz.pm.api.expert.model.dto.ReviewTemplateOptionDTO;
import com.hz.pm.api.expert.model.dto.ReviewTemplateSettingsDTO;
import com.hz.pm.api.expert.model.entity.ExpertReview;
import com.hz.pm.api.expert.model.entity.ReviewTemplateSettings;
import com.hz.pm.api.expert.model.req.ExpertReviewDetailReq;
import com.hz.pm.api.expert.model.vo.ExpertReviewDetailVO;
import com.hz.pm.api.expert.model.vo.ProjectReviewDetailVO;
import com.hz.pm.api.expert.model.vo.ReviewTemplateVO;
import com.hz.pm.api.expert.service.IExpertReviewService;
import com.hz.pm.api.expert.service.IReviewTemplateSettingsService;
import com.hz.pm.api.meeting.entity.domain.Meeting;
import com.hz.pm.api.meeting.service.IMeetingExpertService;
import com.hz.pm.api.meeting.service.IMeetingInnerProjectService;
import com.hz.pm.api.meeting.service.IMeetingService;
import com.hz.pm.api.user.util.LoginUserUtil;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.cache.lock.DistributedLock;
import com.ningdatech.file.entity.vo.result.AttachFileVo;
import com.ningdatech.file.service.FileService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

import static com.hz.pm.api.expert.model.vo.ProjectReviewDetailVO.ReviewDetailByTypeVO;
import static com.hz.pm.api.meeting.entity.enumeration.ExpertAttendStatusEnum.AGREED;

/**
 * <p>
 * ExpertReviewManage
 * </p>
 *
 * @author WendyYang
 * @since 14:25 2023/2/15
 */
@Component
@RequiredArgsConstructor
public class ExpertReviewManage {

    private final IExpertReviewService expertReviewService;
    private final DistributedLock distributedLock;
    private final IMeetingExpertService meetingExpertService;
    private final FileService fileService;
    private final IMeetingInnerProjectService meetingInnerProjectService;
    private final IMeetingService meetingService;
    private final IReviewTemplateSettingsService templateSettingsService;

    private static final String EXPERT_REVIEW_KEY = "expert_review:";

    private String buildExpertReviewKey(Long projectId, Long expertId) {
        return EXPERT_REVIEW_KEY + projectId + ":" + expertId;
    }

    private ExpertReviewDetailVO buildExpertReviewDetail(ExpertReview review) {
        return ExpertReviewDetailVO.builder()
                .reviewResult(review.getReviewResult())
                .creator(review.getCreator())
                .projectId(review.getProjectId())
                .templateId(review.getTemplateId())
                .otherAdvice(review.getOtherAdvice())
                .isFinal(review.getIsFinal())
                .createOn(review.getCreateOn())
                .attachFileId(review.getAttachFileId())
                .reviewTemplateOptions(JSONUtil.toList(review.getContent(), ReviewTemplateOptionDTO.class))
                .build();
    }

    public void expertReview(ExpertReviewDetailReq req) {
        Long userId = LoginUserUtil.getUserId();
        Long projectId = req.getProjectId();
        Long meetingId = req.getMeetingId();
        String expertReviewKey = buildExpertReviewKey(projectId, userId);
        if (!distributedLock.lock(expertReviewKey)) {
            throw BizException.wrap("保存评审意见失败，请重试");
        }
        try {
            List<ExpertReview> reviews = expertReviewService.listByProjectIdAndExpertId(projectId, userId, meetingId);
            if (req.getIsFinal()) {
                if (reviews.isEmpty()) {
                    throw BizException.wrap("请先填写个人评审意见");
                }
                if (reviews.size() > 1) {
                    throw BizException.wrap("不可重复填写最终评审意见");
                }
                int agreeCnt = meetingExpertService.countExpertByStatusAndMeetingId(AGREED, req.getMeetingId(), null);
                if (agreeCnt > 1) {
                    LambdaQueryWrapper<ExpertReview> query = Wrappers.lambdaQuery(ExpertReview.class)
                            .eq(ExpertReview::getProjectId, req.getProjectId())
                            .eq(ExpertReview::getMeetingId, req.getMeetingId())
                            .ne(ExpertReview::getCreateBy, userId);
                    if (expertReviewService.count(query) < agreeCnt - 1) {
                        throw BizException.wrap("所有组员填写完评审意见后，方可填写最终评审意见");
                    }
                }
            } else {
                if (!reviews.isEmpty()) {
                    throw BizException.wrap("不可重复填写评审意见");
                }
            }
            ExpertReview review = new ExpertReview();
            review.setReviewResult(req.getReviewResult());
            review.setContent(JSONUtil.toJsonStr(req.getReviewTemplateOptions()));
            review.setProjectId(req.getProjectId());
            review.setProjectCode(req.getProjectCode());
            review.setTemplateId(req.getTemplateId());
            review.setMeetingId(req.getMeetingId());
            review.setOtherAdvice(req.getOtherAdvice());
            review.setAttachFileId(req.getAttachFileId());
            review.setIsFinal(req.getIsFinal());
            review.setCreator(LoginUserUtil.getUsername());
            expertReviewService.save(review);
        } finally {
            distributedLock.releaseLock(expertReviewKey);
        }
    }

    public ExpertReviewDetailVO getExpertReviewDetail(Long projectId, Long userId, Long meetingId) {
        List<ExpertReview> reviews = expertReviewService.listByProjectIdAndExpertId(projectId, userId, meetingId);
        reviews.removeIf(ExpertReview::getIsFinal);
        if (reviews.isEmpty()) {
            throw BizException.wrap("评审记录不存在");
        }
        return buildExpertReviewDetail(reviews.get(0));
    }

    public List<ExpertReviewDetailVO> listReviews(Long projectId, Long meetingId, boolean onlyTeamMember) {
        LambdaQueryWrapper<ExpertReview> query = Wrappers.lambdaQuery(ExpertReview.class);
        query.eq(ExpertReview::getProjectId, projectId);
        query.eq(ExpertReview::getMeetingId, meetingId);
        query.eq(onlyTeamMember, ExpertReview::getIsFinal, Boolean.FALSE);
        query.orderByDesc(ExpertReview::getCreateOn);
        List<ExpertReview> reviews = expertReviewService.list(query);
        if (reviews.isEmpty()) {
            return Collections.emptyList();
        }
        List<Long> attachFileIds = CollUtils.fieldList(reviews, ExpertReview::getAttachFileId);
        Map<Long, AttachFileVo> fileMap = new HashMap<>(attachFileIds.size());
        if (!attachFileIds.isEmpty()) {
            List<AttachFileVo> files = fileService.getByIds(attachFileIds);
            files.forEach(w -> fileMap.put(w.getFileId(), w));
        }
        return CollUtils.convert(reviews, w -> {
            ExpertReviewDetailVO reviewDetail = buildExpertReviewDetail(w);
            reviewDetail.setAttachFile(fileMap.get(w.getAttachFileId()));
            return reviewDetail;
        });
    }

    public ProjectReviewDetailVO projectExpertReviewDetail(String projectCode) {
        ProjectReviewDetailVO detail = new ProjectReviewDetailVO();
        List<Long> meetingIds = meetingInnerProjectService.listMeetingIdByProjectCode(projectCode);
        if (meetingIds.isEmpty()) {
            return detail;
        }
        List<Meeting> meetings = meetingService.listByIds(meetingIds);
        Collection<Long> tmpMeetingIds = meetings.stream()
                .collect(Collectors.groupingBy(Meeting::getType,
                        Collectors.collectingAndThen(Collectors.toList(), w -> {
                            w.sort(Comparator.comparing(Meeting::getCreateOn));
                            return w.get(w.size() - 1).getId();
                        }))).values();
        LambdaQueryWrapper<ExpertReview> erQuery = Wrappers.lambdaQuery(ExpertReview.class)
                .in(ExpertReview::getMeetingId, tmpMeetingIds)
                .eq(ExpertReview::getProjectCode, projectCode)
                .orderByDesc(ExpertReview::getCreateOn);
        List<ExpertReview> expertReviews = expertReviewService.list(erQuery);
        if (expertReviews.isEmpty()) {
            return detail;
        }
        List<Long> templateIds = CollUtils.fieldList(expertReviews, ExpertReview::getTemplateId);
        List<ReviewTemplateSettings> templates = templateSettingsService.listByIds(templateIds);
        Map<Long, ReviewTemplateVO> templateMap = CollUtils.listToMap(templates,
                ReviewTemplateSettings::getId,
                ReviewTemplateSettingsManage::buildTemplateDetail);
        detail.setTemplates(templateMap.values());
        Map<Integer, ReviewDetailByTypeVO> map = new HashMap<>(8);
        expertReviews.forEach(review -> {
            ReviewTemplateVO template = templateMap.get(review.getTemplateId());
            ReviewDetailByTypeVO reviewDetailByType = map.computeIfAbsent(template.getTemplateType(), k -> {
                ReviewDetailByTypeVO tmpReviewDetail = new ReviewDetailByTypeVO();
                tmpReviewDetail.setReviewType(k);
                tmpReviewDetail.setTeamMemberReviews(new ArrayList<>());
                return tmpReviewDetail;
            });
            ExpertReviewDetailVO tmpReview = buildExpertReviewDetail(review);
            if (Boolean.TRUE.equals(review.getIsFinal())) {
                reviewDetailByType.setFinalReview(tmpReview);
            } else {
                reviewDetailByType.getTeamMemberReviews().add(tmpReview);
            }
        });
        detail.setReviews(map.values());
        return detail;
    }

    public List<ExpertReview> listFinalExpertReviews(Long meetingId) {
        LambdaQueryWrapper<ExpertReview> query = Wrappers.lambdaQuery(ExpertReview.class)
                .eq(ExpertReview::getIsFinal, Boolean.TRUE)
                .eq(ExpertReview::getMeetingId, meetingId);
        return expertReviewService.list(query);
    }

    public Map<Long, String> buildExpertReviewToStr(Long meetingId) {
        List<ExpertReview> reviews = listFinalExpertReviews(meetingId);
        if (reviews.isEmpty()) {
            return Collections.emptyMap();
        }
        List<Long> templateIds = CollUtils.fieldList(reviews, ExpertReview::getTemplateId);
        List<ReviewTemplateSettings> ts = templateSettingsService.listByIds(templateIds);
        Map<Long, ReviewTemplateSettings> tsMap = CollUtils.listToMap(ts, ReviewTemplateSettings::getId);
        Map<Long, String> resMap = new HashMap<>(8);
        for (ExpertReview review : reviews) {
            ReviewTemplateSettings settings = tsMap.get(review.getTemplateId());
            List<ReviewTemplateSettingsDTO> optionTemplates = JSONUtil.toList(settings.getContent(), ReviewTemplateSettingsDTO.class);
            List<ReviewTemplateOptionDTO> options = JSONUtil.toList(review.getContent(), ReviewTemplateOptionDTO.class);
            Map<Integer, ReviewTemplateOptionDTO> optionsMap = CollUtils.listToMap(options, ReviewTemplateOptionDTO::getQuestionSerialNo);
            StringBuilder str = new StringBuilder();
            optionTemplates.forEach(ot -> {
                ReviewTemplateOptionDTO rto = optionsMap.get(ot.getSerialNo());
                String optionsContent = ot.getOptions().stream()
                        .filter(w -> rto.getOptionSerialNo().contains(w.getSerialNo()))
                        .map(ReviewTemplateSettingsDTO.OptionDTO::getOption)
                        .collect(Collectors.joining("、"));
                str.append("<w:br/>").append(ot.getTitle()).append("：").append(optionsContent).append("；");
            });
            str.append("<w:br/>").append("其他意见或建议").append("：")
                    .append(StrUtil.blankToDefault(review.getOtherAdvice(), "--")).append("；");
            str.append("<w:br/>").append("评审结果").append("：")
                    .append(ReviewResultEnum.getByCode(review.getReviewResult()).getValue()).append("；");
            resMap.put(review.getProjectId(), str.toString());
        }
        return resMap;
    }
}
