package cn.ey.biz.module.system.service.errorcode;

import cn.ey.biz.common.enums.LangEnum;
import cn.ey.biz.framework.mybatis.core.util.MyBatisUtils;
import cn.ey.biz.module.system.controller.admin.errorcode.vo.*;
import cn.hutool.core.collection.CollUtil;
import cn.ey.biz.common.pojo.PageResult;
import cn.ey.biz.module.system.api.errorcode.dto.ErrorCodeAutoGenerateReqDTO;
import cn.ey.biz.module.system.api.errorcode.dto.ErrorCodeRespDTO;
import cn.ey.biz.module.system.convert.errorcode.ErrorCodeConvert;
import cn.ey.biz.module.system.dal.dataobject.errorcode.ErrorCodeDO;
import cn.ey.biz.module.system.dal.mapper.errorcode.ErrorCodeMapper;
import cn.ey.biz.module.system.enums.errorcode.ErrorCodeTypeEnum;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import static cn.ey.biz.common.exception.util.ServiceExceptionUtil.exception;
import static cn.ey.biz.common.util.collection.CollectionUtils.convertMap;
import static cn.ey.biz.common.util.collection.CollectionUtils.convertSet;
import static cn.ey.biz.module.system.enums.ErrorCodeConstants.ERROR_CODE_DUPLICATE;
import static cn.ey.biz.module.system.enums.ErrorCodeConstants.ERROR_CODE_NOT_EXISTS;

/**
 * 错误码 Service 实现类
 *
 */
@Service
@Validated
@Slf4j
public class ErrorCodeServiceImpl implements ErrorCodeService {

    @Resource
    private ErrorCodeMapper errorCodeMapper;

    @Override
    public void createErrorCode(ErrorCodeCreateReqVO createReqVO) {
        // 校验 code 重复
        validateCodeDuplicate(createReqVO.getCode(),createReqVO.getApplicationName());
        List<ErrorCodeDO> list = Lists.newArrayList();
        createReqVO.getLangList().forEach(item -> {
            ErrorCodeDO errorCode = ErrorCodeConvert.INSTANCE.convert(createReqVO);
            errorCode.setType(ErrorCodeTypeEnum.MANUAL_OPERATION.getType());
            errorCode.setLang(item.getLang());
            errorCode.setMessage(item.getLabel());
            list.add(errorCode);
        });
        errorCodeMapper.insertBatch(list);
    }

    @Override
    @Transactional
    public void updateErrorCode(ErrorCodeUpdateReqVO updateReqVO) {
        // 校验存在
        //this.validateErrorCodeExists(updateReqVO.getId());
        // 校验 code 重复
        validateCodeDuplicateUpdate(updateReqVO);
        List<ErrorCodeDO> errorCodeDo = errorCodeMapper
                .selectList(new LambdaQueryWrapper<ErrorCodeDO>()
                        .in(ErrorCodeDO::getId, updateReqVO.getIds()));

        if(CollectionUtil.isNotEmpty(errorCodeDo)){
            errorCodeMapper.deleteBatchIds(errorCodeDo);
        }
        List<ErrorCodeDO> list = Lists.newArrayList();
        updateReqVO.getLangList().forEach(item -> {
            ErrorCodeDO errorCode = ErrorCodeConvert.INSTANCE.convert(updateReqVO);
            errorCode.setId(null);
            if(CollectionUtil.isNotEmpty(errorCodeDo)){
                errorCode.setType(errorCodeDo.get(0).getType());
            }
            errorCode.setLang(item.getLang());
            errorCode.setMessage(item.getLabel());
            list.add(errorCode);
        });
        errorCodeMapper.insertBatch(list);
    }

