/*
 * Copyright (c) 2023 codvision.com All Rights Reserved.
 */

package com.codvision.workflow.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.codvision.auth.entity.Org;
import com.codvision.auth.entity.vo.SysUserVO;
import com.codvision.auth.service.IOrgService;
import com.codvision.auth.service.ISysUserService;
import com.codvision.auth.support.UserContext;
import com.codvision.commoncore.utils.Preconditions;
import com.codvision.webcore.service.impl.BaseServiceImpl;
import com.codvision.workflow.constant.enums.WorkFlowOperationTypeEnum;
import com.codvision.workflow.entity.WorkFlowRecord;
import com.codvision.workflow.entity.dto.WorkFlowRecordOperatorDTO;
import com.codvision.workflow.entity.vo.WorkFlowRecordVO;
import com.codvision.workflow.mapper.WorkFlowRecordMapper;
import com.codvision.workflow.service.WorkFlowRecordService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.engine.HistoryService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.identitylink.api.IdentityLinkInfo;
import org.flowable.identitylink.api.IdentityLinkType;
import org.flowable.identitylink.api.history.HistoricIdentityLink;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.stereotype.Service;

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

import static com.codvision.workflow.constant.WorkFlowConstant.WORK_FLOW_TIME_OUT_CLAIM_TASK_ID;
import static com.codvision.workflow.constant.WorkFlowConstant.WORK_FLOW_TIME_OUT_COMPLETE_TASK_ID;
import static com.codvision.workflow.constant.enums.WorkFlowOperationTypeEnum.CLAIM;
import static com.codvision.workflow.constant.enums.WorkFlowOperationTypeEnum.REVOKE_REDO;

/**
 * @author hxl
 */
@Service
@RequiredArgsConstructor
public class WorkFlowRecordServiceImpl extends BaseServiceImpl<WorkFlowRecordMapper, WorkFlowRecord, WorkFlowRecordVO> implements WorkFlowRecordService {

    private final HistoryService historyService;
    private final TaskService taskService;
    private final ISysUserService sysUserService;
    private final IOrgService orgService;

    @Override
    public WorkFlowRecord getByTodoTaskId(String taskId) {
        return getBaseMapper().getByTodoTaskId(taskId);
    }

    @Override
    public WorkFlowRecord getLatestByInstanceId(String instanceId) {
        return this.getOne(Wrappers.<WorkFlowRecord>lambdaQuery()
                .eq(WorkFlowRecord::getInstanceId, instanceId).orderByDesc(WorkFlowRecord::getId));
    }

    @Override
    public WorkFlowRecord getLatestByInstanceIdAndActivityId(String instanceId, String activityId) {
        return this.getOne(Wrappers.<WorkFlowRecord>lambdaQuery()
                .eq(WorkFlowRecord::getInstanceId, instanceId).eq(WorkFlowRecord::getActivityId, activityId)
                .orderByDesc(WorkFlowRecord::getId));
    }

    @Override
    public WorkFlowRecord getByTaskId(String taskId) {
        return this.getOne(Wrappers.<WorkFlowRecord>lambdaQuery().eq(WorkFlowRecord::getTaskId, taskId).orderByDesc(WorkFlowRecord::getId));
    }

    @Override
    public WorkFlowRecord getByTaskIdAndAction(String taskId, Integer action) {
        return this.getOne(Wrappers.<WorkFlowRecord>lambdaQuery().eq(WorkFlowRecord::getTaskId, taskId).eq(WorkFlowRecord::getAction, action));
    }

    @Override
    public void delAllByInstanceId(String instanceId) {
        this.remove(Wrappers.<WorkFlowRecord>lambdaQuery().eq(WorkFlowRecord::getInstanceId, instanceId));
    }

    @Override
    public void delByTaskId(String taskId) {
        this.remove(Wrappers.<WorkFlowRecord>lambdaQuery().eq(WorkFlowRecord::getTaskId, taskId));
    }

