package cn.axzo.msg.center.message.service.impl;

import cn.axzo.apollo.core.web.Result;
import cn.axzo.apollo.workspace.api.workspace.OrganizationalNodePractitionerWideApi;
import cn.axzo.apollo.workspace.api.workspace.WorkspaceApi;
import cn.axzo.apollo.workspace.api.workspace.req.OrganizationalNodePractitionerWideReqParams;
import cn.axzo.apollo.workspace.api.workspace.res.OrganizationalNodePractitionerWideDto;
import cn.axzo.apollo.workspace.api.workspace.res.SimpleWorkspaceRes;
import cn.axzo.basics.common.BeanMapper;
import cn.axzo.framework.core.util.MapUtil;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.im.center.api.feign.MessageApi;
import cn.axzo.im.center.api.vo.req.CustomMessageInfo;
import cn.axzo.im.center.common.enums.AppTypeEnum;
import cn.axzo.im.center.common.enums.BizTypeEnum;
import cn.axzo.maokai.api.client.OrganizationalTeamOuRelationApi;
import cn.axzo.maokai.api.vo.response.OrganizationalTeamOuRelationResp;
import cn.axzo.msg.center.api.enums.MsgRecordTerminalTypeEnum;
import cn.axzo.msg.center.api.enums.MsgStateEnum;
import cn.axzo.msg.center.api.response.MessageDetailRes;
import cn.axzo.msg.center.common.enums.TableIsDeleteEnum;
import cn.axzo.msg.center.common.exception.ServiceException;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.common.utils.PageHelperUtil;
import cn.axzo.msg.center.common.utils.PlaceholderResolver;
import cn.axzo.msg.center.dal.MessageGroupNodeDao;
import cn.axzo.msg.center.dal.PendingMessageRecordDao;
import cn.axzo.msg.center.dal.mapper.PendingMessageRecordMapper;
import cn.axzo.msg.center.domain.dto.PendingCalendarCodeDTO;
import cn.axzo.msg.center.domain.entity.MessageGroupNode;
import cn.axzo.msg.center.domain.entity.PendingMessageRecord;
import cn.axzo.msg.center.domain.entity.PendingRecordAdapter;
import cn.axzo.msg.center.domain.enums.NativeTypeEnum;
import cn.axzo.msg.center.domain.persistence.BaseEntityExt;
import cn.axzo.msg.center.inside.notices.config.PendingCalendarCodeConfig;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import cn.axzo.msg.center.message.domain.dto.GroupTreeNodePathDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO;
import cn.axzo.msg.center.message.domain.dto.MessageTemplateRouterDTO;
import cn.axzo.msg.center.message.domain.dto.PendingMessageDTO;
import cn.axzo.msg.center.message.domain.dto.PendingMessageStatisticDTO;
import cn.axzo.msg.center.message.domain.param.MessageGroupNodeStatisticParam;
import cn.axzo.msg.center.message.domain.param.PendingMessagePushParam;
import cn.axzo.msg.center.message.service.MessageGroupNodeService;
import cn.axzo.msg.center.message.service.MessageTemplateGroupService;
import cn.axzo.msg.center.message.service.MessageTemplateNewService;
import cn.axzo.msg.center.message.service.PendingMessageNewService;
import cn.axzo.msg.center.message.service.todo.MigrateUtils;
import cn.axzo.msg.center.message.service.todo.manage.TodoExt;
import cn.axzo.msg.center.notices.common.enums.IsDeletedEnum;
import cn.axzo.msg.center.service.dto.ButtonRouterDTO;
import cn.axzo.msg.center.service.dto.DetailRouterDTO;
import cn.axzo.msg.center.service.dto.IdentityDTO;
import cn.axzo.msg.center.service.dto.MessageCardContentItemDTO;
import cn.axzo.msg.center.service.dto.PersonDTO;
import cn.axzo.msg.center.service.dto.QueryOrderByDTO;
import cn.axzo.msg.center.service.enums.AppTerminalTypeEnum;
import cn.axzo.msg.center.service.enums.BizCategoryEnum;
import cn.axzo.msg.center.service.enums.BizFinalStateEnum;
import cn.axzo.msg.center.service.enums.IdentityTypeEnum;
import cn.axzo.msg.center.service.enums.MessageGroupCategoryEnum;
import cn.axzo.msg.center.service.enums.OrganizationTypeEnum;
import cn.axzo.msg.center.service.enums.PendingMessageRoleCategoryEnum;
import cn.axzo.msg.center.service.enums.PendingMessageStateEnum;
import cn.axzo.msg.center.service.enums.RouterCategoryEnum;
import cn.axzo.msg.center.service.enums.TerminalTypeEnum;
import cn.axzo.msg.center.service.pending.request.CompletePendingBySubCodeRequest;
import cn.axzo.msg.center.service.pending.request.CompletePendingMessageByIdRequest;
import cn.axzo.msg.center.service.pending.request.CompletePendingMessageRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessageByBizCodeRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessageFixedTemplatePageRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessageIterateRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessagePageRequest;
import cn.axzo.msg.center.service.pending.request.PendingMessageQueryRequest;
import cn.axzo.msg.center.service.pending.request.RevokePendingMessageByIdRequest;
import cn.axzo.msg.center.service.pending.request.SetHideRequest;
import cn.axzo.msg.center.service.pending.request.UpdatePendingMessageByIdRequest;
import cn.axzo.msg.center.service.pending.response.AnalysisPage;
import cn.axzo.msg.center.service.pending.response.PendingMessageIterateResponse;
import cn.axzo.msg.center.service.pending.response.PendingMessageResponse;
import cn.axzo.msg.center.service.pending.response.PendingMessageSimpleDTO;
import cn.axzo.msg.center.service.pending.response.PushPendingMessageDTO;
import cn.axzo.msg.center.service.template.response.MessageDetailStyle;
import cn.axzo.msg.center.utils.DateFormatUtil;
import cn.axzo.msg.center.utils.JSONObjectUtil;
import cn.axzo.msg.center.utils.MessageCardUtil;
import cn.axzo.msg.center.utils.MessageRouterUtil;
import cn.axzo.msg.center.utils.OrderFieldParseUtil;
import cn.axzo.msg.center.utils.UUIDUtil;
import cn.azxo.framework.common.model.Page;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Nullable;
import javax.validation.constraints.NotEmpty;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

