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

import cn.axzo.framework.jackson.utility.JSON;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO;
import cn.axzo.workflow.common.model.request.form.definition.FormDefinitionDTO;
import cn.axzo.workflow.common.model.request.form.model.FormModelCreateDTO;
import cn.axzo.workflow.common.model.request.form.model.FormModelUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.form.model.FormModelBaseVO;
import cn.axzo.workflow.form.service.FormModelService;
import cn.axzo.workflow.form.service.converter.FormModelConverter;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.NativeModelQuery;
import org.flowable.form.api.FormDeployment;
import org.flowable.form.api.FormRepositoryService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Nullable;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.FORM_FILE_SUFFIX;
import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE;
import static cn.axzo.workflow.common.constant.MetaInfoConstants.MODEL_TYPE_FORM;
import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.countSql;
import static cn.axzo.workflow.common.util.BpmnNativeQueryUtil.sqlConnectors;

/**
 * 表单模型 Service
 *
 * @author wangli
 * @since 2023/7/25 10:13
 */
@Slf4j
@Service
public class FormModelServiceImpl implements FormModelService {

    @Resource
    private FormRepositoryService formRepositoryService;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ManagementService managementService;
    @Resource
    private FormModelConverter formModelConverter;

    @Override
    public String createFormModel(FormModelCreateDTO dto) {
        Model persistModel = repositoryService.createModelQuery()
                .modelKey(dto.getKey())
                .modelCategory(FORM_FILE_SUFFIX)
                .modelTenantId(dto.getTenantId())
                .singleResult();

        persistModel = repositoryService.newModel();
        persistModel.setKey(dto.getKey());
        persistModel.setCategory(FORM_FILE_SUFFIX);
        persistModel.setName(dto.getName());
        persistModel.setTenantId(dto.getTenantId());
        Map<String, String> metaInfoMap = new HashMap<>();
        metaInfoMap.put(MODEL_TYPE, MODEL_TYPE_FORM);

        persistModel.setMetaInfo(JSONUtil.toJsonStr(metaInfoMap));
        repositoryService.saveModel(persistModel);

        FormDefinitionDTO formDefinition = new FormDefinitionDTO();
        formDefinition.setKey(dto.getKey());
        formDefinition.setName(dto.getName());
        formDefinition.setVersion(persistModel.getVersion());
        formDefinition.setFields(dto.getFormFields());

        repositoryService.addModelEditorSource(persistModel.getId(), JSON.toJSONString(formDefinition).getBytes(StandardCharsets.UTF_8));
        return persistModel.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFormModel(FormModelUpdateDTO dto) {
        Model model = repositoryService.createModelQuery()
                .modelId(dto.getFormModelId())
                .modelKey(dto.getKey())
                .modelCategory(FORM_FILE_SUFFIX)
                .modelTenantId(dto.getTenantId())
                .singleResult();
        if (Objects.isNull(model)) {
            model = repositoryService.newModel();
        }
        model.setName(dto.getName());
        model.setKey(dto.getKey());
        model.setCategory(FORM_FILE_SUFFIX);

        Map<String, String> metaInfoMap = new HashMap<>();
        metaInfoMap.put(MODEL_TYPE, MODEL_TYPE_FORM);

        model.setMetaInfo(JSONUtil.toJsonStr(metaInfoMap));
        model.setTenantId(dto.getTenantId());
        repositoryService.saveModel(model);

        FormDefinitionDTO formDefinition = new FormDefinitionDTO();
        formDefinition.setKey(model.getKey());
        formDefinition.setName(dto.getName());
        formDefinition.setVersion(model.getVersion());
        formDefinition.setFields(dto.getFormFields());

        repositoryService.addModelEditorSource(model.getId(), JSON.toJSONString(formDefinition).getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public BpmPageResult<FormModelBaseVO> getModelPage(BpmnModelSearchDTO dto) {
        String tableName = managementService.getTableName(Model.class);
        StringBuilder baseQuerySql = new StringBuilder("SELECT * FROM ")
                .append(tableName);
        NativeModelQuery query = repositoryService.createNativeModelQuery();
        if (!CollectionUtils.isEmpty(dto.getKeys())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" a.KEY_ in (");
            for (int i = 0; i < dto.getKeys().size(); i++) {
                baseQuerySql.append("#{KEY_").append(i).append("}");
                if (i < dto.getKeys().size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("KEY_" + i, dto.getKeys().get(i));
            }
            baseQuerySql.append(")");
        }
        if (StringUtils.hasLength(dto.getName())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" NAME_ = #{name}");
            query.parameter("name", dto.getName());
        }
        if (!CollectionUtils.isEmpty(dto.getCategories())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" CATEGORY_ in (");
            for (int i = 0; i < dto.getCategories().size(); i++) {
                baseQuerySql.append("#{CATEGORY_").append(i).append("}");
                if (i < dto.getCategories().size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("CATEGORY_" + i, dto.getCategories().get(i));
            }
            baseQuerySql.append(")");
        }
        if (!CollectionUtils.isEmpty(dto.getTenantIds())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" TENANT_ID_ in (");
            for (int i = 0; i < dto.getTenantIds().size(); i++) {
                baseQuerySql.append("#{tenantId").append(i).append("}");
                if (i < dto.getTenantIds().size() - 1) {
                    baseQuerySql.append(",");
                }
                query.parameter("TENANT_ID_" + i, dto.getTenantIds().get(i));
            }
            baseQuerySql.append(")");
        }

        baseQuerySql.append(sqlConnectors(baseQuerySql))
                .append(" META_INFO_ like #{metaInfo}");
        query.parameter("metaInfo", "%" + MODEL_TYPE_FORM + "%");
        NativeModelQuery dateSqlQuery = query.sql(baseQuerySql.append(" order by LAST_UPDATE_TIME_ desc").toString());
        List<Model> models = dateSqlQuery
                .listPage((dto.getPageNo() - 1) * dto.getPageSize(), dto.getPageSize());
        if (CollectionUtils.isEmpty(models)) {
            return BpmPageResult.empty();
        }

        NativeModelQuery countSqlQuery = query.sql(countSql(baseQuerySql));
        return new BpmPageResult<>(formModelConverter.toVos(models), countSqlQuery.count());
    }

    @Override
    public FormModelBaseVO getById(String formModelId, @Nullable String tenantId) {
        Model model = repositoryService.getModel(formModelId);
        if (Objects.isNull(model) || !model.getMetaInfo().contains(MODEL_TYPE_FORM)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        if (Objects.nonNull(tenantId) && !Objects.equals(model.getTenantId(), tenantId)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        return formModelConverter.toVo(model);
    }

    @Override
    public FormModelBaseVO getByKey(String formModelKey, @Nullable String tenantId) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        if (StringUtils.hasLength(tenantId)) {
            modelQuery.modelTenantId(tenantId);
        }

        Model model = modelQuery.modelKey(formModelKey).singleResult();
        if (Objects.isNull(model) || !model.getMetaInfo().contains(MODEL_TYPE_FORM)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        return formModelConverter.toVo(model);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String deployFormModelById(String formModelId, String tenantId, String parentDeploymentId) {
        Model model = repositoryService.getModel(formModelId);
        if (Objects.isNull(model) || !model.getMetaInfo().contains(MODEL_TYPE_FORM)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        if (Objects.nonNull(tenantId) && !Objects.equals(model.getTenantId(), tenantId)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        byte[] modelEditorSource = repositoryService.getModelEditorSource(model.getId());
        String modelStr;
        if (StringUtils.hasText(modelStr = new String(modelEditorSource))) {
            try {
                FormDefinitionDTO formDefinitionDTO = JSON.parseObject(modelStr, FormDefinitionDTO.class);
                if (Objects.isNull(formDefinitionDTO) || CollectionUtils.isEmpty(formDefinitionDTO.getFields())) {
                    return null;
                }
            } catch (Exception e) {
                log.warn("form model parse error: {}", e.getMessage(), e);
                return null;
            }
        }
        FormDeployment deploy = formRepositoryService.createDeployment()
                .addFormDefinition(model.getKey() + FORM_FILE_SUFFIX, modelStr)
                .name(model.getName())
                .category(model.getKey())
                .tenantId(model.getTenantId())
                .parentDeploymentId(parentDeploymentId)
                .deploy();

        model.setDeploymentId(deploy.getId());
        return deploy.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String deployFormModelByKey(String formModelKey, String tenantId) {
        ModelQuery modelQuery = repositoryService.createModelQuery().modelKey(formModelKey);
        if (StringUtils.hasLength(tenantId)) {
            modelQuery.modelTenantId(tenantId);
        }
        Model model = modelQuery.singleResult();
        if (Objects.isNull(model) || !model.getMetaInfo().contains(MODEL_TYPE_FORM)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }

        return deployFormModelById(model.getId(), tenantId, null);
    }

    @Override
    public void deleteFormModelById(String formModelId, @Nullable String tenantId) {
        Model model = repositoryService.getModel(formModelId);
        if (Objects.isNull(model)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        if (Objects.nonNull(tenantId) && !Objects.equals(model.getTenantId(), tenantId)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        repositoryService.deleteModel(formModelId);
    }

    @Override
    public void deleteFormModelByKey(String formModelKey, @Nullable String tenantId) {
        ModelQuery modelQuery = repositoryService.createModelQuery().modelKey(formModelKey);
        if (StringUtils.hasLength(tenantId)) {
            modelQuery.modelTenantId(tenantId);
        }
        Model model = modelQuery.singleResult();
        if (Objects.isNull(model)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        repositoryService.deleteModel(model.getId());
    }

}
