package cn.axzo.workflow.core.service.impl;

import cn.axzo.workflow.common.enums.FormFieldTypeEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.BpmnFormRelationSearchDTO;
import cn.axzo.workflow.common.model.request.form.definition.StartFormSearchDTO;
import cn.axzo.workflow.common.model.request.form.instance.FormDetailDTO;
import cn.axzo.workflow.common.model.request.form.instance.FromDataSearchDTO;
import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessDefinitionVO;
import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import cn.axzo.workflow.common.model.response.form.FormVO;
import cn.axzo.workflow.common.model.response.form.definition.FormDefinitionVO;
import cn.axzo.workflow.common.model.response.form.instance.FormDataVO;
import cn.axzo.workflow.common.model.response.form.instance.FormInstanceVO;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.common.utils.FormHelper;
import cn.axzo.workflow.core.engine.cmd.GetFormInstanceAndPermissionCmd;
import cn.axzo.workflow.core.repository.entity.ExtAxBpmnFormRelation;
import cn.axzo.workflow.core.service.BpmnProcessDefinitionService;
import cn.axzo.workflow.core.service.BpmnProcessTaskForEsService;
import cn.axzo.workflow.core.service.CategoryService;
import cn.axzo.workflow.core.service.ExtAxBpmnFormRelationService;
import cn.axzo.workflow.core.service.FormCoreService;
import cn.axzo.workflow.form.service.converter.FormFieldConverter;
import cn.axzo.workflow.form.service.converter.FormInstanceConverter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;
import org.flowable.engine.impl.cmd.GetBpmnModelCmd;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.model.FormContainer;
import org.flowable.form.model.FormField;
import org.flowable.form.model.SimpleFormModel;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;

/**
 * @author wangli
 * @since 2024-11-14 10:18
 */
@Slf4j
@Service
public class FormCoreServiceImpl implements FormCoreService {
    @Resource
    private SpringProcessEngineConfiguration springProcessEngineConfiguration;
    @Resource
    private ExtAxBpmnFormRelationService bpmnFormRelationService;
    @Resource
    private BpmnProcessTaskForEsService bpmnProcessTaskForEsService;
    @Resource
    private FormInstanceConverter formInstanceConverter;
    @Resource
    private FormRepositoryService formRepositoryService;
    @Resource
    private BpmnProcessDefinitionService bpmnProcessDefinitionService;
    @Resource
    private FormFieldConverter formFieldConverter;
    @Resource
    private CategoryService categoryService;

    @Override
    public List<FormVO> pageForm(BpmnFormRelationSearchDTO searchDTO) {
        List<ExtAxBpmnFormRelation> list = bpmnFormRelationService.keyQuery(searchDTO);

        Set<String> uniqueKeys = new HashSet<>();
        List<ExtAxBpmnFormRelation> uniqueRecords = list.stream()
                .filter(record -> uniqueKeys.add(record.getKey() + record.getTenantId()))
                .collect(Collectors.toList());
        List<String> keys = list.stream().map(ExtAxBpmnFormRelation::getKey).distinct().collect(Collectors.toList());
        Map<String, CategoryItemVO> categoryMap = categoryService.listByValue(BPM_MODEL_CATEGORY, keys).stream().collect(Collectors.toMap(CategoryItemVO::getValue, Function.identity(), (s, t) -> s));
        return uniqueRecords.stream()
                .map(e -> FormVO.builder()
                        .key(e.getKey())
                        .name(categoryMap.getOrDefault(e.getKey(), new CategoryItemVO()).getLabel())
                        .tenantId(e.getTenantId())
                        .build())
                .collect(Collectors.toList());
    }

