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

import cn.axzo.workflow.common.constant.BpmnConstants;
import cn.axzo.workflow.common.model.request.category.CategoryCreateDTO;
import cn.axzo.workflow.common.model.request.category.CategorySearchDTO;
import cn.axzo.workflow.common.model.request.category.CategoryUpdateDTO;
import cn.axzo.workflow.common.model.response.BpmPageResult;
import cn.axzo.workflow.common.model.response.category.CategoryConfigItemVO;
import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import cn.axzo.workflow.core.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.repository.entity.ExtAxDict;
import cn.axzo.workflow.core.repository.mapper.ExtAxDictMapper;
import cn.axzo.workflow.core.service.CategoryConfigService;
import cn.axzo.workflow.core.service.CategoryService;
import cn.axzo.workflow.core.service.converter.CategoryConverter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_DATA_ERROR;
import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_ID_NOT_EXISTS;
import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_NAME_EXISTS;
import static cn.axzo.workflow.core.common.code.CategoryRespCode.CATEGORY_VALUE_EXISTS;


@Service
@RequiredArgsConstructor
@Slf4j
public class CategoryServiceImpl extends ServiceImpl<ExtAxDictMapper, ExtAxDict> implements CategoryService {
    @Resource
    private ExtAxDictMapper dictMapper;
    @Resource
    private CategoryConfigService categoryConfigService;

    private final CategoryConverter categoryConverter;

    @Override
    public Optional<CategoryItemVO> getById(Long id) {
        ExtAxDict extAxDict = dictMapper.selectById(id);
        return Optional.ofNullable(categoryConverter.toVo(extAxDict));
    }

    @Override
    public Optional<CategoryItemVO> get(String type, String value) {
        List<ExtAxDict> list = this.lambdaQuery().eq(ExtAxDict::getType, type)
                .eq(ExtAxDict::getValue, value)
                .list();
        if (CollectionUtils.isEmpty(list)) {
            return Optional.empty();
        } else if (list.size() > 1) {
            throw new WorkflowEngineException(CATEGORY_DATA_ERROR, value);
        } else {
            return Optional.of(categoryConverter.toVo(list.get(0)));
        }
    }

    @Override
    public CategoryItemVO createCategory(CategoryCreateDTO dto) {
        // value 要全局去重
        checkValue(dto.getValue());

        // name 在相同的工作台类型下去重
        checkLabel(dto.getLabel(), dto.getWorkspaceCodeType());

        ExtAxDict dict = new ExtAxDict();
        populateDict(dto, dict);
        dictMapper.insert(dict);
        return categoryConverter.toVo(dict);
    }

    private void checkLabel(String label, String workspaceCodeType) {
        Optional<ExtAxDict> optLabel = this.lambdaQuery()
                .eq(ExtAxDict::getLabel, label)
                .eq(ExtAxDict::getWorkspaceTypeCode, workspaceCodeType)
                .eq(ExtAxDict::getType, BPM_MODEL_CATEGORY)
                .eq(ExtAxDict::getTenantId, BpmnConstants.NO_TENANT_ID)
                .oneOpt();
        if (optLabel.isPresent()) {
            throw new WorkflowEngineException(CATEGORY_NAME_EXISTS, label);
        }
    }

    private void checkValue(String value) {
        Optional<ExtAxDict> optValue = this.lambdaQuery()
                .eq(ExtAxDict::getValue, value)
                .eq(ExtAxDict::getType, BPM_MODEL_CATEGORY)
                .eq(ExtAxDict::getTenantId, BpmnConstants.NO_TENANT_ID)
                .oneOpt();
        if (optValue.isPresent()) {
            throw new WorkflowEngineException(CATEGORY_VALUE_EXISTS, value);
        }
    }

    private static void populateDict(CategoryCreateDTO dto, ExtAxDict dict) {
        dict.setType(dto.getType());
        dict.setLabel(dto.getLabel());
        dict.setValue(dto.getValue());
        dict.setRemark(dto.getRemark());
        dict.setSort(dto.getSort());
        dict.setStatus(Objects.isNull(dto.getStatus()) ? 1 : 0);
        dict.setTenantId(dto.getTenantId());
        dict.setOperatorName(dto.getOperatorName());
        dict.setWorkspaceTypeCode(dto.getWorkspaceCodeType());
    }

    @Override
    public CategoryItemVO updateCategory(CategoryUpdateDTO dto) {
        Optional<ExtAxDict> dictDO = this.lambdaQuery().eq(ExtAxDict::getId, dto.getId())
                .oneOpt();
        if (!dictDO.isPresent()) {
            throw new WorkflowEngineException(CATEGORY_ID_NOT_EXISTS, String.valueOf(dto.getId()));
        }

        ExtAxDict dictInDb = dictDO.get();
        if (!Objects.equals(dictInDb.getValue(), dto.getValue())) {
            checkValue(dto.getValue());
        }

        if (!Objects.equals(dictInDb.getLabel(), dto.getLabel())) {
            checkLabel(dto.getLabel(), dto.getWorkspaceCodeType());
        }

        dictDO.ifPresent(dict -> {
            populateDict(dto, dict);
            dictMapper.updateById(dictInDb);
        });
        return categoryConverter.toVo(dictInDb);
    }

    @Override
    public int deleteCategory(Long id) {
        return dictMapper.deleteById(id);
    }