    @Override
    public void addCustomRecord(String taskId, Integer operationType) {
        Preconditions.checkArgument(!(StringUtils.isBlank(taskId) || operationType == null), 30002);
        String currentTime = DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN);
        // 更新当前任务操作记录信息
        WorkFlowRecord workFlowRecord = updateCurrentTaskRecord(taskId, operationType, null, currentTime);
        // 添加下一步处理记录
        this.save(buildRecordObject(workFlowRecord, null, currentTime, null, null));
    }
    @Override
    public List<WorkFlowRecordVO> getListByInstanceId(String instanceId) {
        Preconditions.checkArgument(StringUtils.isNotBlank(instanceId), 30002);
        Map<String, Object> paraMap = new HashMap<>(2);
        paraMap.put("instanceId", instanceId);
        List<WorkFlowRecordVO> records = getBaseMapper().findAll(paraMap);
        if (CollectionUtils.isNotEmpty(records)) {
            for (WorkFlowRecordVO record : records) {
                if (!(BpmnXMLConstants.ELEMENT_TASK_USER.equals(record.getActivityType())
                        || BpmnXMLConstants.ELEMENT_EVENT_START.equals(record.getActivityType()))) {
                    continue;
                }

                // 计算耗时
                long receiveTime = DateUtil.parse(record.getReceiveTime(), DatePattern.NORM_DATETIME_PATTERN).getTime();
                long completeTime = StringUtils.isBlank(record.getCompleteTime()) ? System.currentTimeMillis() : DateUtil.parse(record.getCompleteTime(), DatePattern.NORM_DATETIME_PATTERN).getTime();
                record.setDurationTime(getDate(completeTime - receiveTime));

                // 判断是否存在办结人
                if (record.getOperatorUid() != null) {
                    WorkFlowRecordOperatorDTO recordOperator = WorkFlowRecordOperatorDTO.builder().operatorName(record.getOperatorName()).operatorTel(record.getOperatorTel()).operatorOrgName(record.getOperatorOrgName()).build();
                    record.setOperatorList(Collections.singletonList(recordOperator));
                    continue;
                }

                List<HistoricIdentityLink> identityLinks = historyService.getHistoricIdentityLinksForTask(record.getTaskId());
                if (CollectionUtils.isEmpty(identityLinks)) {
                    continue;
                }

                // 获取候选信息
                identityLinks = identityLinks.stream().filter(identityLink -> IdentityLinkType.CANDIDATE.equals(identityLink.getType())).collect(Collectors.toList());
                if (CollectionUtils.isEmpty(identityLinks)) {
                    continue;
                }

                // 判断是否存在候选组
                Optional<String> groupIdOptional = identityLinks.stream().map(IdentityLinkInfo::getGroupId).filter(StringUtils::isNotBlank).findFirst();
                if (groupIdOptional.isPresent()) {
                    Org org = orgService.getById(Integer.valueOf(groupIdOptional.get()));
                    if (org != null) {
                        record.setOperatorOrgName(org.getName());
                        continue;
                    }
                }

                // 判断是否存在候选人
                List<WorkFlowRecordOperatorDTO> operatorList = new ArrayList<>();
                List<String> userIdList = identityLinks.stream().map(IdentityLinkInfo::getUserId).filter(StringUtils::isNotBlank).collect(Collectors.toList());
                for (String uid : userIdList) {
                    SysUserVO sysUserVO = sysUserService.getDetail(Integer.valueOf(uid));
                    if (sysUserVO == null || StringUtils.isBlank(sysUserVO.getName())) {
                        continue;
                    }

                    WorkFlowRecordOperatorDTO recordOperator = WorkFlowRecordOperatorDTO.builder()
                            .operatorName(sysUserVO.getName()).operatorTel(sysUserVO.getMobile()).operatorOrgName(sysUserVO.getActiveOrgName()).build();
                    operatorList.add(recordOperator);
                }

                record.setOperatorList(operatorList);
            }
        }
        return records;
    }

    @Override
    public void initStartRecords(String instanceId) {
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instanceId)
                .activityTypes(CollUtil.newHashSet(BpmnXMLConstants.ELEMENT_EVENT_START, BpmnXMLConstants.ELEMENT_TASK_USER))
                .list();

        batchInsert(instanceId, historicActivityInstances);
    }
    private void batchInsert(String instanceId, List<HistoricActivityInstance> historicActivityInstances) {
        if (CollectionUtils.isEmpty(historicActivityInstances)) {
            return;
        }

        List<WorkFlowRecord> workFlowRecords = new ArrayList<>();
        for (HistoricActivityInstance hisActIns : historicActivityInstances) {
            workFlowRecords.add(buildRecord(instanceId, hisActIns));
        }

        getBaseMapper().insertBatch(workFlowRecords);
    }

    @Override
    public void addTaskRecord(String taskId, Integer action, String comment, String formDataJson) {
        if (REVOKE_REDO.getType() == action) {
            // 添加撤回重办记录
            addRevokeRedoRecord(taskId, comment);
            return;
        }

        if (CLAIM.getType() == action) {
            // 添加签收记录
            addClaimRecord(taskId, comment);
            return;
        }

        WorkFlowRecord workFlowRecord = getByTodoTaskId(taskId);
        if (workFlowRecord != null) {
            // 添加当前操作人信息
            initCurrentUser(workFlowRecord);
            // 添加当前处理动作
            workFlowRecord.setAction(action);
            // 添加审核意见
            workFlowRecord.setComment(comment);

            if (StringUtils.isNotBlank(formDataJson)) {
                // 添加操作数据
                workFlowRecord.setFormDataJson(formDataJson);
            }
            // 添加完成时间
            HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery().taskId(taskId).singleResult();
            if (historicTaskInstance.getEndTime() != null) {
                workFlowRecord.setCompleteTime(DateUtil.format(historicTaskInstance.getEndTime(), DatePattern.NORM_DATETIME_PATTERN));
            } else {
                workFlowRecord.setCompleteTime(DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN));
            }

            this.updateById(workFlowRecord);
            // 初始化下一步处理记录
            initNextRecords(historicTaskInstance.getProcessInstanceId());
        }
    }

    @Override
    public void addReturnRecord(String processInstanceId, String targetActivityId, String currentTime) {
        WorkFlowRecord targetWorkFlowRecord = getLatestByInstanceIdAndActivityId(processInstanceId, targetActivityId);
        Preconditions.checkArgument(targetWorkFlowRecord != null, "获取目标任务节点信息失败");

        Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).taskDefinitionKey(targetActivityId).singleResult();
        Preconditions.checkArgument(task != null, "获取目标任务节点信息失败");
        // 添加下一步处理记录
        targetWorkFlowRecord.setTaskId(task.getId());
        targetWorkFlowRecord.setExecutionId(task.getExecutionId());
        this.save(buildRecordObject(targetWorkFlowRecord, null, currentTime, null, null));
    }

    @Override
    public void delByInstanceIdAndActivityIdAndTaskIds(String instanceId, String activityId, List<String> taskIds) {
        getBaseMapper().delByInstanceIdAndActivityIdAndTaskIds(instanceId, activityId, taskIds);
    }

    @Override
    public WorkFlowRecord updateCurrentTaskRecord(String taskId, Integer action, String comment, String currentTime) {
        WorkFlowRecord currentWorkFlowRecord = getByTodoTaskId(taskId);
        Preconditions.checkArgument(currentWorkFlowRecord != null,"获取当前任务操作记录信息失败");

        // 添加当前操作人信息
        initCurrentUser(currentWorkFlowRecord);
        // 添加当前处理动作
        currentWorkFlowRecord.setAction(action);
        // 添加审核意见
        currentWorkFlowRecord.setComment(comment);
        // 添加完成时间
        currentWorkFlowRecord.setCompleteTime(currentTime);
        // 更新当前操作信息
        this.updateById(currentWorkFlowRecord);
        return currentWorkFlowRecord;
    }

    private void addClaimRecord(String taskId, String comment) {
        // 更新当前任务操作记录信息
        String claimDeadlineTime = (String) taskService.getVariableLocal(taskId, WORK_FLOW_TIME_OUT_CLAIM_TASK_ID + taskId);
        if (StringUtils.isBlank(claimDeadlineTime)) {
            WorkFlowRecord workFlowRecord = getByTodoTaskId(taskId);
            if (workFlowRecord != null) {
                // 若未配置签收超时，则清除超时数据
                workFlowRecord.setIsExpired(null);
                workFlowRecord.setDeadlineTime(null);
                this.updateById(workFlowRecord);
            }
        }
        WorkFlowRecord workFlowRecord = updateCurrentTaskRecord(taskId, CLAIM.getType(), comment, DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN));
        // 添加下一步处理记录
        WorkFlowRecord nextRecord = buildRecordObject(workFlowRecord, null, workFlowRecord.getReceiveTime(), null, null);
        String completeDeadlineTime = (String) taskService.getVariableLocal(taskId, WORK_FLOW_TIME_OUT_COMPLETE_TASK_ID + taskId);
        if (StringUtils.isNotBlank(completeDeadlineTime)) {
            nextRecord.setIsExpired(DateUtil.compare(new Date(), DateUtil.parse(completeDeadlineTime, DatePattern.NORM_DATETIME_PATTERN)) > 0);
            nextRecord.setDeadlineTime(completeDeadlineTime);
        }
        this.save(nextRecord);
    }

    private void addRevokeRedoRecord(String taskId, String comment) {
        WorkFlowRecord taskRecord = getByTaskId(taskId);
        Preconditions.checkArgument(taskRecord != null,"获取任务操作记录信息失败");

        String currentTime = DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN);
        // 添加撤回重办记录
        this.save(buildRecordObject(taskRecord, REVOKE_REDO.getType(), currentTime, currentTime, comment));
        // 添加下一步处理记录
        Task task = taskService.createTaskQuery().processInstanceId(taskRecord.getInstanceId()).taskDefinitionKey(taskRecord.getActivityId()).singleResult();
        Preconditions.checkArgument(task != null, "获取目标任务节点信息失败");
        taskRecord.setTaskId(task.getId());
        taskRecord.setTaskName(task.getName());
        this.save(buildRecordObject(taskRecord, null, currentTime, null, null));
    }

    private void initNextRecords(String instanceId) {
        // 获取下一步代办的用户节点信息
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instanceId).unfinished().activityType(BpmnXMLConstants.ELEMENT_TASK_USER).list();

        // 判断流程是否已执行完毕，若结束事件节点存在，则添加
        if (CollectionUtils.isEmpty(historicActivityInstances)) {
            List<HistoricActivityInstance> hisActInsEnds = historyService.createHistoricActivityInstanceQuery()
                    .processInstanceId(instanceId).activityType(BpmnXMLConstants.ELEMENT_EVENT_END).list();
            batchInsert(instanceId, hisActInsEnds);
        }

        // 预处理下一步代办的用户节点的操作记录
        for (HistoricActivityInstance hisActIns : historicActivityInstances) {
            WorkFlowRecord workFlowRecord = getBaseMapper().getByTodoTaskId(hisActIns.getTaskId());
            if (workFlowRecord == null) {
                this.save(buildRecord(instanceId, hisActIns));
            }
        }
    }

    private WorkFlowRecord buildRecord(String instanceId, HistoricActivityInstance hisActIns) {
        HistoricProcessInstance hisIns = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(instanceId)
                .singleResult();

        WorkFlowRecord record = WorkFlowRecord.builder().deployId(hisIns.getDeploymentId()).instanceId(instanceId)
                .executionId(hisActIns.getExecutionId()).taskId(hisActIns.getTaskId())
                .taskName(hisActIns.getActivityName()).activityId(hisActIns.getActivityId()).activityType(hisActIns.getActivityType())
                .receiveTime(DateUtil.format(hisActIns.getStartTime(), DatePattern.NORM_DATETIME_PATTERN))
                .build();

        if (hisActIns.getEndTime() != null) {
            record.setCompleteTime(DateUtil.format(hisActIns.getEndTime(), DatePattern.NORM_DATETIME_PATTERN));
        }

        String userId = null;
        if (BpmnXMLConstants.ELEMENT_EVENT_START.equals(hisActIns.getActivityType())) {
            userId = hisIns.getStartUserId();
            record.setAction(WorkFlowOperationTypeEnum.INITIATED.getType());
        } else if (BpmnXMLConstants.ELEMENT_TASK_USER.equals(hisActIns.getActivityType())) {
            userId = hisActIns.getAssignee();
            initTaskDeadlineTime(record);
        } else if (BpmnXMLConstants.ELEMENT_EVENT_END.equals(hisActIns.getActivityType())) {
            record.setAction(WorkFlowOperationTypeEnum.ENDED.getType());
        }

        if (StringUtils.isNotBlank(userId)) {
            SysUserVO sysUserVO = sysUserService.getDetail(Integer.valueOf(userId));
            if (sysUserVO != null) {
                record.setOperatorUid(sysUserVO.getId());
                record.setOperatorName(sysUserVO.getName());
                record.setOperatorOid(sysUserVO.getActiveOid());
                record.setOperatorOrgName(sysUserVO.getActiveOrgName());
            }
        }
        return record;
    }

    private void initCurrentUser(WorkFlowRecord workFlowRecord) {
        if (workFlowRecord == null) {
            return;
        }

        // 初始化当前操作人信息
        SysUserVO sysUserVO = UserContext.get();
        workFlowRecord.setOperatorUid(sysUserVO.getId());
        workFlowRecord.setOperatorName(sysUserVO.getName());
        workFlowRecord.setOperatorOid(sysUserVO.getActiveOid());
        workFlowRecord.setOperatorOrgName(sysUserVO.getActiveOrgName());
    }

    private WorkFlowRecord buildRecordObject(WorkFlowRecord record, Integer action, String receiveTime, String completeTime, String comment) {
        return WorkFlowRecord.builder()
                .deployId(record.getDeployId()).instanceId(record.getInstanceId()).executionId(record.getExecutionId())
                .taskId(record.getTaskId()).taskName(record.getTaskName())
                .operatorUid(record.getOperatorUid()).operatorName(record.getOperatorName())
                .operatorOid(record.getOperatorOid()).operatorOrgName(record.getOperatorOrgName())
                .receiveTime(receiveTime).completeTime(completeTime)
                .activityId(record.getActivityId()).activityType(record.getActivityType())
                .comment(comment).action(action)
                .build();
    }

    private void initTaskDeadlineTime(WorkFlowRecord workFlowRecord) {
        Task task = taskService.createTaskQuery().taskId(workFlowRecord.getTaskId()).singleResult();
        if (task == null) {
            return;
        }

        if (StringUtils.isBlank(task.getAssignee())) {
            // 判断是否配置了签收超时
            boolean hasClaim = initTaskDeadlineTime(workFlowRecord, WORK_FLOW_TIME_OUT_CLAIM_TASK_ID + workFlowRecord.getTaskId());
            if (hasClaim) {
                return;
            }
        }

        initTaskDeadlineTime(workFlowRecord, WORK_FLOW_TIME_OUT_COMPLETE_TASK_ID + workFlowRecord.getTaskId());
    }

    private boolean initTaskDeadlineTime(WorkFlowRecord workFlowRecord, String variableName) {
        String deadlineTime = (String) taskService.getVariableLocal(workFlowRecord.getTaskId(), variableName);
        if (StringUtils.isBlank(deadlineTime)) {
            return false;
        }

        workFlowRecord.setIsExpired(false);
        workFlowRecord.setDeadlineTime(deadlineTime);
        return true;
    }

    private String getDate(long ms) {
        long day = ms / (24 * 60 * 60 * 1000);
        long hour = (ms / (60 * 60 * 1000) - day * 24);
        long minute = ((ms / (60 * 1000)) - day * 24 * 60 - hour * 60);
        long second = (ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60);

        String str = "";
        if (day > 0) {
            str = str + day + "天";
        }
        if (hour > 0) {
            str = str + hour + "小时";
        }
        if (minute > 0) {
            str = str + minute + "分钟";
        }
        if (second > 0) {
            str = str + second + "秒";
        }
        if (StringUtils.isBlank(str)) {
            return 0 + "秒";
        }

        return str;
    }
}