    @Override
    public FormDefinitionVO getStartForm(StartFormSearchDTO dto) {
        FormInfo formModel;
        try {
            BpmnProcessDefinitionVO definitionVO = bpmnProcessDefinitionService.getActiveProcessDefinitionByKey(dto.getKey(), dto.getTenantId());
            formModel = formRepositoryService.getFormModelByKey(dto.getKey(), definitionVO.getTenantId(), false);
            CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
            BpmnModel bpmnModel = commandExecutor.execute(new GetBpmnModelCmd(definitionVO.getId()));
            BpmnMetaParserHelper.getFormFieldPermissionConf(bpmnModel.getFlowElement(NODE_STARTER.getType()))
                    .ifPresent(permission -> {
                        if (formModel.getFormModel() instanceof SimpleFormModel) {
                            SimpleFormModel simpleFormModel = (SimpleFormModel) formModel.getFormModel();
                            FormHelper.populateFormModel(simpleFormModel, permission, new HashMap<>(), dto.getShowOriginDefaultValue());
                        }
                    });
            FormDefinitionVO formDefinitionVO = new FormDefinitionVO();
            formDefinitionVO.setFormDefinitionId(formModel.getId());
            formDefinitionVO.setName(formModel.getName());
            formDefinitionVO.setKey(formModel.getKey());
            formDefinitionVO.setVersion(formModel.getVersion());
            formDefinitionVO.setTenantId(dto.getTenantId());
            List<FormField> fields = ((SimpleFormModel) formModel.getFormModel()).getFields();
            formDefinitionVO.setFields(formFieldConverter.toVos(fields));
            return formDefinitionVO;
        } catch (FlowableObjectNotFoundException e) {
            log.warn("can't found form model");
            if (Objects.equals(Boolean.TRUE, dto.getThrowException())) {
                throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
            }
        }
        return null;
    }

    @Override
    public FormInstanceVO getFormInstance(FormDetailDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        FormInstanceInfo formInstanceInfo = commandExecutor.execute(new GetFormInstanceAndPermissionCmd(bpmnFormRelationService,
                bpmnProcessTaskForEsService, dto.getAssigner(), dto.getProcessInstanceId(), dto.getTaskId(), dto.getShowOriginDefaultValue()));
        return formInstanceConverter.toVo(formInstanceInfo);
    }

    @Override
    public List<FormDataVO> getFormData(FromDataSearchDTO dto) {
        CommandExecutor commandExecutor = springProcessEngineConfiguration.getCommandExecutor();
        FormInstanceInfo formInstanceInfo = commandExecutor.execute(new GetFormInstanceAndPermissionCmd(bpmnFormRelationService,
                bpmnProcessTaskForEsService, null, dto.getProcessInstanceId(), dto.getTaskId(), true));
        List<FormField> fields = null;
        if (Objects.isNull(formInstanceInfo)
                || Objects.isNull(formInstanceInfo.getFormModel())
                || (formInstanceInfo.getFormModel() instanceof SimpleFormModel) && CollectionUtils.isEmpty(fields = ((SimpleFormModel) formInstanceInfo.getFormModel()).getFields())) {
            return Collections.emptyList();
        }

        if (!CollectionUtils.isEmpty(fields)) {
            List<FormDataVO> dataList = new ArrayList<>();
            fields.forEach(i -> recursiveConvert(i, dataList));
            return dataList;
        }
        return Collections.emptyList();
    }

    private void recursiveConvert(FormField field, List<FormDataVO> formDataList) {
        if (Objects.isNull(field)) {
            return;
        }
        if (field instanceof FormContainer) {
            ((FormContainer) field).getFields().forEach(subField -> subField.forEach(i -> recursiveConvert(i, formDataList)));
        } else {
            FormDataVO formDataVO = new FormDataVO();
            formDataVO.setFieldId(field.getId());
            formDataVO.setFieldType(FormFieldTypeEnum.valueOfType(field.getType()));
            // TODO 根据不同数据类型，进行数据转码
            formDataVO.setFieldValue(FormFieldTypeEnum.parseValue(field.getType(), field.getValue(), field.getParams()));
            formDataList.add(formDataVO);
        }
    }

}
