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

import cn.axzo.workflow.common.model.request.bpmn.model.BpmnModelSearchDTO;
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.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.service.FormModelService;
import cn.axzo.workflow.core.service.converter.FormModelConverter;
import cn.hutool.json.JSONUtil;
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.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

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.core.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS;
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.countSql;
import static cn.axzo.workflow.core.common.utils.BpmnNativeQueryUtil.sqlConnectors;

/**
 * 表单模型 Service
 *
 * @author wangli
 * @since 2023/7/25 10:13
 */
@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())
                .modelTenantId(dto.getTenantId())
                .singleResult();
        if (Objects.nonNull(persistModel)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        Map<String, String> metaInfoMap = new HashMap<>();
        metaInfoMap.put(MODEL_TYPE, MODEL_TYPE_FORM);

        Model model = repositoryService.newModel();
        model.setKey(dto.getKey());
        model.setCategory(dto.getCategory());
        model.setName(dto.getName());
        model.setTenantId(dto.getTenantId());
        model.setMetaInfo(JSONUtil.toJsonStr(metaInfoMap));
        repositoryService.saveModel(model);
        return model.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFormModel(FormModelUpdateDTO dto) {
        Model model = repositoryService.createModelQuery()
                .modelId(dto.getFormModelId())
                .modelKey(dto.getKey())
                .modelTenantId(dto.getTenantId())
                .singleResult();
        if (Objects.isNull(model)) {
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        model.setName(dto.getName());
        model.setCategory(dto.getCategory());
        repositoryService.saveModel(model);
    }

    @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 (StringUtils.hasLength(dto.getKey())) {
            baseQuerySql.append(sqlConnectors(baseQuerySql))
                    .append(" KEY_ = #{key}");
            query.parameter("key", dto.getKey());
        }
        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) {
        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);
        }
        FormDeployment deploy = formRepositoryService.createDeployment()
                .addFormDefinition(model.getKey() + FORM_FILE_SUFFIX,
                        new String(repositoryService.getModelEditorSource(model.getId())))
                .name(model.getName())
                .category(model.getCategory())
                .tenantId(model.getTenantId())
                .deploy();

        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);
    }

    @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());
    }

}