    @Override
    public void deleteErrorCode(List<Long> ids) {
        // 校验存在
        //this.validateErrorCodeExists(id);
        // 删除
        List<ErrorCodeDO> errorCodeDo = errorCodeMapper
                .selectList(new LambdaQueryWrapper<ErrorCodeDO>()
                        .in(ErrorCodeDO::getId, ids));

        if(CollectionUtil.isNotEmpty(errorCodeDo)){
            errorCodeMapper.deleteBatchIds(errorCodeDo);
        }
    }

    /**
     *
     * @param code
     * @param applicationName
     */
    @VisibleForTesting
    public void validateCodeDuplicate(Integer code,String applicationName) {
        List<ErrorCodeDO> errorCodeDO = errorCodeMapper.selectByCode(code,applicationName);
        if (CollectionUtil.isNotEmpty(errorCodeDO)) {
            throw exception(ERROR_CODE_DUPLICATE);
        }
    }


    public void validateCodeDuplicateUpdate(ErrorCodeUpdateReqVO updateReqVO) {
        List<ErrorCodeDO> errorCodeDO = errorCodeMapper.selectByCode(updateReqVO.getCode(),updateReqVO.getApplicationName());
        if (CollectionUtil.isNotEmpty(errorCodeDO)) {
            if(!updateReqVO.getIds().contains(errorCodeDO.get(0).getId())){
                throw exception(ERROR_CODE_DUPLICATE);
            }
        }
    }


    @VisibleForTesting
    public void validateErrorCodeExists(Long id) {
        if (errorCodeMapper.selectById(id) == null) {
            throw exception(ERROR_CODE_NOT_EXISTS);
        }
    }

    @Override
    public ErrorCodeRespVO getErrorCode(List<Long> ids) {
        ErrorCodeRespVO errorCodeRespVO=null;

        List<ErrorCodeDO> errorCodeDo = errorCodeMapper
                .selectList(new LambdaQueryWrapper<ErrorCodeDO>()
                        .in(ErrorCodeDO::getId, ids));

        if(CollectionUtil.isNotEmpty(errorCodeDo)){
            ErrorCodeDO errorCodeDO=errorCodeDo.get(0);
            errorCodeRespVO=ErrorCodeConvert.INSTANCE.convert(errorCodeDO);

            List<ErrorCodeLangReqVo> errorCodeLangReqVoList=new ArrayList<>();
            List<Long> idList=new ArrayList<Long>();
            for(ErrorCodeDO err:errorCodeDo){
                ErrorCodeLangReqVo errorCodeLangReqVo=new ErrorCodeLangReqVo();
                errorCodeLangReqVo.setLang(err.getLang());
                errorCodeLangReqVo.setLabel(err.getMessage());
                errorCodeLangReqVoList.add(errorCodeLangReqVo);
                idList.add(err.getId());
            }
            errorCodeRespVO.setLangList(errorCodeLangReqVoList);
            errorCodeRespVO.setIds(idList);
        }
       return errorCodeRespVO;
    }

    @Override
    public PageResult<ErrorCodeRespVO> getErrorCodePage(ErrorCodePageReqVO pageReqVO) {
        IPage page = MyBatisUtils.buildPage(pageReqVO);
        IPage<ErrorCodeDO> result = errorCodeMapper.selectListPage(page, pageReqVO);
        PageResult<ErrorCodeRespVO> errorCodeRespVOPage=ErrorCodeConvert.INSTANCE.convertPage(new PageResult<ErrorCodeDO>(result.getRecords(), result.getTotal()));
        List<ErrorCodeRespVO> errorCodeVoList=errorCodeRespVOPage.getList();
        if(CollectionUtil.isNotEmpty(errorCodeRespVOPage.getList())){
            for(ErrorCodeRespVO er:errorCodeVoList){
                LambdaQueryWrapper<ErrorCodeDO> wrappers = Wrappers.lambdaQuery(ErrorCodeDO.class)
                        .eq(ErrorCodeDO::getApplicationName, er.getApplicationName())
                        .eq(ErrorCodeDO::getCode,er.getCode());
                List<ErrorCodeDO> errorCodeDo=errorCodeMapper.selectList(wrappers);

                List<ErrorCodeLangReqVo> errorCodeLangReqVoList=new ArrayList<>();
                List<Long> idList=new ArrayList<Long>();
                for(ErrorCodeDO  err:errorCodeDo){
                    ErrorCodeLangReqVo errorCodeLangReqVo=new ErrorCodeLangReqVo();
                    errorCodeLangReqVo.setLang(err.getLang());
                    errorCodeLangReqVo.setLabel(err.getMessage());
                    errorCodeLangReqVoList.add(errorCodeLangReqVo);
                    idList.add(err.getId());
                }
                er.setLangList(errorCodeLangReqVoList);
                er.setIds(idList);
            }
        }
        return errorCodeRespVOPage;
    }