    @Override
    public BpmPageResult<CategoryItemVO> search(CategorySearchDTO dto) {
        LambdaQueryWrapper<ExtAxDict> queryWrapper = Wrappers.lambdaQuery(ExtAxDict.class)
                .eq(StringUtils.isNotBlank(dto.getDictType()), ExtAxDict::getType, dto.getDictType())
                .like(StringUtils.isNotBlank(dto.getLabel()), ExtAxDict::getLabel, dto.getLabel())
                .eq(StringUtils.isNotBlank(dto.getValue()), ExtAxDict::getValue, dto.getValue())
                .eq(Objects.nonNull(dto.getStatus()), ExtAxDict::getStatus, dto.getStatus())
                .eq(StringUtils.isNotBlank(dto.getWorkspaceTypeCode()), ExtAxDict::getWorkspaceTypeCode,
                        dto.getWorkspaceTypeCode())
                .eq(ExtAxDict::getTenantId, dto.getTenantId())
                .orderByDesc(ExtAxDict::getCreateAt);

        Page<ExtAxDict> page = dictMapper.selectPage(new Page<>(dto.getPageNo(), dto.getPageSize()),
                queryWrapper);

        List<CategoryItemVO> categoryVOS = categoryConverter.toVos(page.getRecords());
        return new BpmPageResult<>(categoryVOS, page.getTotal());
    }

    @Override
    public CategoryItemVO get(Long id) {
        ExtAxDict extAxDict = dictMapper.selectById(id);
        return categoryConverter.toVo(extAxDict);
    }

    @Override
    public List<CategoryItemVO> getIds(List<Long> ids) {
        List<ExtAxDict> extAxDicts = dictMapper.selectBatchIds(ids);
        return categoryConverter.toVos(extAxDicts);
    }

    @Override
    public List<CategoryItemVO> getByValues(List<String> values) {
        if (CollectionUtils.isEmpty(values)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<ExtAxDict> queryWrapper = Wrappers.lambdaQuery(ExtAxDict.class)
                .in(ExtAxDict::getValue, values)
                .eq(ExtAxDict::getIsDelete, 0);
        List<ExtAxDict> extAxDicts = dictMapper.selectList(queryWrapper);
        return categoryConverter.toVos(extAxDicts);
    }

    @Override
    public Boolean updateState(Long id, Boolean state) {
        Optional<ExtAxDict> dictDO = this.lambdaQuery().eq(ExtAxDict::getId, id).oneOpt();
        if (!dictDO.isPresent()) {
            throw new WorkflowEngineException(CATEGORY_ID_NOT_EXISTS, String.valueOf(id));
        }
        dictDO.ifPresent(dict -> {
            dict.setStatus(state ? 1 : 0);
        });
        dictMapper.updateById(dictDO.get());
        return true;
    }

    @Override
    public Boolean updateRemark(Long id, String configType) {
        Optional<ExtAxDict> dictDO = this.lambdaQuery().eq(ExtAxDict::getId, id).oneOpt();
        if (!dictDO.isPresent()) {
            throw new WorkflowEngineException(CATEGORY_ID_NOT_EXISTS, String.valueOf(id));
        }
        dictDO.ifPresent(dict -> {
            dict.setRemark(configType);
        });
        dictMapper.updateById(dictDO.get());
        return true;
    }

    @Override
    public List<CategoryItemVO> list(CategorySearchDTO dto) {
        LambdaQueryWrapper<ExtAxDict> queryWrapper = Wrappers.lambdaQuery(ExtAxDict.class)
                .eq(StringUtils.isNotBlank(dto.getDictType()), ExtAxDict::getType, dto.getDictType())
                .like(StringUtils.isNotBlank(dto.getLabel()), ExtAxDict::getLabel, dto.getLabel())
                .eq(StringUtils.isNotBlank(dto.getValue()), ExtAxDict::getValue, dto.getValue())
                .eq(Objects.nonNull(dto.getStatus()), ExtAxDict::getStatus, dto.getStatus())
                .eq(StringUtils.isNotBlank(dto.getWorkspaceTypeCode()), ExtAxDict::getWorkspaceTypeCode,
                        dto.getWorkspaceTypeCode())
                .eq(ExtAxDict::getTenantId, dto.getTenantId())
                .eq(ExtAxDict::getIsDelete, 0);
        List<ExtAxDict> extAxDicts = dictMapper.selectList(queryWrapper);
        return categoryConverter.toVos(extAxDicts);
    }

    @Override
    public List<CategoryItemVO> listByValue(String type, List<String> values) {
        LambdaQueryWrapper<ExtAxDict> queryWrapper = Wrappers.lambdaQuery(ExtAxDict.class)
                .eq(StringUtils.isNotBlank(type), ExtAxDict::getType, type)
                .in(!CollectionUtils.isEmpty(values), ExtAxDict::getValue, values)
                .eq(ExtAxDict::getIsDelete, 0);
        List<ExtAxDict> extAxDicts = dictMapper.selectList(queryWrapper);
        return categoryConverter.toVos(extAxDicts);
    }

    @Override
    public Boolean checkCategoryStatus(Long tenantId, String categoryCode) {
        Optional<CategoryItemVO> optCategory = get(BPM_MODEL_CATEGORY, categoryCode);
        if (!optCategory.isPresent()) {
            return true;
        }
        AtomicReference<Boolean> result = new AtomicReference<>(false);
        optCategory.ifPresent(category -> {
            if (Objects.equals("white", category.getRemark())) {
                categoryConfigService.list(category.getId(), category.getRemark())
                        .stream().map(CategoryConfigItemVO::getWorkspaceId)
                        .filter(i -> Objects.equals(i, tenantId))
                        .findAny()
                        .ifPresent(i -> result.set(true));
            } else if (Objects.equals("black", category.getRemark())) {
                boolean b = categoryConfigService.list(category.getId(), category.getRemark())
                        .stream().map(CategoryConfigItemVO::getWorkspaceId)
                        .noneMatch(i -> Objects.equals(i, tenantId));
                result.set(b);
            } else {
                result.set(true);
            }
        });
        return result.get();
    }
}
