package cn.axzo.workflow.server.engine.ext.listener;

import cn.axzo.foundation.page.PageResp;
import cn.axzo.orggateway.api.cooperateship.CooperateShipHierarchicalQueryApi;
import cn.axzo.orggateway.api.cooperateship.req.ListCooperateShipAncestorReq;
import cn.axzo.orggateway.api.cooperateship.resp.ListCooperateShipResp;
import cn.axzo.orggateway.api.nodeuser.OrgNodeUserApi;
import cn.axzo.orggateway.api.nodeuser.dto.OrgNodeUserDTO;
import cn.axzo.orggateway.api.nodeuser.req.ListOrgNodeUserReq;
import cn.axzo.orgmanax.dto.common.WorkspaceOuPair;
import cn.axzo.orgmanax.dto.nodeuser.req.ListNodeUserReq;
import cn.axzo.workflow.common.enums.BpmnFlowNodeMode;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.enums.SignApproverOrgLimitEnum;
import cn.axzo.workflow.common.model.dto.JobInfo;
import cn.axzo.workflow.common.model.dto.NodeInfo;
import cn.axzo.workflow.common.model.dto.OrgSnapshotInfo;
import cn.axzo.workflow.common.model.dto.OrgStructureSnapshotInfo;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.engine.listener.entity.EntityEventHandle;
import cn.axzo.workflow.core.repository.entity.ExtAxProcessLog;
import cn.axzo.workflow.core.service.ExtAxProcessLogService;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.bpmn.behavior.MultiInstanceActivityBehavior;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_ADVICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.COMMENT_TYPE_OPERATION_DESC;
import static cn.axzo.workflow.common.constant.BpmnConstants.HIDDEN_ASSIGNEE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_INFO;
import static cn.axzo.workflow.common.constant.BpmnConstants.NO_ASSIGNEE;
import static cn.axzo.workflow.common.constant.BpmnConstants.SUPPORT_UPGRADE_VARIABLE;
import static cn.axzo.workflow.common.constant.BpmnConstants.TASK_COMPLETE_OPERATION_TYPE;
import static cn.axzo.workflow.common.enums.ApprovalMethodEnum.nobody;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.AND;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.GENERAL;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.OR;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
import static cn.axzo.workflow.core.common.enums.BpmnProcessTaskResultEnum.HANDLING;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getActivitySignature;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprovalMethod;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNodeType;

/**
 * 同意、评论、加签、转交、驳回、撤回、中止、抄送
 * <p>
 * 回退
 *
 * @author wangli
 * @since 2024-09-06 00:02
 */
