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

import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.karma.client.feign.FlowSupportApi;
import cn.axzo.karma.client.model.request.PersonProfileQueryReq;
import cn.axzo.karma.client.model.response.PersonProfileResp;
import cn.axzo.orggateway.api.nodeuser.resp.FlowTaskAssignerV2Resp;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.CarbonCopyObjectType;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.conf.SupportRefreshProperties;
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.model.NoticeFlowElement;
import cn.axzo.workflow.core.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 javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

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


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

    @Override
    public boolean support(String param) {
        return getType().equals(param);
    }

    @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));
            }
        } else if (flowElement instanceof NoticeFlowElement) {
            ApproverScopeEnum processor = ((NoticeFlowElement) flowElement).getProcessor();
            assigners.addAll(privateSelector(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()) && Boolean.TRUE.equals(throwException)) {
            throw new WorkflowEngineException(ENGINE_USER_TASK_PARAM_ERROR, flowElement.getId(), processorType.getDesc());
        }
        try {
            return populateNameAndAvatar(invokeService(flowElement, execution, scopeDto), flowSupportApi, refreshProperties, applicationContext);
        } catch (Throwable t) {
            if (Boolean.TRUE.equals(throwException)) {
                log.warn("执行查询候选审批人时发现异常, 审批节点：{}, 异常信息：{}", flowElement.getId(), t.getMessage());
                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 parseFoundationApiResult(Supplier<cn.axzo.foundation.result.ApiResult<T>> supplier, String operatorDesc,
                                                   String extInfo, Object... param) {
        StopWatch stopWatch = new StopWatch(operatorDesc);
        log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param));
        stopWatch.start();
        cn.axzo.foundation.result.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() > refreshProperties.getApiTimeout() && Boolean.TRUE.equals(refreshProperties.getSendDingTalk())) {
                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();
    }


    protected final <T> T parseApiResult(Supplier<ApiResult<T>> supplier, String operatorDesc,
                                         String extInfo, Object... param) {
        return parseApiResult(supplier, operatorDesc, extInfo, refreshProperties, applicationContext, param);
    }

    public static <T> T parseApiResult(Supplier<ApiResult<T>> supplier, String operatorDesc,
                                       String extInfo, SupportRefreshProperties refreshProperties,
                                       ApplicationContext context, 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() > refreshProperties.getApiTimeout() && refreshProperties.getSendDingTalk()) {
                DingTalkUtils.sendDingTalkForSlowUrl(context.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 if (flowElement instanceof NoticeFlowElement) {
                String customValues = ((NoticeFlowElement) flowElement).getCustomValues();
                if (StringUtils.hasText(customValues)) {
                    return JSON.parseArray(customValues, String.class)
                            .stream().map(JSON::parseObject)
                            .map(i -> i.getString("value"))
                            .collect(Collectors.toList());
                }
                return Collections.emptyList();
            } else {
                return Collections.emptyList();
            }
        } catch (Exception e) {
            throw new WorkflowEngineException(CONVERTOR_META_DATA_FORMAT_ERROR, "AbstractBpmnTaskAssigneeSelector" +
                    "#getTypes", e.getMessage());
        }
    }

    public static List<BpmnTaskDelegateAssigner> populateNameAndAvatar(List<BpmnTaskDelegateAssigner> assigners,
                                                                       FlowSupportApi flowSupportApi,
                                                                       SupportRefreshProperties refreshProperties,
                                                                       ApplicationContext context) {
        if (CollectionUtils.isEmpty(assigners)) {
            return assigners;
        }
        List<Long> personIds = assigners.stream()
                .map(BpmnTaskDelegateAssigner::getPersonId)
                .filter(e -> Objects.nonNull(e) && StringUtils.hasText(e) && !Objects.equals("null", e) && !Objects.equals("system", e))
                .map(Long::parseLong)
                .distinct().collect(Collectors.toList());
        if (CollectionUtils.isEmpty(personIds)) {
            return assigners;
        }
        Map<Long, PersonProfileResp> personProfileMap = parseApiResult(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personIds(personIds).build()),
                "根据 PersonId 查询自然人档案", "cn.axzo.karma.client.feign.FlowSupportApi.listPersons", refreshProperties, context, personIds)
                .stream().collect(Collectors.toMap(PersonProfileResp::getId, Function.identity(), (s, t) -> s));
        assigners.forEach(assigner -> {
            if (!StringUtils.hasText(assigner.getPersonId()) || Objects.equals("null", assigner.getPersonId()) || Objects.equals("system", assigner.getPersonId())) {
                return;
            }
            long personId = Long.parseLong(Optional.ofNullable(assigner.getPersonId()).orElse("-1"));
            if (personProfileMap.containsKey(personId)) {
                log.info("查询到的人员信息：{}", JSON.toJSONString(personProfileMap.getOrDefault(personId, null)));
                assigner.setAvatar(personProfileMap.getOrDefault(personId, new PersonProfileResp()).getAvatarUrl());
                assigner.setAssignerName(personProfileMap.getOrDefault(personId, new PersonProfileResp()).getRealName());

            } else {
                log.warn("未找到对应 person：{} 的数据", personId);
            }
        });
        return assigners;
    }

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

    public static List<BpmnTaskDelegateAssigner> convertApprover(List<FlowTaskAssignerV2Resp> flowTaskAssignersV2) {
        if (CollectionUtils.isEmpty(flowTaskAssignersV2)) {
            return Collections.emptyList();
        }
        return flowTaskAssignersV2.stream()
                .map(i -> BpmnTaskDelegateAssigner.builder()
                        .assignee(i.getIdentityId())
                        .assigneeType(i.getIdentityType())
                        .assignerName(i.getRealName())
                        .personId(String.valueOf(i.getPersonId()))
                        .tenantId(String.valueOf(i.getWorkspaceId()))
                        .ouId(String.valueOf(i.getOuId()))
                        .avatar(i.getAvatarUrl())
                        .nodeId(i.getNodeId())
                        .build())
                .collect(Collectors.toList());
    }

}