/**
 * @author cold_blade
 * @version 1.0
 * @description
 * @date 2023/9/28
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PendingMessageNewServiceImpl implements PendingMessageNewService {

    private final PendingMessageRecordDao pendingMessageRecordDao;
    private final PendingMessageRecordMapper pendingMessageRecordMapper;
    private final MessageGroupNodeService messageGroupNodeService;
    private final MessageTemplateNewService messageTemplateNewService;
    private final MessageTemplateGroupService messageTemplateGroupService;
    private final WorkspaceApi workspaceApi;
    private final OrganizationalNodePractitionerWideApi organizationalNodePractitionerWideApi;
    private final PendingCalendarCodeConfig calendarCodeConfig;
    private final MessageApi messageApi;
    private final OrganizationalTeamOuRelationApi organizationalTeamOuRelationApi;
    private final PendingMessageBizConfig pendingMessageBizConfig;
    private final MessageGroupNodeDao messageGroupNodeDao;
    private final MessageRouterUtil messageRouterUtil;;

    @Value("${msg.center.pending.hide-seconds:60}")
    private int pendingDefaultHideSeconds;
    @Value("${msg.center.pending.analysisToken:123331}")
    private String analysisToken;

    @Override
    public List<PendingMessageStatisticDTO> groupStatistic(MessageGroupNodeStatisticParam param) {
        List<String> groupTreeRootNodeCodes = messageGroupNodeService
                .listGroupTreeRootNodeCodes(MessageGroupCategoryEnum.PENDING, param.getTerminalType());
        List<GroupTreeNodePathDTO> leafNodePaths = messageGroupNodeService.leafGroupNodeCodePathsByRootNodeCodes(groupTreeRootNodeCodes);
        return leafNodePaths.parallelStream()
                .map(e -> statistic(e, param))
                .collect(Collectors.toList());
    }

    @Override
    public Integer countUncompleted(MessageGroupNodeStatisticParam param) {
        List<String> groupTreeRootNodeCodes = messageGroupNodeService
                .listGroupTreeRootNodeCodes(MessageGroupCategoryEnum.PENDING, param.getTerminalType());
        List<GroupTreeNodePathDTO> leafNodePaths = messageGroupNodeService.leafGroupNodeCodePathsByRootNodeCodes(groupTreeRootNodeCodes);
        return doStatistic(leafNodePaths, param);
    }

    @Override
    public Integer countUncompleted(Long personId, Collection<Long> templateIds) {
        if (Objects.isNull(personId) || CollectionUtils.isEmpty(templateIds)) {
            log.info("the personId is null or templateIds is empty. personId:[{}], templateIds:{}", personId, templateIds);
            return 0;
        }
        List<String> templateCodes = messageTemplateNewService.listTemplateCodesByIds(templateIds);
        if (CollectionUtils.isEmpty(templateCodes)) {
            log.info("the templateCodes is empty. templateIds:{}", templateIds);
            return 0;
        }
        PersonDTO operator = PersonDTO.builder().id(personId).build();
        LambdaQueryChainWrapper<PendingMessageRecord> query = pendingMessageRecordDao.lambdaQuery()
                .in(PendingMessageRecord::getTemplateCode, templateCodes)
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT);
        buildPersonCondition(query, false, PendingMessageRoleCategoryEnum.EXECUTOR, operator);
        return query.count();
    }

    private void limitPageSize(PendingMessagePageRequest request) {
        long maxPageSize = 50L;
        if (request.getPageSize() == null) {
            request.setPageSize(10L);
        }
        else if (request.getPageSize() <= 0 && request.getPageSize() > maxPageSize) {
            request.setPageSize(maxPageSize);
        }
    }

    @Override
    public AnalysisPage<PendingMessageResponse> pageQuery(PendingMessagePageRequest request) {
        limitPageSize(request);
        PersonDTO operator = PersonDTO.from(request.getPersonId(), request.getIdentityId(), request.getIdentityType());
        PendingMessageStateEnum pendingMessageState = fetchPendingMessageState(request.getRoleCategory(), request.getMsgState());
        BizFinalStateEnum bizFinalState = fetchBizFinalState(request.getRoleCategory(), request.getBizFinalState());
        // 开始构建分页查询条件
        LambdaQueryChainWrapper<PendingMessageRecord> query = pendingMessageRecordDao.lambdaQuery()
                .eq(Objects.nonNull(pendingMessageState), PendingMessageRecord::getState, pendingMessageState)
                .eq(Objects.nonNull(bizFinalState), PendingMessageRecord::getBizFinalState, bizFinalState)
                .eq(PendingMessageRecord::getIsDelete, IsDeletedEnum.NO.getCode())
                .eq(Objects.nonNull(request.getWorkspaceId()), PendingMessageRecord::getOrgId, request.getWorkspaceId());
        // REQ-1507期发起人不通过ouId过滤, 只有执行人对ouId进行过滤
        List<Long> determinedOuIds = Collections.emptyList();
        if (request.determineRoleCategory() == PendingMessageRoleCategoryEnum.EXECUTOR) {
            determinedOuIds = appendExecutorOuIdExpr(query, request.getOuId());
        }
        if (StringUtils.isNotBlank(request.getTitle())) {
            query.and(v -> v.like(PendingMessageRecord::getTitle, request.getTitle())
                    .or().like(PendingMessageRecord::getPromoterName, request.getTitle()));
        }
        // 设置隐藏时间
        buildHideUntil(query);
        // 构建人维度的查询条件
        buildPersonCondition(query, request.getWithIdentify(), request.getRoleCategory(), operator);
        List<String> templateCodes = determinePageQueryTemplateCodes(request);
        List<String> templateCodesCopy = new ArrayList<>(templateCodes);
        if (CollectionUtils.isEmpty(templateCodes)) {
            // 如果该分类未关联任何模板，直接返回空集合
            log.info("there is not any template matched... appTerminal:[{}]", request.getAppTerminalType());
            return AnalysisPage.emptyPage(request.getPage(), request.getPageSize());
        }
        query.in(PendingMessageRecord::getTemplateCode, templateCodes);
//        query.groupBy(PendingMessageRecord::getTemplateCode,PendingMessageRecord::getBizCode);
        // 构建排序条件
        buildSortCondition(query, request.getOrderFields(), pendingMessageState);
        IPage<PendingMessageRecord> page = request.toPage();
        List<PendingMessageRecord> pendingMessageRecords = pendingMessageRecordMapper.selectPageWithCustomSql(query.getWrapper(), page);
        templateCodes = pendingMessageRecords.stream().map(PendingMessageRecord::getTemplateCode)
                .collect(Collectors.toList());
        List<MessageTemplateDTO> messageTemplates = messageTemplateNewService.listByTemplateCodes(templateCodes);
        List<PendingMessageResponse> responseRecords = pendingMessageRecords.stream()
                .map(e -> convert(e, messageTemplates).toResponse(request.getTerminalType()))
                .collect(Collectors.toList());
        AnalysisPage<PendingMessageResponse> resultPage = new AnalysisPage<>(
                request.getPage(), request.getPageSize(), page.getTotal(), responseRecords);
        resultPage.setEnableAnalysis(analysisToken.equals(request.getAnalysisToken()));
        if (resultPage.isEnableAnalysis()) {
            resultPage.addAnalysis("terminalConfig", getTerminalConfigInfo());
        }
        resultPage.addAnalysis("templateCodes", new HashSet<>(templateCodesCopy));
        resultPage.addAnalysis("personId", request.getPersonId());
        resultPage.addAnalysis("operator", operator);
        resultPage.addAnalysis("ouId", request.getOuId());
        resultPage.addAnalysis("determinedOuIds", determinedOuIds);
        resultPage.addAnalysis("request", request);
        return resultPage;
    }

    public Map<String, Object> getTerminalConfigInfo() {
        Map<AppTerminalTypeEnum, List<Long>> appType2NodeIds = pendingMessageBizConfig.getMsgGroupConfig();
        if (appType2NodeIds == null || appType2NodeIds.isEmpty()) {
            return Collections.emptyMap();
        }
        Set<Long> nodeIds = appType2NodeIds.values().stream()
                .flatMap(Collection::stream)
                .collect(toSet());
        Map<Long, MessageGroupNode> nodeId2Node = messageGroupNodeDao.lambdaQuery()
                .eq(MessageGroupNode::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .in(MessageGroupNode::getId, nodeIds)
                .list().stream()
                .collect(toMap(BaseEntityExt::getId, identity()));
        HashMap<String, Object> result = new HashMap<>();
        for (AppTerminalTypeEnum terminal : appType2NodeIds.keySet()) {
            List<Long> ids = appType2NodeIds.getOrDefault(terminal, Collections.emptyList());
            List<Map<String, Object>> nodeInfoList = new LinkedList<>();
            for (Long id : ids) {
                MessageGroupNode node = nodeId2Node.get(id);
                if (node == null) continue;
                HashMap<String, Object> info = new HashMap<>();
                info.put("id", node.getId());
                info.put("name", node.getName());
                info.put("code", node.getCode());
                nodeInfoList.add(info);
            }
            result.put(String.format("%s:%s", terminal.name(), terminal.getDesc()), nodeInfoList);
        }
        return result;
    }

    @Override
    public Page<PendingMessageIterateResponse> iterate(PendingMessageIterateRequest request) {
        IPage<PendingMessageRecord> page = pendingMessageRecordDao.lambdaQuery()
                //.eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .eq(PendingMessageRecord::getTemplateCode, request.getTemplateCode())
                .le(request.getCratedAtAfter() != null, PendingMessageRecord::getCreateAt, request.getCratedAtAfter())
                .eq(StringUtils.isNotBlank(request.getBizCode()),
                        PendingMessageRecord::getBizCode, request.getBizCode())
                .orderByAsc(PendingMessageRecord::getId)
                .page(request.toPage());
        if (CollectionUtils.isEmpty((page.getRecords()))) {
            return new Page<>((long) request.getPage(), (long) request.getPageSize(), 0L, Collections.emptyList());
        }
        List<PendingMessageIterateResponse> pageItems = page.getRecords().stream()
                .map(m -> BeanMapper.copyBean(m, PendingMessageIterateResponse.class))
                .collect(Collectors.toList());
        return new Page<>((long)request.getPage(), (long)request.getPageSize(), page.getTotal(), pageItems);
    }

    @Nullable
    @Override
    public List<String> determinePageQueryTemplateCodes(PendingMessagePageRequest request) {
        List<String> groupTreeRootNodeCodes = messageGroupNodeService
                .listGroupTreeRootNodeCodes(MessageGroupCategoryEnum.PENDING, request.getAppTerminalType());
        // 模板的分类，对代办进行分组过滤
        List<GroupTreeNodePathDTO> leafNodePaths = messageGroupNodeService
                .leafGroupNodeCodePathsByRootNodeCodes(groupTreeRootNodeCodes);
        List<String> paths = leafNodePaths.stream().map(GroupTreeNodePathDTO::getNodeCodePath).collect(Collectors.toList());
        if (StringUtils.isNotBlank(request.getGroupNodeCode())) {
            GroupTreeNodePathDTO nodePath = messageGroupNodeService.queryLeafGroupNodeCodePath(request.getGroupNodeCode())
                    .orElse(null);
            if (nodePath == null) {
                // 如果该分类未关联任何模板，直接返回空集合
                log.info("invalid group node code...");
                paths = Collections.emptyList();
            } else {
                paths = Lists.newArrayList(nodePath.getNodeCodePath());
            }
        }
        List<String> templateCodes = null;
        if (CollectionUtils.isNotEmpty(paths)) {
            templateCodes = messageTemplateGroupService.listMessageTemplateCodes(paths);
        }
        return templateCodes;
    }

    @Override
    public List<PendingMessageResponse> getPendingMessageByAppWorker(PendingMessageFixedTemplatePageRequest request) {
        List<PendingMessageResponse> result = new ArrayList<>();
        // 未来无待办
        LocalDate localDate = request.getSelectDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        if (localDate.isAfter(LocalDate.now())) {
            return result;
        }
        Boolean isNowDay = localDate.isBefore(LocalDate.now()) ? false : true;
        PendingCalendarCodeDTO calendarCodeDTO = buildCalendarCodesDTO(calendarCodeConfig);
        List<PendingMessageRecord> pendingMessageRecords = pendingMessageRecordMapper.queryByTemplateCodes(calendarCodeDTO, request.getWorkspaceId(), request.getSelectDate(), request.getPersonId(), isNowDay);
        List<MessageTemplateDTO> messageTemplates = messageTemplateNewService.listByTemplateCodes(calendarCodeConfig.getCalendarAllCodes());
        result = pendingMessageRecords.stream()
                .map(e -> convert(e, messageTemplates).toResponse(request.getTerminalType()))
                .collect(Collectors.toList());
        // 构建分页结构返回
        return result;
    }

    private PendingCalendarCodeDTO buildCalendarCodesDTO(PendingCalendarCodeConfig calendarCodeConfig) {
        return new PendingCalendarCodeDTO(calendarCodeConfig.getConstructionCodes(), calendarCodeConfig.getClockInCodes(), calendarCodeConfig.getPunchInCodes(), calendarCodeConfig.getCalendarAllCodes());
    }

    @Deprecated
    @Override
    public Page<MessageDetailRes> compatiblePageQuery(PendingMessagePageRequest request) {
        limitPageSize(request);
        List<String> groupTreeRootNodeCodes = messageGroupNodeService
                .listGroupTreeRootNodeCodes(MessageGroupCategoryEnum.PENDING, request.getAppTerminalType());
        PendingMessageStateEnum pendingMessageState = fetchPendingMessageState(request.getRoleCategory(), request.getMsgState());
        PersonDTO operator = PersonDTO.from(request.getPersonId(), request.getIdentityId(), request.getIdentityType());
        // 开始构建分页查询条件
        LambdaQueryChainWrapper<PendingMessageRecord> query = pendingMessageRecordDao.lambdaQuery()
                .eq(Objects.nonNull(request.getOuId()), PendingMessageRecord::getOuId, request.getOuId())
                .eq(Objects.nonNull(pendingMessageState), PendingMessageRecord::getState, pendingMessageState);
        // 设置隐藏时间
        buildHideUntil(query);
        // 构建人维度的查询条件
        buildPersonCondition(query, request.getWithIdentify(), request.getRoleCategory(), operator);
        // 模板的分类，对代办进行分组过滤
        List<GroupTreeNodePathDTO> leafNodePaths = messageGroupNodeService
                .leafGroupNodeCodePathsByRootNodeCodes(groupTreeRootNodeCodes);
        if (StringUtils.isNotBlank(request.getGroupNodeCode())) {
            GroupTreeNodePathDTO nodePath = messageGroupNodeService.queryLeafGroupNodeCodePath(request.getGroupNodeCode())
                    .orElse(null);
            if (Objects.isNull(nodePath)
                    || leafNodePaths.stream().noneMatch(e -> e.equals(nodePath))) {
                // 如果该分类未关联任何模板，直接返回空集合
                log.info("invalid group node code...");
                return PageHelperUtil.emptyPage(request.getPage(), request.getPageSize());
            }
            leafNodePaths = Lists.newArrayList(nodePath);
        }
        List<String> paths = leafNodePaths.stream().map(GroupTreeNodePathDTO::getNodeCodePath).collect(Collectors.toList());
        Map<String, List<String>> templateCodesPathMap = messageTemplateGroupService.groupingByMessageTemplateCodes(paths);
        if (MapUtil.isEmpty(templateCodesPathMap)) {
            // 如果该分类未关联任何模板，直接返回空集合
            log.info("there is not any template matched... appTerminal:[{}]", request.getAppTerminalType());
            return PageHelperUtil.emptyPage(request.getPage(), request.getPageSize());
        }
        query.in(PendingMessageRecord::getTemplateCode, templateCodesPathMap.keySet());
        // 构建排序条件
        buildSortCondition(query, request.getOrderFields(), pendingMessageState);
        IPage<PendingMessageRecord> result = query.page(request.toPage());
        if (CollectionUtils.isEmpty(result.getRecords())) {
            return Page.toPage(request.getPage(), request.getPageSize(), result.getTotal(), Collections.emptyList());
        }
        List<String> templateCodes = result.getRecords().stream().map(PendingMessageRecord::getTemplateCode)
                .collect(Collectors.toList());
        List<MessageTemplateDTO> messageTemplates = messageTemplateNewService.listByTemplateCodes(templateCodes);
        Map<String, GroupTreeNodePathDTO> tmpMap = leafNodePaths.stream()
                .collect(toMap(GroupTreeNodePathDTO::getNodeCodePath, identity(), (pre, next) -> next));
        final Map<String, String> templateCodeNodeNameMap = new HashMap<>();
        templateCodes.stream()
                .filter(templateCodesPathMap::containsKey)
                .forEach(e -> {
                    List<GroupTreeNodePathDTO> codePaths = templateCodesPathMap.get(e).stream()
                            .map(tmpMap::get)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(codePaths)) {
                        templateCodeNodeNameMap.put(e, codePaths.get(0).getNodeName());
                    }
                });
        List<MessageDetailRes> records = result.getRecords().stream()
                .map(e -> convert(e, messageTemplates))
                .map(e -> convert(e, templateCodeNodeNameMap, request.getTerminalType()))
                .collect(Collectors.toList());
        return Page.toPage(request.getPage(), request.getPageSize(), result.getTotal(), records);
    }

    @Override
    public PendingMessageResponse query(PendingMessageQueryRequest request) {
        return pendingMessageRecordDao.queryByIdentityCode(request.getMsgIdentityCode())
                .filter(v -> filter(v, request))
                .map(v -> {
                    List<MessageTemplateDTO> messageTemplates = messageTemplateNewService
                            .listByTemplateCodes(Lists.newArrayList(v.getTemplateCode()));
                    return convert(v, messageTemplates).toResponse(request.getTerminalType());
                })
                .orElse(null);
    }

    @Override
    public Optional<PendingMessageDTO> detail(String msgIdentityCode, TerminalTypeEnum terminalType) {
        if (StringUtils.isBlank(msgIdentityCode)) {
            log.info("the param of identityCode is blank.");
            return Optional.empty();
        }
        PendingMessageRecord record = pendingMessageRecordDao.queryByIdentityCode(msgIdentityCode)
                .orElse(null);
        if (Objects.isNull(record)) {
            return Optional.empty();
        }
        List<MessageTemplateDTO> messageTemplates = messageTemplateNewService
                .listByTemplateCodes(Lists.newArrayList(record.getTemplateCode()));
        return Optional.of(convert(record, messageTemplates));
    }

    @Override
    public List<PushPendingMessageDTO> push(PendingMessagePushParam param) {
        BizAssertions.assertNotEmpty(param.getExecutor(), "接受者不能为空");
        MessageTemplateDTO msgTemplate = messageTemplateNewService
                .queryEnableTemplateByCode(param.getTemplateCode())
                .orElseThrow(() -> new ServiceException("not found message template."));
        // 生成requestNo
        String requestNo = UUIDUtil.uuidString();
        // 查询工作台信息
        SimpleWorkspaceRes workspace = null;
        Long workspaceId = param.getWorkspaceId();
        Result<SimpleWorkspaceRes> workspaceRes = workspaceApi.getOne(workspaceId);
        if (200 == workspaceRes.getCode()) {
            workspace = workspaceRes.getData();
        } else {
            log.info("未查询到工作台信息 workspaceId:{}", workspaceId);
        }
        List<PendingMessageRecord> record = convert(param, msgTemplate, requestNo, workspace);
        pendingMessageRecordDao.saveBatch(record);
        try {
            ListUtils.emptyIfNull(record).forEach(re -> {
                // 发送IM通知
                pushIMMessage(re.getExecutorPersonId());
            });
        } catch (Exception e) {
            log.error("sendCustomMessage error.", e);
        }
        return record.stream().map(e -> PushPendingMessageDTO.builder()
                .id(e.getId())
                .executorId(e.getExecutorId())
                .executorPersonId(e.getExecutorPersonId())
                .requestNo(e.getRequestNo())
                .build()).collect(Collectors.toList());
    }

    @Override
    public Boolean complete(String requestNo) {
        if (StringUtils.isBlank(requestNo)) {
            log.warn("the message requestNo is blank.");
            return false;
        }
        log.info("the [{}] record is updated complete.", requestNo);
        return pendingMessageRecordDao.lambdaUpdate()
                .set(PendingMessageRecord::getState, PendingMessageStateEnum.COMPLETED)
                .set(PendingMessageRecord::getHideUntil, null)
                .eq(PendingMessageRecord::getRequestNo, requestNo)
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
    }

    @Override
    public Boolean completeById(CompletePendingMessageByIdRequest param) {
        log.info("the [{}] record is updated complete by id.", param);
        String bizExtParam = null;
        if (StringUtils.isNotBlank(param.getBizExtParams())) {
            bizExtParam = param.getBizExtParams();
        }
        Boolean update = pendingMessageRecordDao.lambdaUpdate()
                .set(PendingMessageRecord::getState, PendingMessageStateEnum.COMPLETED)
                .set(StringUtils.isNotBlank(bizExtParam), PendingMessageRecord::getBizExtParam, bizExtParam)
                .set(PendingMessageRecord::getHideUntil, null)
                .eq(PendingMessageRecord::getId, param.getId())
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
        try {
            if (update) {
                PendingMessageRecord one = pendingMessageRecordDao.lambdaQuery().eq(PendingMessageRecord::getId, param.getId()).one();
                // 发送IM通知
                pushIMMessage(one.getExecutorPersonId());
            }
        } catch (Exception e) {
            log.error("sendCustomMessage error.", e);
        }
        return update;
    }

    @Override
    public Boolean revoke(String requestNo) {
        if (StringUtils.isBlank(requestNo)) {
            log.warn("the message requestNo is blank.");
            return false;
        }
        log.info("the [{}] record is updated retract.", requestNo);
        return pendingMessageRecordDao.lambdaUpdate()
                .set(PendingMessageRecord::getState, PendingMessageStateEnum.RETRACT)
                .set(PendingMessageRecord::getHideUntil, null)
                .eq(PendingMessageRecord::getRequestNo, requestNo)
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
    }

    @Override
    public Boolean revokeByMsgId(String msgId) {
        if (StringUtils.isBlank(msgId)) {
            log.warn("the message msgId is blank.");
            return false;
        }
        log.info("the [{}] record is updated retract by id.", msgId);
        Boolean update = pendingMessageRecordDao.lambdaUpdate()
                .set(PendingMessageRecord::getState, PendingMessageStateEnum.RETRACT)
                .set(PendingMessageRecord::getHideUntil, null)
                .eq(PendingMessageRecord::getId, msgId)
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
        try {
            if (update) {
                PendingMessageRecord one = pendingMessageRecordDao.lambdaQuery().eq(PendingMessageRecord::getId, msgId).one();
                // 发送IM通知
                pushIMMessage(one.getExecutorPersonId());
            }
        } catch (Exception e) {
            log.error("sendCustomMessage error.", e);
        }
        return update;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean completeByTemplateCodeBizCode(CompletePendingMessageRequest param) {
        log.info("the [{}] record is completeByTemplateCodeBizCode retract.", param);
        Boolean result = false;
        if (null != param.getFinalStatus() && param.getFinalStatus() && null == param.getBizFinalStateEnum()) {
            return result;
        }
        result = pendingMessageRecordDao.lambdaUpdate()
                .set(PendingMessageRecord::getState, PendingMessageStateEnum.COMPLETED)
                .set(PendingMessageRecord::getHideUntil, null)
                .eq(PendingMessageRecord::getTemplateCode, param.getTemplateCode())
                .eq(PendingMessageRecord::getBizCode, param.getBizCode())
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
        // 关联业务的终态设置
        if (null != param.getFinalStatus() && param.getFinalStatus()) {
            result = pendingMessageRecordDao.lambdaUpdate()
                    .set(PendingMessageRecord::getBizFinalState, param.getBizFinalStateEnum())
                    .eq(PendingMessageRecord::getTemplateCode, param.getTemplateCode())
                    .eq(PendingMessageRecord::getBizCode, param.getBizCode())
                    .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                    .eq(PendingMessageRecord::getState, PendingMessageStateEnum.COMPLETED)
                    .update();
        }
        try {
            if (result) {
                List<PendingMessageRecord> list = pendingMessageRecordDao.lambdaQuery()
                        .eq(PendingMessageRecord::getTemplateCode, param.getTemplateCode())
                        .eq(PendingMessageRecord::getBizCode, param.getBizCode())
                        .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                        .list();
                ListUtils.emptyIfNull(list).forEach(record -> {
                    // 发送IM通知
                    pushIMMessage(record.getExecutorPersonId());
                });
            }
        } catch (Exception e) {
            log.error("sendCustomMessage error.", e);
        }
        return result;
    }

    private void pushIMMessage(Long personId) {
        log.info("the [{}] record is personId.", personId);
        if (personId != null) {
            CustomMessageInfo imRequest = new CustomMessageInfo();
            imRequest.setAppTypeList(Lists.newArrayList(AppTypeEnum.CM, AppTypeEnum.CMP));
            imRequest.setToPersonId(String.valueOf(personId));
            imRequest.setBizType(BizTypeEnum.PENDING);
            messageApi.sendCustomMessage(imRequest);
        }
    }

    @Override
    public Boolean completeByTemplateCodeSubBizCode(CompletePendingBySubCodeRequest param) {
        log.info("the [{}] record is completeByTemplateCodeSubBizCode retract.", param);
        return pendingMessageRecordDao.lambdaUpdate()
                .set(PendingMessageRecord::getState, PendingMessageStateEnum.COMPLETED)
                .set(PendingMessageRecord::getHideUntil, null)
                .eq(PendingMessageRecord::getTemplateCode, param.getTemplateCode())
                .eq(PendingMessageRecord::getBizCode, param.getBizCode())
                .eq(PendingMessageRecord::getSubBizCode, param.getSubBizCode())
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
    }

    @Override
    public Boolean revokeByTemplateCodeBizCode(CompletePendingMessageRequest param) {
        log.info("the [{}] record is revokeByTemplateCodeBizCode retract.", param);
        return pendingMessageRecordDao.lambdaUpdate()
                .set(PendingMessageRecord::getState, PendingMessageStateEnum.RETRACT)
                .set(PendingMessageRecord::getHideUntil, null)
                .eq(PendingMessageRecord::getTemplateCode, param.getTemplateCode())
                .eq(PendingMessageRecord::getBizCode, param.getBizCode())
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
    }

    @Override
    public Boolean revokeById(RevokePendingMessageByIdRequest param) {
        log.info("the [{}] record is RevokePendingMessageByIdRequest.", param);
        return pendingMessageRecordDao.lambdaUpdate()
                .set(PendingMessageRecord::getState, PendingMessageStateEnum.RETRACT)
                .set(PendingMessageRecord::getHideUntil, null)
                .eq(PendingMessageRecord::getId, param.getId())
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
    }

    @Override
    public Boolean updateById(UpdatePendingMessageByIdRequest param) {
        log.info("the [{}] record is UpdatePendingMessageByIdRequest.", param);
        BizAssertions.assertFalse(
                StringUtils.isBlank(param.getRouterParam()) && StringUtils.isBlank(param.getBizParam()),
                "routerParam和bizParam至少要转一个");
        return pendingMessageRecordDao.lambdaUpdate()
                .set(StringUtils.isNotBlank(param.getBizParam()),
                        PendingMessageRecord::getBizExtParam, param.getBizParam())
                .set(StringUtils.isNotBlank(param.getRouterParam()),
                        PendingMessageRecord::getRouterParams, param.getRouterParam())
                .set(StringUtils.isNotBlank(param.getTemplateCode()),
                        PendingMessageRecord::getTemplateCode, param.getTemplateCode())
                .eq(PendingMessageRecord::getId, param.getId())
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .update();
    }

    @Override
    public List<PendingMessageSimpleDTO> getLatestByBizCode(PendingMessageByBizCodeRequest param) {
        List<String> bizCodes = param.getBizCodes();
        if (CollectionUtils.isEmpty(bizCodes)) {
            return Collections.emptyList();
        }
        bizCodes = bizCodes.stream()
                .filter(StringUtils::isNotBlank)
                .distinct()
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(bizCodes)) {
            return Collections.emptyList();
        }
        log.info("getLatestByBizCode param->{}", JSON.toJSONString(param));
        List<PendingMessageRecord> pendingMessageRecords = pendingMessageRecordDao.lambdaQuery()
                .in(PendingMessageRecord::getBizCode, bizCodes)
                .eq(PendingMessageRecord::getIsDelete, TableIsDeleteEnum.NORMAL.value)
                .last("LIMIT 500")
                .list();
        Map<String, List<PendingMessageRecord>> groupByMap = pendingMessageRecords.stream().collect(Collectors.groupingBy(PendingMessageRecord::getBizCode));
        Map<String, PendingMessageRecord> groupByMapFiltered = new HashMap<>(groupByMap.size());
        for (Map.Entry<String, List<PendingMessageRecord>> entry : groupByMap.entrySet()) {
            List<PendingMessageRecord> value = entry.getValue();
            List<PendingMessageRecord> personRecords = new ArrayList<>(value.size());
            // 存在操作人
            if (param.getOperatorPersonId() != null) {
                personRecords = ListUtils.emptyIfNull(value).stream().filter(e -> e.getExecutorPersonId().equals(param.getOperatorPersonId())).collect(Collectors.toList());
            }
            else if (null != param.getOperatorIdentityId()) {
                personRecords = ListUtils.emptyIfNull(value).stream().filter(e -> e.getExecutorId().equals(param.getOperatorIdentityId())).collect(Collectors.toList());
            }
            // 流程最终的记录
            List<PendingMessageRecord> pendingMessageRecords1 = CollectionUtils.isEmpty(personRecords) ? value : personRecords;
            // 获取最新的待办
            PendingMessageRecord maxRecord = pendingMessageRecords1.stream().max(Comparator.comparing(PendingMessageRecord::getId)).orElseGet(null);
            if (null != maxRecord) {
                groupByMapFiltered.put(entry.getKey(), maxRecord);
            }
        }
        List<PendingMessageRecord> groupByList = groupByMapFiltered.values().stream().collect(Collectors.toList());
        List<PendingMessageSimpleDTO> pendingMessageSimpleDTOS = BeanUtil.copyToList(groupByList, PendingMessageSimpleDTO.class);
        return pendingMessageSimpleDTOS;
    }

    @Override
    public Boolean setHide(SetHideRequest req) {
        BizAssertions.assertTrue(StringUtils.isNotBlank(req.getSubBizCode()), "subBizCode不能为空");
        int seconds = req.getHideSeconds() == null ? pendingDefaultHideSeconds : req.getHideSeconds();
        Date expireTime = DateTime.now().plusSeconds(seconds).toDate();
        return pendingMessageRecordDao.lambdaUpdate()
                .eq(PendingMessageRecord::getSubBizCode, req.getSubBizCode())
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .set(PendingMessageRecord::getHideUntil, expireTime)
                .update();
    }

    public PendingMessageDTO convert(PendingRecordAdapter adapter, List<MessageTemplateDTO> messageTemplates) {
        PendingMessageDTO pendingMessage;
        if (adapter instanceof PendingMessageRecord) {
            pendingMessage = PendingMessageDTO.from((PendingMessageRecord)adapter);
        } else {
            pendingMessage = PendingMessageDTO.from(MigrateUtils.convertAdapterToPending(adapter));
        }
        // 发起人单位、项目信息
        pendingMessage.setPromoterOuId(adapter.getPromoterOuId());
        pendingMessage.setPromoterWorkspaceId(adapter.getPromoterOrgId());
        pendingMessage.setPromoterWorkspaceName(adapter.getPromoterOrgName());
        pendingMessage.setPromoterWorkspaceType(adapter.getPromoterOrgType());
        // 接收者单位、项目信息
        pendingMessage.setExecutorOuId(adapter.getOuId());
        pendingMessage.setExecutorWorkspaceId(adapter.getOrgId());
        pendingMessage.setExecutorWorkspaceName(adapter.getOrgName());
        pendingMessage.setExecutorWorkspaceType(adapter.getExecutorOrgType());
        // 兼容页面老逻辑
        pendingMessage.setOuId(adapter.getOuId());
        pendingMessage.setWorkspaceId(adapter.getPromoterOrgId());
        // 只有项目才设置发起人项目部名称, 单位的话不返回, 因为会导致待办详情中显示重复的单位信息
        if (adapter.getPromoterOrgType() == OrganizationTypeEnum.PROJECT)
            pendingMessage.setWorkspaceName(adapter.getPromoterOrgName());
        else
            pendingMessage.setWorkspaceName("");
        // 其它信息
        pendingMessage.setTodoType(adapter.getTodoType());
        pendingMessage.setIsRead(adapter.isRead());
        pendingMessage.setProposedButtons(adapter.getCustomButtons());
        pendingMessage.setBizExtParamObj(adapter.getBizExtParamObj());
        pendingMessage.setRouterExtParamObj(adapter.getRouterParamObj());

        Map<String, MessageTemplateDTO> templateCode2Template = messageTemplates.stream()
                .collect(toMap(MessageTemplateDTO::getCode, identity()));
        MessageTemplateDTO template = templateCode2Template.get(adapter.getTemplateCode());
        if (template != null) {
            pendingMessage.setTemplateCategory(template.getMsgCategory());
        }
        if (template != null && template.getDetailStyleCode() != null) {
            MessageDetailStyle detailStyle = pendingMessageBizConfig
                    .findMessageDetailType(template.getDetailStyleCode())
                    .orElse(null);
            pendingMessage.setDetailStyle(detailStyle);
        }
        // 对应模板的路由策略
        MessageTemplateRouterDTO msgTemplateRouter = messageTemplates.stream()
                .filter(e -> Objects.equals(e.getCode(), adapter.getTemplateCode()))
                .findFirst()
                .map(MessageTemplateDTO::getMsgTemplateRouter)
                .orElse(null);
        // 解析并替换掉路由地址中的动态参数变量
        JSONObject routerParam = JSONObjectUtil.parseObject(adapter.getRouterParams());
        routerParam.put(TodoExt.CTX, ImmutableMap.of(
                "business", adapter.getTodoBusiness(),
                "todo", adapter.getTodo()));
        JSONObject authParam = new JSONObject();
        if (!routerParam.containsKey("ouId")) {
            authParam.put("ouId", adapter.getOuId());
        }
        if (!routerParam.containsKey("workspaceId")) {
            authParam.put("workspaceId", adapter.getOrgId());
        }
        authParam.putAll(routerParam);
        // 流程待办 - url无身份ID，手动拼接
        if (adapter.getBizCategory() == BizCategoryEnum.FLOW && (!authParam.containsKey("identityCode") || null == authParam.get("identityCode"))) {
            authParam.put("identityCode", adapter.getIdentityCode());
        }
        msgTemplateRouter = messageRouterUtil.parseRouteUrl(msgTemplateRouter, authParam);
        msgTemplateRouter = messageRouterUtil.parseAndConcatRouteUrl(template, msgTemplateRouter, routerParam);
        pendingMessage.setMsgTemplateRouter(msgTemplateRouter);
        // 获取模板卡片信息
        List<MessageCardContentItemDTO> msgCardContentItems = messageTemplates.stream()
                .filter(e -> Objects.equals(e.getCode(), adapter.getTemplateCode()))
                .findFirst()
                .map(MessageTemplateDTO::getMsgCardContentItems)
                .orElse(null);
        // 解析并替换卡片信息里面的动态参数变量
        msgCardContentItems = MessageCardUtil.parseMessageCard(msgCardContentItems, adapter.getBizExtParam());
        pendingMessage.setCardItems(msgCardContentItems);
        return pendingMessage;
    }

    private MessageDetailRes convert(PendingMessageDTO response, Map<String, String> templateCodeNodeNameMap,
                                     TerminalTypeEnum terminalType) {
        MessageDetailRes res = new MessageDetailRes();
        res.setMsgTitle(response.getTitle());
        res.setContent(response.getContent());
        res.setModuleName(templateCodeNodeNameMap.get(response.getTemplateCode()));
        res.setTerminalId(response.getWorkspaceId());
        res.setTerminalName(response.getWorkspaceName());
        res.setTerminalType(MsgRecordTerminalTypeEnum.valueOf(response.getOrganizationType().name()));
        res.setState(MsgStateEnum.getByCode(response.getState().getDeprecatedCode()));
        res.setMsgTimestamp(DateFormatUtil.toTimestamp(response.getCreateTime()));
        res.setRouterParam(response.getRouterParam());
        buildRouter(res, response.getMsgTemplateRouter(), terminalType);
        return res;
    }

    private void buildRouter(MessageDetailRes detail, MessageTemplateRouterDTO router, TerminalTypeEnum terminalType) {
        if (Objects.isNull(router)) {
            return;
        }
        DetailRouterDTO detailRouter = MessageRouterUtil.fetchBizDetailRouter(router, terminalType).orElse(null);
        if (Objects.nonNull(detailRouter)) {
            NativeTypeEnum nativeType = detailRouter.getTerminalType() == null
                    ? null
                    : NativeTypeEnum.getByCode(detailRouter.getTerminalType().getCode());
            if (Objects.isNull(nativeType)) {
                return;
            }
            detail.setRouteUrl(detailRouter.getUrl());
            detail.setRouteType(nativeType.getMessage());
            return;
        }
        List<ButtonRouterDTO> buttonRouters = MessageRouterUtil.fetchMessageRouterButtons(router, terminalType);
        if (CollectionUtils.isEmpty(buttonRouters)) {
            return;
        }
        ButtonRouterDTO btnRouter = buttonRouters.stream()
                .filter(e -> Objects.equals(e.getCategory(), RouterCategoryEnum.JUMP))
                .findFirst().orElse(null);
        if (Objects.isNull(btnRouter)) {
            return;
        }
        NativeTypeEnum nativeType = NativeTypeEnum.getByCode(btnRouter.getTerminalType().getCode());
        if (Objects.isNull(nativeType)) {
            return;
        }
        detail.setRouteUrl(btnRouter.getUrl());
        detail.setRouteType(nativeType.getMessage());
    }

    private PendingMessageStatisticDTO statistic(GroupTreeNodePathDTO leafNodePath, MessageGroupNodeStatisticParam param) {
        PendingMessageStatisticDTO dto = PendingMessageStatisticDTO.of(leafNodePath);
        dto.setPendingCount(doStatistic(leafNodePath, param));
        return dto;
    }

    private int doStatistic(GroupTreeNodePathDTO leafNodePath, MessageGroupNodeStatisticParam param) {
        return doStatistic(Lists.newArrayList(leafNodePath), param);
    }

    private int doStatistic(Collection<GroupTreeNodePathDTO> groupNodePaths, MessageGroupNodeStatisticParam param) {
        List<String> groupNodePathCodes = groupNodePaths.stream()
                .map(GroupTreeNodePathDTO::getNodeCodePath)
                .collect(Collectors.toList());
        List<String> templateCodes = messageTemplateGroupService.listMessageTemplateCodes(groupNodePathCodes);
        if (CollectionUtils.isEmpty(templateCodes)) {
            return 0;
        }
        LambdaQueryChainWrapper<PendingMessageRecord> query = pendingMessageRecordDao.lambdaQuery()
                .in(PendingMessageRecord::getTemplateCode, templateCodes)
                .eq(PendingMessageRecord::getState, PendingMessageStateEnum.HAS_BEEN_SENT)
                .eq(PendingMessageRecord::getIsDelete, IsDeletedEnum.NO.getCode());
        appendExecutorOuIdExpr(query, param.getOuId());
        buildPersonCondition(query, param.getWithIdentify(), PendingMessageRoleCategoryEnum.EXECUTOR, param.getOperator());
        return pendingMessageRecordMapper.selecCustomSqlCount(query.getWrapper());
    }

    private PendingMessageStateEnum fetchPendingMessageState(PendingMessageRoleCategoryEnum roleCategory,
                                                             PendingMessageStateEnum pendingMessageState) {
        // 当且仅当查询的角色不为发起者时，待办状态参数才有意义
        return PendingMessageRoleCategoryEnum.PROMOTER.equals(roleCategory) ? null : pendingMessageState;
    }

    private BizFinalStateEnum fetchBizFinalState(PendingMessageRoleCategoryEnum roleCategory, BizFinalStateEnum bizFinalState) {
        // 当且仅当查询的角色为发起者时，业务终态参数才有意义
        return Optional.ofNullable(roleCategory)
                .filter(PendingMessageRoleCategoryEnum.PROMOTER::equals)
                .map(v -> bizFinalState)
                .orElse(null);
    }

    private void buildHideUntil(LambdaQueryChainWrapper<PendingMessageRecord> query) {
        query.and(q -> q.isNull(PendingMessageRecord::getHideUntil)
                .or()
                .lt(PendingMessageRecord::getHideUntil, new Date()));
    }

    private void buildPersonCondition(LambdaQueryChainWrapper<PendingMessageRecord> query, Boolean withIdentify,
                                      PendingMessageRoleCategoryEnum roleCategory, PersonDTO operator) {
        IdentityDTO identity = null;
        if (Objects.equals(withIdentify, true)) {
            // 代办需要对身份进行隔离
            identity = operator.getIdentity();
        }
        SFunction<PendingMessageRecord, Long> personIdFunc;
        SFunction<PendingMessageRecord, Long> identityIdFunc;
        SFunction<PendingMessageRecord, IdentityTypeEnum> identityTypeFunc;
        if (Objects.equals(PendingMessageRoleCategoryEnum.PROMOTER, roleCategory)) {
            // 代办发起者查询条件
            personIdFunc = PendingMessageRecord::getPromoterPersonId;
            identityIdFunc = PendingMessageRecord::getPromoterId;
            identityTypeFunc = PendingMessageRecord::getPromoterType;
            buildPromoterCondition(query, operator);
        } else {
            // 代办执行者查询条件
            personIdFunc = PendingMessageRecord::getExecutorPersonId;
            identityIdFunc = PendingMessageRecord::getExecutorId;
            identityTypeFunc = PendingMessageRecord::getExecutorType;
        }
        query.eq(personIdFunc, operator.getId());
        if (Objects.nonNull(identity) && identity.isValid()) {
            query.eq(identityIdFunc, identity.getId());
            query.eq(identityTypeFunc, identity.getType());
        }
    }

    private void buildPromoterCondition(LambdaQueryChainWrapper<PendingMessageRecord> query, PersonDTO operator) {
        Date queryFrom = DateFormatUtil.toDate(LocalDateTime.now().minusDays(90));
        List<PendingMessageRecord> records = pendingMessageRecordDao.lambdaQuery()
                .eq(PendingMessageRecord::getPromoterPersonId, operator.getId())
                .gt(PendingMessageRecord::getCreateAt, queryFrom)
                .select(PendingMessageRecord::getId, PendingMessageRecord::getRequestNo)
                .list();
        Map<String, List<PendingMessageRecord>> map = records.stream()
                .collect(Collectors.groupingBy(PendingMessageRecord::getRequestNo));
        List<Long> ids = map.values().stream()
                .map(e -> e.stream().mapToLong(PendingMessageRecord::getId).max().orElse(0))
                .collect(Collectors.toList());
        query.in(CollectionUtils.isNotEmpty(ids), PendingMessageRecord::getId, ids);
    }

    private void buildSortCondition(LambdaQueryChainWrapper<PendingMessageRecord> query,
                                    Collection<QueryOrderByDTO> orderFields, PendingMessageStateEnum state) {
        if (CollectionUtils.isEmpty(orderFields)) {
            // 默认时间降序
            if (PendingMessageStateEnum.COMPLETED.equals(state)) {
                query.orderByDesc(PendingMessageRecord::getUpdateAt);
            } else {
                query.orderByDesc(PendingMessageRecord::getCreateAt);
            }
            return;
        }
        orderFields.stream()
                .filter(e -> OrderFieldParseUtil.containsPendingMessageRecordOrderField(e.getOrderField()))
                .forEach(e -> {
                    if (e.getIsAsc()) {
                        query.orderByAsc(OrderFieldParseUtil.parsePendingMessageRecordOrderField(e.getOrderField()));
                    } else {
                        query.orderByDesc(OrderFieldParseUtil.parsePendingMessageRecordOrderField(e.getOrderField()));
                    }
                });
    }

    private List<Long> appendExecutorOuIdExpr(LambdaQueryChainWrapper<PendingMessageRecord> query, Long ouId) {
        List<Long> ouIds = determineOuIds(ouId);
        query.in(PendingMessageRecord::getOuId, ouIds);
        return ouIds;
    }

    @NotEmpty
    public List<Long> determineOuIds(Long ouId) {
        List<Long> ouIds = new ArrayList<>();
        if (ouId == null)
            return ouIds;
        ouIds.add(ouId);
        // 2. 查询ouId下面所有的平台班组id当成ouId
        if (ouId != 0) {
            ApiResult<List<OrganizationalTeamOuRelationResp>> resp =
                    organizationalTeamOuRelationApi.teamOuRelationList(ouId);
            List<OrganizationalTeamOuRelationResp> relations =
                    BizAssertions.assertResponse(resp, "获取平台班组id失败");
            relations.stream()
                    .map(OrganizationalTeamOuRelationResp::getTeamOuId)
                    .distinct()
                    .forEach(ouIds::add);
        }
        return ouIds;
    }

    private List<PendingMessageRecord> convert(PendingMessagePushParam param, MessageTemplateDTO msgTemplate,
                                               String requestNo, SimpleWorkspaceRes workspace) {
        // 多个执行者生成多条record
        return param.getExecutor().stream().map(executor -> {
            PendingMessageRecord record = new PendingMessageRecord();
            record.setOuId(param.getOuId());
            record.setIdentityCode(UUIDUtil.uuidString());
            record.setRequestNo(requestNo);
            record.setState(PendingMessageStateEnum.HAS_BEEN_SENT);
            // 构建代办记录的人维度的相关信息
            buildPersonInfo(record, param.getPromoter(), executor,
                    Optional.ofNullable(workspace).map(SimpleWorkspaceRes::getId).orElse(0L));
            // 构建模板信息
            buildTemplateInfo(record, msgTemplate, param.getBizExtParams());
            // 构建代办所属企业/项目等相关信息
            record.setOrgType(Objects.isNull(param.getOrgType()) ? OrganizationTypeEnum.UNKNOWN : param.getOrgType());
            record.setOrgId(param.getWorkspaceId());
            if (workspace != null) {
                record.setOrgName(workspace.getName());
            }
            // 构建业务类信息
            buildBusinessInfo(record, param);
            return record;
        }).collect(Collectors.toList());
    }

    private void buildPersonInfo(PendingMessageRecord record, PersonDTO promoter, PersonDTO executor, Long workspaceId) {
        IdentityDTO promoterIdentity = promoter.getIdentity();
        IdentityDTO executorIdentity = executor.getIdentity();
        record.setPromoterId(promoterIdentity == null || promoterIdentity.getId() == null ? 0 : promoterIdentity.getId());
        record.setPromoterType(promoterIdentity == null ? IdentityTypeEnum.NOT_SUPPORT : promoterIdentity.getType());
        record.setPromoterPersonId(promoter.getId());
        record.setPromoterName(promoter.getName());
        record.setExecutorId(executorIdentity == null || executorIdentity.getId() == null ? 0 : executorIdentity.getId());
        record.setExecutorType(executorIdentity == null ? IdentityTypeEnum.NOT_SUPPORT : executorIdentity.getType());
        record.setExecutorPersonId(executor.getId());
        record.setExecutorName(executor.getName());
        // 执行人身份是从业人员，查询ouId
        record.setOuId(getPractitionOuId(executor, workspaceId, record.getOuId()));
    }

    private Long getPractitionOuId(PersonDTO executor, Long workspaceId, Long ouId) {
        Supplier<Long> executorOuIdFun = () -> ouId == null ? 0L : ouId;
        // 从业人员单位处理
        if (executor.getIdentity() != null && IdentityTypeEnum.PRACTITIONER == executor.getIdentity().getType()) {
            if (null == ouId) {
                OrganizationalNodePractitionerWideReqParams param = new OrganizationalNodePractitionerWideReqParams();
                param.setWorkspaceId(workspaceId);
                param.setIdentityId(executor.getIdentity().getId());
                log.info("get ouId params->{}", JSON.toJSONString(param));
                Result<OrganizationalNodePractitionerWideDto> result = organizationalNodePractitionerWideApi.getOne(param);
                log.info("get ouId result->{}", JSON.toJSONString(result));
                if (null != result && null != result.getData() && null != result.getData().getOrganizationalUnitId()) {
                    return result.getData().getOrganizationalUnitId();
                }
            } else {
                // 业务传递了从业人员ouId
                return executorOuIdFun.get();
            }
        } else {
            // 非从业人员处理
            return executorOuIdFun.get();
        }
        return executorOuIdFun.get();
    }

    private void buildTemplateInfo(PendingMessageRecord record, MessageTemplateDTO msgTemplate, String bizParam) {
        JSONObject bizExtParam = JSONObjectUtil.parseObject(bizParam);
        String title = PlaceholderResolver
                .getDefaultResolver().resolveByMap(msgTemplate.getTitle(), bizExtParam);
        String content = PlaceholderResolver
                .getDefaultResolver().resolveByMap(msgTemplate.getContent(), bizExtParam);
        ;
        // 获取模板卡片信息
        List<MessageCardContentItemDTO> rawCardContentItems = msgTemplate.getMsgCardContentItems();
        List<MessageCardContentItemDTO> cardContentItems = rawCardContentItems;
        if (CollectionUtils.isNotEmpty(rawCardContentItems) && Objects.nonNull(record.getBizExtParam())
                && !record.getBizExtParam().isEmpty()) {
            // 克隆，避免修改入参
            cardContentItems = cardContentItems.stream()
                    .map(MessageCardContentItemDTO::deepClone)
                    .collect(Collectors.toList());
            cardContentItems.forEach(e -> {
                String value = PlaceholderResolver.getDefaultResolver()
                        .resolveByMap(e.getValue(), JSON.parseObject(record.getBizExtParam()));
                e.setValue(value);
            });
            record.setBizExtParam(JSON.toJSONString(cardContentItems));
        }
        record.setTitle(title);
        record.setContent(content);
        record.setTemplateCode(msgTemplate.getCode());
    }

    private void buildBusinessInfo(PendingMessageRecord record, PendingMessagePushParam param) {
        record.setBizCode(param.getBizCode());
        record.setSubBizCode(param.getSubBizCode());
        record.setBizDesc(param.getBizDesc());
        record.setBizCategory(param.getBizCategory());
        record.setBizExtParam(JSONObjectUtil.checkAndReturn(param.getBizExtParams()));
        record.setRouterParams(JSONObjectUtil.checkAndReturn(param.getRouterParams()));
        record.setBizFlag(param.getBizFlag());
        record.setBizFinalState(param.getBizFinalState());
        record.setDeadline(param.getDeadline());
    }

    private boolean filter(PendingMessageRecord pendingMessage, PendingMessageQueryRequest request) {
        if (Objects.nonNull(request.getBizFinalState())
                && !request.getBizFinalState().equals(pendingMessage.getBizFinalState())) {
            return false;
        }
        if (Objects.nonNull(request.getMsgState())
                && !request.getMsgState().equals(pendingMessage.getState())) {
            return false;
        }
        if (StringUtils.isNotBlank(request.getTitle())
                && !pendingMessage.getTitle().contains(request.getTitle())) {
            return false;
        }
        return Objects.isNull(request.getWorkspaceId())
                || Objects.equals(request.getWorkspaceId(), pendingMessage.getOrgId());
    }
}