    @Override
    public List<ErrorCodeDO> getErrorCodeList(ErrorCodeExportReqVO exportReqVO) {
        return errorCodeMapper.selectList(exportReqVO);
    }

    @Override
    @Transactional
    public void autoGenerateErrorCodes(List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs) {
        if (CollUtil.isEmpty(autoGenerateDTOs)) {
            return;
        }
        // 获得错误码
        List<ErrorCodeDO> errorCodeDOs = errorCodeMapper.selectListByCodes(
                convertSet(autoGenerateDTOs, ErrorCodeAutoGenerateReqDTO::getCode));
        Map<Integer, ErrorCodeDO> errorCodeDOMap = convertMap(errorCodeDOs, ErrorCodeDO::getCode);

        // 遍历 autoGenerateBOs 数组，逐个插入或更新。考虑到每次量级不大，就不走批量了
        autoGenerateDTOs.forEach(autoGenerateDTO -> {
            ErrorCodeDO errorCodeDO = errorCodeDOMap.get(autoGenerateDTO.getCode());
            // 不存在，则进行新增
            if (errorCodeDO == null) {
                errorCodeDO = ErrorCodeConvert.INSTANCE.convert(autoGenerateDTO)
                        .setLang(LangEnum.ZH_CN.getLang())
                        .setType(ErrorCodeTypeEnum.AUTO_GENERATION.getType());
                errorCodeMapper.insert(errorCodeDO);
                return;
            }
            // 存在，则进行更新。更新有三个前置条件：
            // 条件 1. 只更新自动生成的错误码，即 Type 为 ErrorCodeTypeEnum.AUTO_GENERATION
            if (!ErrorCodeTypeEnum.AUTO_GENERATION.getType().equals(errorCodeDO.getType())) {
                return;
            }
            // 条件 2. 分组 applicationName 必须匹配，避免存在错误码冲突的情况
            if (!autoGenerateDTO.getApplicationName().equals(errorCodeDO.getApplicationName())) {
                log.error("[autoGenerateErrorCodes][自动创建({}/{}) 错误码失败，数据库中已经存在({}/{})]",
                        autoGenerateDTO.getCode(), autoGenerateDTO.getApplicationName(),
                        errorCodeDO.getCode(), errorCodeDO.getApplicationName());
                return;
            }
            // 条件 3. 错误提示语存在差异
            if (autoGenerateDTO.getMessage().equals(errorCodeDO.getMessage())) {
                return;
            }
            // 最终匹配，进行更新
            errorCodeMapper.updateById(new ErrorCodeDO().setId(errorCodeDO.getId()).setMessage(autoGenerateDTO.getMessage()));
        });
    }

    @Override
    public List<ErrorCodeRespDTO> getErrorCodeList(String applicationName, Date minUpdateTime) {
        List<ErrorCodeDO> errorCodeDOs = errorCodeMapper.selectListByApplicationNameAndUpdateTimeGt(
                applicationName, minUpdateTime);
        return ErrorCodeConvert.INSTANCE.convertList03(errorCodeDOs);
    }

}

