package com.huigou.bpm.engine.application.impl;

import com.huigou.bpm.engine.application.BusinessProcessApplication;
import com.huigou.bpm.engine.delegate.BizTaskListener;
import com.huigou.bpm.engine.domain.model.BizTask;
import com.huigou.bpm.engine.repository.BizTaskRepository;
import com.huigou.bpm.management.application.ProcessVariableApplication;
import com.huigou.bpm.management.application.ServiceParamApplication;
import com.huigou.bpm.management.domain.model.*;
import com.huigou.bpm.management.repository.ProcInstRepository;
import com.huigou.bpm.management.repository.ProcessDeploymentRepository;
import com.huigou.bpm.management.repository.ServiceMappingRepository;
import com.huigou.bpm.management.repository.ServiceParamMappingRepository;
import com.huigou.domain.ValidStatus;
import com.huigou.exception.ApplicationException;
import com.huigou.util.StringUtil;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ManagementService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

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

/**
 * @author yonghuan
 */
public class BusinessProcessApplicationImpl implements BusinessProcessApplication {

    private final static Logger LOG = LoggerFactory.getLogger(BusinessProcessApplicationImpl.class);
    private RuntimeService runtimeService;
    private ServiceParamMappingRepository serviceParamMappingRepository;
    private ProcessVariableApplication processVariableApplication;
    private ServiceParamApplication serviceParamApplication;
    private ProcessDeploymentRepository processDeploymentRepository;
    private ServiceMappingRepository serviceMappingRepository;
    private ProcInstRepository procInstRepository;
    private HistoryService historyService;
    private BizTaskRepository bizTaskRepository;
    private BizTaskListener taskListener;
    private ManagementService managementService;

    @Autowired
    public void setRuntimeService(RuntimeService runtimeService) {
        this.runtimeService = runtimeService;
    }

    @Autowired
    public void setServiceParamMappingRepository(ServiceParamMappingRepository serviceParamMappingRepository) {
        this.serviceParamMappingRepository = serviceParamMappingRepository;
    }

    @Autowired
    public void setProcessVariableApplication(ProcessVariableApplication processVariableApplication) {
        this.processVariableApplication = processVariableApplication;
    }

    @Autowired
    public void setServiceParamApplication(ServiceParamApplication serviceParamApplication) {
        this.serviceParamApplication = serviceParamApplication;
    }

    @Autowired
    public void setProcessDeploymentRepository(ProcessDeploymentRepository processDeploymentRepository) {
        this.processDeploymentRepository = processDeploymentRepository;
    }

    @Autowired
    public void setServiceMappingRepository(ServiceMappingRepository serviceMappingRepository) {
        this.serviceMappingRepository = serviceMappingRepository;
    }

    @Autowired
    public void setProcInstRepository(ProcInstRepository procInstRepository) {
        this.procInstRepository = procInstRepository;
    }

    @Autowired
    public void setHistoryService(HistoryService historyService) {
        this.historyService = historyService;
    }

    @Autowired
    public void setBizTaskRepository(BizTaskRepository bizTaskRepository) {
        this.bizTaskRepository = bizTaskRepository;
    }

    @Autowired
    public void setTaskListener(BizTaskListener taskListener) {
        this.taskListener = taskListener;
    }