@Slf4j
@Component
@AllArgsConstructor
public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
    private final ExtAxProcessLogService processLogService;
    private final OrgNodeUserApi orgNodeUserApi;
    private final CooperateShipHierarchicalQueryApi cooperateShipHierarchicalQueryApi;

    @Override
    public boolean support(Object entity) {
        return entity instanceof TaskEntity;
    }

    @Override
    public TaskEntity convert(Object entity) {
        return (TaskEntity) entity;
    }

    @Override
    public void onCreate(TaskEntity taskEntity) {
        // 记录发起人
        boolean isNodeStarter = Objects.equals(taskEntity.getTaskDefinitionKey(), NODE_STARTER.getType());

        BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(taskEntity.getProcessDefinitionId());
        FlowElement flowElement = bpmnModel.getFlowElement(taskEntity.getTaskDefinitionKey());

        ExtAxProcessLog log = new ExtAxProcessLog();
        log.setProcessInstanceId(taskEntity.getProcessInstanceId());
        log.setTenantId(taskEntity.getTenantId());
        log.setActivityId(taskEntity.getTaskDefinitionKey());
        log.setActivityName(taskEntity.getName());
        log.setApprovalMethod((isNodeStarter ? nobody : getApprovalMethod(flowElement).orElse(nobody)).getType());
        log.setNodeType((getNodeType(flowElement).orElse(BpmnFlowNodeType.NODE_EMPTY)).getType());
        log.setNodeMode((isNodeStarter ? BpmnFlowNodeMode.GENERAL : getNodeMode(flowElement)).getType());
        log.setTaskId(taskEntity.getId());
        String operationDesc = taskEntity.getVariable(COMMENT_TYPE_OPERATION_DESC, String.class);
        log.setOperationDesc(StringUtils.hasText(operationDesc) ? operationDesc : HANDLING.getDesc());
        log.setStartTime(taskEntity.getCreateTime());
        log.setStatus(PROCESSING.getStatus());
        log.setSignature(getActivitySignature(flowElement));

        processLogService.insert(log);
    }

    @Override
    public void onInitialized(TaskEntity taskEntity) {
        Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId());
        ExtAxProcessLog queryLog = new ExtAxProcessLog();
        queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
        queryLog.setTaskId(taskEntity.getId());
        ExtAxProcessLog updateLog = new ExtAxProcessLog();
        BpmnMetaParserHelper.getButtonConfig(process, taskEntity.getTaskDefinitionKey())
                .ifPresent(updateLog::setButtonConf);
        BpmnMetaParserHelper.getFormFieldPermissionConf(process.getFlowElement(taskEntity.getTaskDefinitionKey()))
                .ifPresent(updateLog::setFormFieldPermissionConf);
        processLogService.update(queryLog, updateLog);
    }

    @Override
    public void onUpdated(TaskEntity taskEntity) {
        if (Objects.equals(HIDDEN_ASSIGNEE_ID, taskEntity.getAssignee())) {
            ExtAxProcessLog queryLog = new ExtAxProcessLog();
            queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
            queryLog.setTaskId(taskEntity.getId());
            processLogService.delete(queryLog);
        } else {
            ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
            RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
            @SuppressWarnings("unchecked")
            List<BpmnTaskDelegateAssigner> assigneeList = runtimeService.getVariable(taskEntity.getProcessInstanceId(),
                    INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(), List.class);
            ListUtils.emptyIfNull(assigneeList).stream().filter(e -> Objects.equals(e.buildAssigneeId(), taskEntity.getAssignee())).findAny()
                    .ifPresent(assignee -> {
                        ExtAxProcessLog queryLog = new ExtAxProcessLog();
                        queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
                        queryLog.setTaskId(taskEntity.getId());

                        // 快照审批人的组织架构信息
                        OrgStructureSnapshotInfo snapshotInfo = buildApproverOrgStructureInfo(assignee, taskEntity);

                        processLogService.updateAssigneeAndSnapshot(queryLog, assignee, snapshotInfo);
                    });
        }
    }

    private OrgStructureSnapshotInfo buildApproverOrgStructureInfo(BpmnTaskDelegateAssigner assignee, TaskEntity taskEntity) {
        // 先将审批人的提级审批重置为 false
        assignee.setSupportUpgradeApproval(false);
        log.info("build approve org info -> approve : {}, activity: {}", JSON.toJSONString(assignee), taskEntity.getTaskDefinitionKey());
        List<OrgNodeUserDTO> nodeUsers = fetchUserExtInfo(assignee);
        OrgStructureSnapshotInfo.OrgStructureSnapshotInfoBuilder builder = OrgStructureSnapshotInfo.builder();
        if (CollectionUtils.isEmpty(nodeUsers)) {
            log.info("fetch user ext info is empty");
            return builder.build();
        }
        OrgNodeUserDTO firstNodeUser = nodeUsers.get(0);
        assignee.setNodeId(String.valueOf(firstNodeUser.getTopNodeId()));

        OrgStructureSnapshotInfo snapshotInfo = builder.personName(firstNodeUser.getRealName())
                .avatarUrl(Objects.nonNull(firstNodeUser.getPersonProfile()) ? firstNodeUser.getPersonProfile().getAvatarUrl() : "")
                .phone(firstNodeUser.getPhone())
                .topNodeId(firstNodeUser.getTopNodeId())
                .workspaceType(Objects.nonNull(firstNodeUser.getWorkspace()) ? firstNodeUser.getWorkspace().getType() : null)
                .snapshotInfo(Objects.nonNull(firstNodeUser.getCooperateShip()) ? OrgSnapshotInfo.builder()
                        .workspaceName(firstNodeUser.getCooperateShip().getWorkspaceName())
                        .workspaceId(String.valueOf(firstNodeUser.getCooperateShip().getWorkspaceId()))
                        .workspaceType(firstNodeUser.getCooperateShip().getWorkspaceType())
                        .cooperationType(firstNodeUser.getCooperateShip().getCooperateType())
                        .ouName(firstNodeUser.getCooperateShip().getOrganizationalUnitName())
                        .ouId(String.valueOf(firstNodeUser.getCooperateShip().getOrganizationalUnitId()))
                        .topNodeId(String.valueOf(firstNodeUser.getTopNodeId()))
                        .nodeInfos(ListUtils.emptyIfNull(nodeUsers).stream()
                                .filter(i -> Objects.nonNull(i.getNode()))
                                .map(i -> NodeInfo.builder()
                                        .nodeId(i.getNode().getId())
                                        .nodeName(i.getNode().getNodeName())
                                        .build())
                                .collect(Collectors.toList())
                        )
                        .jobInfos(ListUtils.emptyIfNull(nodeUsers).stream()
                                .filter(i -> Objects.nonNull(i.getJob()))
                                .map(i -> JobInfo.builder()
                                        .jobCode(i.getJob().getCode())
                                        .jobName(i.getJob().getName())
                                        .build())
                                .collect(Collectors.toList()))
                        .build() : OrgSnapshotInfo.builder().jobInfos(Collections.emptyList()).build())
                .build();

        FlowElement flowElement = ProcessDefinitionUtil.getBpmnModel(taskEntity.getProcessDefinitionId()).getFlowElement(taskEntity.getTaskDefinitionKey());
        BpmnMetaParserHelper.getUpgradeApprovalConf(flowElement).ifPresent(conf -> {
            Boolean supportUpgradeApproval;
            if (Objects.equals(Boolean.TRUE, conf.getEnabled())) {
                if (Objects.equals(SignApproverOrgLimitEnum.LV_ALL, conf.getOrgLimit())) {
                    supportUpgradeApproval = true;
                } else {
                    ListCooperateShipAncestorReq build = ListCooperateShipAncestorReq.builder()
                            .upLevel(conf.getOrgLimit().getCode())
                            .organizationNodeId(Long.parseLong(assignee.getNodeId()))
                            .includeCurrentNode(false)
                            .build();
                    List<ListCooperateShipResp> result = listAncestors(build);
                    supportUpgradeApproval = !CollectionUtils.isEmpty(result);
                }
                assignee.setSupportUpgradeApproval(supportUpgradeApproval);
                taskEntity.setTransientVariableLocal(SUPPORT_UPGRADE_VARIABLE, supportUpgradeApproval);
            }
        });
        return snapshotInfo;
    }

    @Override
    public void onDeleted(TaskEntity taskEntity) {
        ExtAxProcessLog queryLog = new ExtAxProcessLog();
        queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
        queryLog.setTaskId(taskEntity.getId());
        queryLog.setOperationDesc(HANDLING.getDesc());
        ExtAxProcessLog update = new ExtAxProcessLog();

        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
        RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
        BpmnTaskDelegateAssigner assignee = BpmnTaskDelegateAssigner.toObjectCompatible(taskEntity.getVariable(INTERNAL_TASK_RELATION_ASSIGNEE_INFO + taskEntity.getId()));
        if (Objects.nonNull(assignee) && !Objects.equals(NO_ASSIGNEE, assignee.buildAssigneeId())) {
            update.setAssigneeFull(Lists.newArrayList(assignee));
            update.setAssigneeId(NumberUtils.isDigits(assignee.getPersonId()) ? Long.valueOf(assignee.getPersonId()) : null);
            update.setAssigneeTenantId(assignee.getTenantId());
            update.setAssigneeName(assignee.getAssignerName());
            update.setAssigneeOuId(assignee.getOuId());
            update.setOperationDesc(assignee.getAssignerName());
        }

        boolean needDelete = false;
        if (Objects.equals(taskEntity.getTaskDefinitionKey(), NODE_STARTER.getType())) {
            update.setStatus(APPROVED.getStatus());
        } else {

            Object advice = taskEntity.getTransientVariableLocal(COMMENT_TYPE_ADVICE);
            if (Objects.nonNull(advice) && StringUtils.hasText(advice.toString())) {
                update.setAdvice(advice.toString());
            }
            Object operationDesc = taskEntity.getTransientVariableLocal(COMMENT_TYPE_OPERATION_DESC);
            if (Objects.nonNull(operationDesc) && StringUtils.hasText(operationDesc.toString())) {
                update.setOperationDesc(Objects.nonNull(assignee) ?
                        (StringUtils.hasText(assignee.getAssignerName()) ? assignee.getAssignerName() : "") + operationDesc
                        : operationDesc.toString());
            } else {
                update.setOperationDesc(Objects.nonNull(assignee) ?
                        (StringUtils.hasText(assignee.getAssignerName()) ? assignee.getAssignerName() : "")
                        : "");
            }


            String completionType = taskEntity.getVariable(TASK_COMPLETE_OPERATION_TYPE + taskEntity.getId(), String.class);
            if (StringUtils.hasText(completionType) && !Objects.equals(DELETED.getStatus(), completionType)) {
                update.setStatus(completionType);
            } else {
                // 多实例除操作人以外的任务，直接删除日志， 例如一个节点有两个人或签，A 人驳回了，那么 B 人不再需要操作，任务自动删除。而会签也同理
                update.setStatus(DELETED.getStatus());// delete标志着是多实例删除
                needDelete = true;
            }
        }
        update.setEndTime(new Date());

        // 判断是否抄送节点，如果是的话，需要将抄送人集合放入对应字段
        if (isCarbonCopyNode(queryLog)) {
            // 抄送人集合
            @SuppressWarnings("unchecked")
            List<BpmnTaskDelegateAssigner> carbonCopies = runtimeService.getVariable(taskEntity.getProcessInstanceId(), INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + taskEntity.getTaskDefinitionKey(), List.class);
            update.setAssigneeFull(carbonCopies);
            update.setOperationDesc("抄送" + carbonCopies.size() + "人");
        }

        processLogService.update(queryLog, update);

        if (needDelete) {
            // 再逻辑删除该记录
            ExtAxProcessLog deleteLog = new ExtAxProcessLog();
            deleteLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
            deleteLog.setTaskId(taskEntity.getId());
            processLogService.delete(deleteLog);
        }
    }

    private boolean isCarbonCopyNode(ExtAxProcessLog queryLog) {
        List<ExtAxProcessLog> logs = processLogService.genericQuery(queryLog);
        if (CollectionUtils.isEmpty(logs) || logs.size() != 1) {
            return false;
        }
        return Objects.equals(logs.get(0).getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType());
    }

    private BpmnFlowNodeMode getNodeMode(FlowElement flowElement) {
        BpmnFlowNodeMode node = GENERAL;
        if (flowElement instanceof UserTask) {
            UserTask userTask = (UserTask) flowElement;
            if (userTask.getBehavior() instanceof MultiInstanceActivityBehavior) {
                MultiInstanceActivityBehavior behavior =
                        (MultiInstanceActivityBehavior) userTask.getBehavior();
                node = Objects.equals(AND_SIGN_EXPRESSION, behavior.getCompletionCondition()) ? AND : OR;
            }
        }
        return node;
    }

    private List<OrgNodeUserDTO> fetchUserExtInfo(BpmnTaskDelegateAssigner assigner) {
        String personId = assigner.getPersonId();
        String tenantId = assigner.getTenantId();
        String ouId = assigner.getOuId();
        String nodeId = assigner.getNodeId();
        if (NumberUtils.isDigits(personId) && NumberUtils.isDigits(tenantId) && NumberUtils.isDigits(ouId)) {
            ListOrgNodeUserReq build = ListOrgNodeUserReq.builder()
                    .personIds(Lists.newArrayList(Long.parseLong(personId)))
                    .workspaceOuPairs(Lists.newArrayList(WorkspaceOuPair.builder()
                            .workspaceId(Long.parseLong(tenantId))
                            .ouId(Long.parseLong(ouId))
                            .build()))
                    .needs(ListNodeUserReq.Needs.builder()
                            .job(true)
                            .unit(true)
                            .node(true)
                            .cooperateShip(true)
                            .personProfile(true)
                            .workspace(true)
                            .build())
                    .pageSize(Integer.MAX_VALUE)
                    .build();

            if (NumberUtils.isDigits(nodeId)) {
                build.setAncestorNodeIds(Lists.newArrayList(Long.parseLong(nodeId)));
            }
            return orgNodeUserList(build).getData();
        }
        return Collections.emptyList();

    }

    /**
     * 查询orgNodeUser
     */
    public PageResp<OrgNodeUserDTO> orgNodeUserList(ListOrgNodeUserReq listOrgNodeUserReq) {
        try {
            log.info("orgNodeUserList,params:{}", JSON.toJSONString(listOrgNodeUserReq));
            cn.axzo.foundation.result.ApiResult<PageResp<OrgNodeUserDTO>> apiResult = orgNodeUserApi.list(listOrgNodeUserReq);
            log.info("orgNodeUserList,result:{}", JSON.toJSONString(apiResult));
            if (Objects.isNull(apiResult) || !apiResult.isSuccess() || Objects.isNull(apiResult.getData()) || org.apache.commons.collections.CollectionUtils.isEmpty(apiResult.getData().getData())) {
                return new PageResp<>(0, listOrgNodeUserReq.getPageSize(), listOrgNodeUserReq.getPage(), org.apache.commons.compress.utils.Lists.newArrayList());
            }
            return apiResult.getData();
        } catch (Exception e) {
            log.warn("orgNodeUserList,exception", e);
            throw e;
        }
    }

    public List<ListCooperateShipResp> listAncestors(ListCooperateShipAncestorReq req) {
        try {
            log.info("listAncestors,params:{}", JSON.toJSONString(req));
            cn.axzo.foundation.result.ApiResult<List<ListCooperateShipResp>> listApiResult = cooperateShipHierarchicalQueryApi.listAncestors(req);
            log.info("listAncestors,result:{}", JSON.toJSONString(listApiResult));
            if (Objects.isNull(listApiResult) || !listApiResult.isSuccess() || Objects.isNull(listApiResult.getData()) || org.apache.commons.collections.CollectionUtils.isEmpty(listApiResult.getData())) {
                return org.apache.commons.compress.utils.Lists.newArrayList();
            }
            return listApiResult.getData();
        } catch (Exception e) {
            log.warn("orgNodeUserList,exception", e);
            throw e;
        }
    }
}

