package cn.axzo.workflow.server.controller.delegate;

import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.CarbonCopyObjectType;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.deletage.BpmnTaskAssigneeSelector;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeDTO;
import cn.axzo.workflow.core.deletage.approverscope.ApproverScopeProcessor;
import cn.axzo.workflow.core.engine.listener.EngineExecutionStartListener;
import cn.axzo.workflow.server.common.util.DingTalkUtils;
import cn.hutool.core.lang.Assert;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.CustomProperty;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static cn.axzo.workflow.core.common.code.BpmnTaskRespCode.CALC_TASK_ASSIGNEE_ERROR;
import static cn.axzo.workflow.core.common.code.ConvertorRespCode.CONVERTOR_META_DATA_FORMAT_ERROR;
import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_CALC_ERROR;
import static cn.axzo.workflow.core.common.code.FlowableEngineRespCode.ENGINE_USER_TASK_PARAM_ERROR;


/**
 * 抽象的流程任务审批人选择器
 *
 * @author wangli
 * @since 2023/11/17 11:35
 */
@Slf4j
public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssigneeSelector, ApplicationContextAware {
    private ApplicationContext applicationContext;
    private EngineExecutionStartListener executionStartListener;

    @Override
    public List<BpmnTaskDelegateAssigner> select(FlowElement flowElement, DelegateExecution execution,
                                                 Boolean throwException) {
        List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();
        if (flowElement instanceof UserTask) {
            assigners.addAll(BpmnMetaParserHelper.getApproverScope((UserTask) flowElement)
                    .map(approverScopeEnum -> privateSelector(approverScopeEnum, flowElement, execution,
                            throwException))
                    .orElseGet(Collections::emptyList));
        } else if (flowElement instanceof ServiceTask) {
            List<CustomProperty> customProperties = ((ServiceTask) flowElement).getCustomProperties();
            if (!CollectionUtils.isEmpty(customProperties)) {
                String processor = CarbonCopyObjectType.valueOfType(customProperties.get(0).getName()).getProcessor();
                assigners.addAll(privateSelector(ApproverScopeEnum.valueOfProcessor(processor), flowElement,
                        execution, throwException));
            }
        }
        return assigners.stream().filter(i -> StringUtils.hasText(i.getPersonId())).collect(Collectors.toList());
    }

    private List<BpmnTaskDelegateAssigner> privateSelector(ApproverScopeEnum processorType, FlowElement flowElement,
                                                           DelegateExecution execution, Boolean throwException) {
        ApproverScopeProcessor processor = applicationContext.getBean(processorType.getProcessor(),
                ApproverScopeProcessor.class);
        ApproverScopeDTO scopeDto = processor.build(flowElement, execution);
        if (CollectionUtils.isEmpty(scopeDto.getOrgScopes())
                && CollectionUtils.isEmpty(scopeDto.getWorkerTeamScopes())) {
            if (throwException) {
                throw new WorkflowEngineException(ENGINE_USER_TASK_PARAM_ERROR, flowElement.getId(),
                        processorType.getDesc());
            }
        }
        try {
            return invokeService(flowElement, execution, scopeDto);
        } catch (Throwable t) {
            if (throwException) {
                if (!(t instanceof WorkflowEngineException)) {
                    throw new WorkflowEngineException(ENGINE_USER_TASK_CALC_ERROR, flowElement.getId(),
                            this.getType(), t.getMessage());
                }
                throw t;
            } else {
                return Collections.emptyList();
            }
        }
    }

    protected List<BpmnTaskDelegateAssigner> invokeService(FlowElement flowElement, DelegateExecution execution,
                                                           ApproverScopeDTO scopeDto) {
        return Collections.emptyList();
    }

    protected final <T> T parseApiResult(Supplier<ApiResult<T>> supplier, String operatorDesc,
                                         String extInfo, Object... param) {
        StopWatch stopWatch = new StopWatch(operatorDesc);
        log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param));
        stopWatch.start();
        ApiResult<T> result = supplier.get();
        stopWatch.stop();
        log.info("{}-Cost:{}, Result: {}", operatorDesc,
                "API StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " s",
                JSONUtil.toJsonStr(result));
        try {
            if (stopWatch.getTotalTimeSeconds() > executionStartListener.getApiTimeout()) {
                DingTalkUtils.sendDingTalkForSlowUrl(applicationContext.getEnvironment()
                                .getProperty("spring.profiles.active"),
                        stopWatch.getTotalTimeSeconds(),
                        extInfo,
                        param,
                        result);
            }
        } catch (Exception e) {
            // ignore
        }
        Assert.notNull(result, "服务调用异常");
        // 200自定义处理
        if (HttpStatus.HTTP_OK != result.getCode()) {
            throw new WorkflowEngineException(CALC_TASK_ASSIGNEE_ERROR, "[API:" + extInfo + "]" + result.getMsg());
        }
        return result.getData();
    }

    @Override
    public List<String> getTypes(FlowElement flowElement) {
        // 默认解析格式：[{"name":"预算员", "value":"xxxx"}]
        try {
            if (flowElement instanceof UserTask) {
                return BpmnMetaParserHelper.getApproverSpecifyValue((UserTask) flowElement)
                        .map(value -> JSON.parseArray(value, String.class).stream().map(JSON::parseObject)
                                .map(i -> i.getString("value"))
                                .collect(Collectors.toList()))
                        .orElse(Collections.emptyList());
            } else if (flowElement instanceof ServiceTask) {
                List<CustomProperty> customProperties = ((ServiceTask) flowElement).getCustomProperties();
                if (CollectionUtils.isEmpty(customProperties)) {
                    return Collections.emptyList();
                }
                return JSON.parseArray(customProperties.get(0).getSimpleValue(), String.class)
                        .stream().map(JSON::parseObject)
                        .map(i -> i.getString("value"))
                        .collect(Collectors.toList());
            } else {
                return Collections.emptyList();
            }
        } catch (Exception e) {
            throw new WorkflowEngineException(CONVERTOR_META_DATA_FORMAT_ERROR, "AbstractBpmnTaskAssigneeSelector" +
                    "#getTypes", e.getMessage());
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.applicationContext = context;
        executionStartListener = applicationContext.getBean(EngineExecutionStartListener.class);
    }
}