    @Autowired
    public void setManagementService(ManagementService managementService) {
        this.managementService = managementService;
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public String startProcessByCode(String code, String bizKey, String name, String description, Map<String, Object> bizData) {
        if (LOG.isInfoEnabled()) {
            LOG.info("启动BPMN流程，code={},bizKey={},bizData={}", code, bizKey, bizData);
        }
        // 判断bizKey是否重复
        Assert.isTrue(procInstRepository.countByBizKey(bizKey) == 0, "bizKey已经存在");
        long runningCount = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(bizKey).count();
        Assert.isTrue(runningCount == 0, "bizKey已经存在");
        long historicCount = historyService.createHistoricProcessInstanceQuery().processInstanceBusinessKey(bizKey).count();
        Assert.isTrue(historicCount == 0, "bizKey已经存在");

        List<ProcessDeployment> deployments = processDeploymentRepository.findByCodeAndStatus(code, ValidStatus.ENABLED.id);
        Assert.isTrue(deployments.size() == 1, "未找到流程定义发布信息");
        ProcessDeployment deployment = deployments.get(0);
        Map<String, Object> variables = processVariableApplication.findByProcdefId(deployment.getProcdefId())
                .stream()
                .filter(v -> bizData.containsKey(v.getCode()))
                .collect(Collectors.toMap(v -> v.getCode(), v -> bizData.get(v.getCode())));
        return runtimeService.startProcessInstanceByKey(code, bizKey, variables).getId();
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void startTask(String code, String bizKey, String serviceCode, Map<String, Object> bizData) {
        if (LOG.isInfoEnabled()) {
            LOG.info("开始处理BPM业务节点，code={},bizKey={},serviceCode={}，bizData={}", code, bizKey, serviceCode,bizData);
        }
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(bizKey, code).singleResult();
        String msgName = "start" + StringUtil.toUpperCamelCase(serviceCode) + "Msg";
        ProcessDeployment deployment = processDeploymentRepository.findByActProcdefId(processInstance.getProcessDefinitionId());
        Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstance.getProcessInstanceId()).list()
                .stream()
                .filter(exec -> existsMessageSubscription(msgName, exec))
                .findAny()
                .orElseThrow(() -> new ApplicationException(String.format("开始处理任务时出错, code=%s,bizKey=%s,serviceCode=%s", code, bizKey, serviceCode)));

        Map<String, Object> variables = Collections.emptyMap();
        ServiceMapping serviceMapping = serviceMappingRepository.findByProcdefIdAndActivityCode(deployment.getProcdefId(), serviceCode);
        if (serviceMapping != null) {
            variables = parseVariableFromOutParam(deployment, serviceCode, bizData);
        }
        runtimeService.messageEventReceived(msgName, execution.getId(), variables);

        BizTask bizTask = bizTaskRepository.findByProcInstIdAndServiceCode(processInstance.getId(), serviceCode);
        if (bizTask != null) {
            bizTask.setTakeDate(new Date());
            bizTask.setStatus(BizTask.Status.TAKE.id);
            bizTaskRepository.save(bizTask);
            taskListener.notify(BizTaskListener.EVENTNAME_TAKE, bizTask);
        }
    }

    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void completeTask(String code, String bizKey, String serviceCode, Map<String, Object> bizData) {
        if (LOG.isInfoEnabled()) {
            LOG.info("处理完成BPM业务节点，code={},bizKey={},serviceCode={}，bizData={}", code, bizKey, serviceCode,bizData);
        }
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(bizKey, code).singleResult();
        String msgName = "end" + StringUtil.toUpperCamelCase(serviceCode) + "Msg";
        ProcessDeployment deployment = processDeploymentRepository.findByActProcdefId(processInstance.getProcessDefinitionId());

        Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstance.getProcessInstanceId()).list()
                .stream()
                .filter(exec -> existsMessageSubscription(msgName, exec))
                .findAny()
                .orElseThrow(() -> new ApplicationException(String.format("完成任务时出错, code=%s,bizKey=%s,serviceCode=%s", code, bizKey, serviceCode)));

        Map<String, Object> variables = Collections.emptyMap();
        ServiceMapping serviceMapping = serviceMappingRepository.findByProcdefIdAndActivityCode(deployment.getProcdefId(), serviceCode);
        if (serviceMapping != null) {
            variables = parseVariableFromOutParam(deployment, serviceCode, bizData);
        }
        runtimeService.messageEventReceived(msgName, execution.getId(), variables);

        BizTask bizTask = bizTaskRepository.findByProcInstIdAndServiceCode(processInstance.getId(), serviceCode);
        if (bizTask != null) {
            bizTask.setEndDate(new Date());
            bizTask.setStatus(BizTask.Status.END.id);
            bizTaskRepository.save(bizTask);
            taskListener.notify(BizTaskListener.EVENTNAME_TAKE, bizTask);
        }
    }

    private boolean existsMessageSubscription(String msgName, Execution execution) {
        return managementService.executeCommand(commandContext -> commandContext.getEventSubscriptionEntityManager()
                .findEventSubscriptionsByNameAndExecution("message", msgName, execution.getId()).size() > 0);
    }

    private Map<String, Object> parseVariableFromOutParam(ProcessDeployment deployment, String serviceCode, Map<String, Object> bizData) {
        List<ServiceParamMapping> paramMappings = serviceParamMappingRepository.findAll(deployment.getProcdefId(), serviceCode);
        Map<String, Object> variables = new HashMap<>(paramMappings.size());
        for (ServiceParamMapping paramMapping : paramMappings) {
            ProcessVariable variable = processVariableApplication.findById(paramMapping.getVariableId());
            if (variable == null) {
                continue;
            }
            ServiceParam param = serviceParamApplication.findById(paramMapping.getParamId());
            if (param == null) {
                continue;
            }
            if (!ServiceParam.Kind.OUT.equals(ServiceParam.Kind.valueOf(param.getKindId()))) {
                continue;
            }
            // 从业务服务的返回参数中获取参数值
            if (bizData.containsKey(param.getCode())) {
                Object paramVal = bizData.get(param.getCode());
                variables.put(variable.getCode(), paramVal);
            }
        }
        return variables;
    }

}
