/**
 * BladeX Commercial License Agreement
 * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
 * <p>
 * Use of this software is governed by the Commercial License Agreement
 * obtained after purchasing a license from BladeX.
 * <p>
 * 1. This software is for development use only under a valid license
 * from BladeX.
 * <p>
 * 2. Redistribution of this software's source code to any third party
 * without a commercial license is strictly prohibited.
 * <p>
 * 3. Licensees may copyright their own code but cannot use segments
 * from this software for such purposes. Copyright of this software
 * remains with BladeX.
 * <p>
 * Using this software signifies agreement to this License, and the software
 * must not be used for illegal purposes.
 * <p>
 * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
 * not liable for any claims arising from secondary or illegal development.
 * <p>
 * Author: Chill Zhuang (bladejava@qq.com)
 */
package org.springblade.transport.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.transport.excel.CreditScoreQuantificationExcel;
import org.springblade.transport.mapper.CreditRatingStandardMapper;
import org.springblade.transport.mapper.CreditScoreCategoryMapper;
import org.springblade.transport.mapper.CreditScoreItemMapper;
import org.springblade.transport.mapper.CreditScoreItemOptionMapper;
import org.springblade.transport.mapper.CreditScoreQuantificationMapper;
import org.springblade.transport.pojo.entity.CreditRatingStandard;
import org.springblade.transport.pojo.entity.CreditScoreCategory;
import org.springblade.transport.pojo.entity.CreditScoreItem;
import org.springblade.transport.pojo.entity.CreditScoreItemOption;
import org.springblade.transport.pojo.entity.CreditScoreQuantification;
import org.springblade.transport.pojo.vo.CreditRatingStandardVO;
import org.springblade.transport.pojo.vo.CreditScoreCategoryVO;
import org.springblade.transport.pojo.vo.CreditScoreItemOptionVO;
import org.springblade.transport.pojo.vo.CreditScoreItemVO;
import org.springblade.transport.pojo.vo.CreditScoreQuantificationVO;
import org.springblade.transport.service.ICreditScoreQuantificationService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
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.stream.Collectors;

/**
 * 评分量化表 服务实现类
 *
 * @author Chill
 */
@Service
@RequiredArgsConstructor
public class CreditScoreQuantificationServiceImpl extends BaseServiceImpl<CreditScoreQuantificationMapper, CreditScoreQuantification> implements ICreditScoreQuantificationService {

	private static final int STATUS_NORMAL = 1;
	private static final int STATUS_DISABLED = 2;
	private static final int STATUS_DRAFT = 3;
	private static final int NAME_MAX_LENGTH = 30;
	private static final int REMARK_MAX_LENGTH = 500;
	private static final int ITEM_NAME_MAX_LENGTH = 20;
	private static final int SCORE_DESCRIPTION_MAX_LENGTH = 300;
	private static final int OPTION_NAME_MAX_LENGTH = 100;
	private static final int STANDARD_DESCRIPTION_MAX_LENGTH = 500;
	private static final String CATEGORY_BASIC = "basic";
	private static final String CATEGORY_PLUS = "plus";
	private static final String CATEGORY_MINUS = "minus";
	private static final String ROW_TYPE_MAIN = "主表";
	private static final String ROW_TYPE_ITEM = "评分项目";
	private static final String ROW_TYPE_OPTION = "选项";
	private static final String ROW_TYPE_STANDARD = "评估标准";

	private final CreditScoreCategoryMapper categoryMapper;
	private final CreditScoreItemMapper itemMapper;
	private final CreditScoreItemOptionMapper optionMapper;
	private final CreditRatingStandardMapper standardMapper;

	@Override
	public IPage<CreditScoreQuantificationVO> selectCreditScoreQuantificationPage(IPage<CreditScoreQuantificationVO> page, CreditScoreQuantificationVO quantification) {
		return page.setRecords(baseMapper.selectCreditScoreQuantificationPage(page, quantification));
	}

	@Override
	public CreditScoreQuantificationVO detail(Long id) {
		if (Func.isEmpty(id)) {
			throw new ServiceException("主键不能为空");
		}
		CreditScoreQuantification quantification = getById(id);
		if (Func.isEmpty(quantification) || Objects.equals(quantification.getIsDeleted(), 1)) {
			throw new ServiceException("评分量化表不存在");
		}
		CreditScoreQuantificationVO detail = Objects.requireNonNull(BeanUtil.copyProperties(quantification, CreditScoreQuantificationVO.class));
		detail.setCategories(loadCategories(id));
		detail.setStandards(loadStandards(id));
		return detail;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submit(CreditScoreQuantificationVO quantification) {
		prepare(quantification);
		validateBase(quantification);
		boolean fullValidate = Objects.equals(quantification.getStatus(), STATUS_NORMAL);
		validateDetail(quantification, fullValidate);
		CreditScoreQuantification entity = Objects.requireNonNull(BeanUtil.copyProperties(quantification, CreditScoreQuantification.class));
		if (Func.isEmpty(entity.getStatus())) {
			entity.setStatus(STATUS_DRAFT);
		}
		boolean result = saveOrUpdate(entity);
		replaceDetail(entity.getId(), quantification);
		return result;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean publish(Long id) {
		CreditScoreQuantificationVO detail = detail(id);
		detail.setStatus(STATUS_NORMAL);
		validateDetail(detail, true);
		CreditScoreQuantification update = new CreditScoreQuantification();
		update.setId(id);
		update.setStatus(STATUS_NORMAL);
		return updateById(update);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean changeStatus(Long id, Integer status) {
		if (Func.isEmpty(id)) {
			throw new ServiceException("主键不能为空");
		}
		if (!Objects.equals(status, STATUS_NORMAL) && !Objects.equals(status, STATUS_DISABLED)) {
			throw new ServiceException("启停状态不正确");
		}
		CreditScoreQuantification detail = getById(id);
		if (Func.isEmpty(detail) || Objects.equals(detail.getIsDeleted(), 1)) {
			throw new ServiceException("评分量化表不存在");
		}
		if (Objects.equals(detail.getStatus(), STATUS_DRAFT)) {
			throw new ServiceException("草稿量表请先发布");
		}
		if (Objects.equals(status, STATUS_NORMAL)) {
			validateDetail(detail(id), true);
		}
		CreditScoreQuantification update = new CreditScoreQuantification();
		update.setId(id);
		update.setStatus(status);
		return updateById(update);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean removeDraft(String ids) {
		List<Long> idList = Func.toLongList(ids);
		if (Func.isEmpty(idList)) {
			throw new ServiceException("请选择需要删除的数据");
		}
		List<CreditScoreQuantification> list = listByIds(idList);
		if (list.size() != idList.size()) {
			throw new ServiceException("评分量化表不存在");
		}
		deleteDetail(idList);
		return deleteLogic(idList);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public List<CreditScoreQuantificationExcel> importCreditScoreQuantification(List<CreditScoreQuantificationExcel> data) {
		if (Func.isEmpty(data)) {
			throw new ServiceException("导入数据不能为空");
		}
		Map<String, List<CreditScoreQuantificationExcel>> dataMap = data.stream()
			.filter(row -> Func.isNotEmpty(trimToNull(row.getTableName())))
			.collect(Collectors.groupingBy(row -> trimToEmpty(row.getTableName()), java.util.LinkedHashMap::new, Collectors.toList()));
		if (Func.isEmpty(dataMap)) {
			data.forEach(row -> row.setErrorMessage("评定表名称不能为空"));
			return data;
		}
		List<CreditScoreQuantificationExcel> errorList = new ArrayList<>();
		dataMap.forEach((tableName, rows) -> {
			try {
				submit(buildImportVO(tableName, rows));
			} catch (Exception exception) {
				rows.forEach(row -> row.setErrorMessage(tableName + "：" + exception.getMessage()));
				errorList.addAll(rows);
			}
		});
		return errorList;
	}

	@Override
	public List<CreditScoreQuantificationExcel> exportCreditScoreQuantification(Wrapper<CreditScoreQuantification> queryWrapper) {
		List<CreditScoreQuantificationExcel> excelList = new ArrayList<>();
		List<CreditScoreQuantification> quantificationList = list(queryWrapper);
		for (CreditScoreQuantification quantification : quantificationList) {
			CreditScoreQuantificationVO detail = detail(quantification.getId());
			excelList.add(buildMainExcel(detail));
			for (CreditScoreCategoryVO category : detail.getCategories()) {
				for (CreditScoreItemVO item : category.getItems()) {
					excelList.add(buildItemExcel(detail, category, item));
					for (CreditScoreItemOptionVO option : item.getOptions()) {
						excelList.add(buildOptionExcel(detail, category, item, option));
					}
				}
			}
			for (CreditRatingStandardVO standard : detail.getStandards()) {
				excelList.add(buildStandardExcel(detail, standard));
			}
		}
		return excelList;
	}

	private List<CreditScoreCategoryVO> loadCategories(Long quantificationId) {
		List<CreditScoreCategory> categories = categoryMapper.selectList(Wrappers.<CreditScoreCategory>lambdaQuery()
			.eq(CreditScoreCategory::getQuantificationId, quantificationId)
			.eq(CreditScoreCategory::getIsDeleted, 0)
			.orderByAsc(CreditScoreCategory::getSort));
		if (Func.isEmpty(categories)) {
			return defaultCategories();
		}
		List<CreditScoreItem> items = itemMapper.selectList(Wrappers.<CreditScoreItem>lambdaQuery()
			.eq(CreditScoreItem::getQuantificationId, quantificationId)
			.eq(CreditScoreItem::getIsDeleted, 0)
			.orderByAsc(CreditScoreItem::getSort));
		List<CreditScoreItemOption> options = optionMapper.selectList(Wrappers.<CreditScoreItemOption>lambdaQuery()
			.eq(CreditScoreItemOption::getQuantificationId, quantificationId)
			.eq(CreditScoreItemOption::getIsDeleted, 0)
			.orderByAsc(CreditScoreItemOption::getSort));
		Map<Long, List<CreditScoreItemOptionVO>> optionMap = options.stream()
			.map(option -> Objects.requireNonNull(BeanUtil.copyProperties(option, CreditScoreItemOptionVO.class)))
			.collect(Collectors.groupingBy(CreditScoreItemOptionVO::getItemId));
		Map<Long, List<CreditScoreItemVO>> itemMap = items.stream().map(item -> {
			CreditScoreItemVO itemVO = Objects.requireNonNull(BeanUtil.copyProperties(item, CreditScoreItemVO.class));
			itemVO.setOptions(optionMap.getOrDefault(item.getId(), new ArrayList<>()));
			return itemVO;
		}).collect(Collectors.groupingBy(CreditScoreItemVO::getCategoryId));
		return categories.stream().map(category -> {
			CreditScoreCategoryVO categoryVO = Objects.requireNonNull(BeanUtil.copyProperties(category, CreditScoreCategoryVO.class));
			categoryVO.setItems(itemMap.getOrDefault(category.getId(), new ArrayList<>()));
			return categoryVO;
		}).toList();
	}

	private List<CreditRatingStandardVO> loadStandards(Long quantificationId) {
		return standardMapper.selectList(Wrappers.<CreditRatingStandard>lambdaQuery()
				.eq(CreditRatingStandard::getQuantificationId, quantificationId)
				.eq(CreditRatingStandard::getIsDeleted, 0)
				.orderByDesc(CreditRatingStandard::getScoreRateLower)
				.orderByAsc(CreditRatingStandard::getSort))
			.stream()
			.map(standard -> Objects.requireNonNull(BeanUtil.copyProperties(standard, CreditRatingStandardVO.class)))
			.toList();
	}

	private CreditScoreQuantificationVO buildImportVO(String tableName, List<CreditScoreQuantificationExcel> rows) {
		CreditScoreQuantificationVO quantification = new CreditScoreQuantificationVO();
		CreditScoreQuantification existed = getOne(Wrappers.<CreditScoreQuantification>lambdaQuery()
			.eq(CreditScoreQuantification::getName, tableName)
			.eq(CreditScoreQuantification::getIsDeleted, 0), false);
		if (Func.isNotEmpty(existed)) {
			quantification.setId(existed.getId());
		}
		quantification.setName(tableName);
		quantification.setStatus(STATUS_DRAFT);
		quantification.setCategories(defaultCategories());
		quantification.setStandards(new ArrayList<>());
		Map<String, CreditScoreItemVO> itemMap = new HashMap<>();
		for (CreditScoreQuantificationExcel row : rows) {
			String rowType = trimToEmpty(row.getRowType());
			if (Func.isEmpty(rowType) || ROW_TYPE_MAIN.equals(rowType)) {
				quantification.setRemark(trimToNull(row.getRemark()));
				quantification.setStandardDescription(trimToNull(row.getTableStandardDescription()));
				quantification.setStatus(parseStatus(row.getStatusName()));
			} else if (ROW_TYPE_ITEM.equals(rowType)) {
				CreditScoreCategoryVO category = findCategory(quantification.getCategories(), row.getCategoryName());
				CreditScoreItemVO item = new CreditScoreItemVO();
				item.setItemName(trimToEmpty(row.getItemName()));
				item.setScore(row.getScore());
				item.setScoreDescription(trimToNull(row.getScoreDescription()));
				item.setOptionDescription(trimToNull(row.getOptionDescription()));
				item.setOptions(new ArrayList<>());
				category.getItems().add(item);
				itemMap.put(itemKey(category.getCategoryName(), item.getItemName()), item);
			} else if (ROW_TYPE_OPTION.equals(rowType)) {
				CreditScoreCategoryVO category = findCategory(quantification.getCategories(), row.getCategoryName());
				CreditScoreItemVO item = findOrCreateItem(category, row, itemMap);
				CreditScoreItemOptionVO option = new CreditScoreItemOptionVO();
				option.setOptionName(trimToEmpty(row.getOptionName()));
				option.setScore(row.getScore());
				item.getOptions().add(option);
			} else if (ROW_TYPE_STANDARD.equals(rowType)) {
				CreditRatingStandardVO standard = new CreditRatingStandardVO();
				standard.setCreditLevel(trimToEmpty(row.getCreditLevel()));
				standard.setScoreRateLower(row.getScoreRateLower());
				standard.setScoreRateUpper(row.getScoreRateUpper());
				standard.setScoreRateIncrease(row.getScoreRateIncrease());
				standard.setCreditLimitLower(row.getCreditLimitLower());
				standard.setCreditLimitUpper(row.getCreditLimitUpper());
				standard.setCreditLimitIncrease(row.getCreditLimitIncrease());
				standard.setStandardDescription(trimToNull(row.getRatingStandardDescription()));
				quantification.getStandards().add(standard);
			} else {
				throw new ServiceException("数据类型仅支持主表、评分项目、选项、评估标准");
			}
		}
		return quantification;
	}

	private CreditScoreCategoryVO findCategory(List<CreditScoreCategoryVO> categories, String categoryName) {
		String trimCategoryName = trimToEmpty(categoryName);
		for (CreditScoreCategoryVO category : categories) {
			if (Objects.equals(category.getCategoryName(), trimCategoryName) || Objects.equals(category.getCategoryCode(), trimCategoryName)) {
				return category;
			}
		}
		throw new ServiceException("评分分类仅支持基础得分项、加分项目、减分项目");
	}

	private CreditScoreItemVO findOrCreateItem(CreditScoreCategoryVO category, CreditScoreQuantificationExcel row, Map<String, CreditScoreItemVO> itemMap) {
		String itemName = trimToEmpty(row.getItemName());
		String key = itemKey(category.getCategoryName(), itemName);
		CreditScoreItemVO item = itemMap.get(key);
		if (Func.isNotEmpty(item)) {
			return item;
		}
		item = new CreditScoreItemVO();
		item.setItemName(itemName);
		item.setScore(row.getScore());
		item.setScoreDescription(trimToNull(row.getScoreDescription()));
		item.setOptionDescription(trimToNull(row.getOptionDescription()));
		item.setOptions(new ArrayList<>());
		category.getItems().add(item);
		itemMap.put(key, item);
		return item;
	}

	private String itemKey(String categoryName, String itemName) {
		return trimToEmpty(categoryName) + "#" + trimToEmpty(itemName);
	}

	private Integer parseStatus(String statusName) {
		String trimStatusName = trimToNull(statusName);
		if (Func.isEmpty(trimStatusName) || "草稿".equals(trimStatusName)) {
			return STATUS_DRAFT;
		}
		if ("正常".equals(trimStatusName) || "发布".equals(trimStatusName)) {
			return STATUS_NORMAL;
		}
		if ("停用".equals(trimStatusName)) {
			return STATUS_DISABLED;
		}
		throw new ServiceException("状态仅支持正常、停用、草稿");
	}

	private CreditScoreQuantificationExcel buildMainExcel(CreditScoreQuantificationVO detail) {
		CreditScoreQuantificationExcel excel = new CreditScoreQuantificationExcel();
		excel.setTableName(detail.getName());
		excel.setRowType(ROW_TYPE_MAIN);
		excel.setRemark(detail.getRemark());
		excel.setTableStandardDescription(detail.getStandardDescription());
		excel.setStatusName(statusName(detail.getStatus()));
		return excel;
	}

	private CreditScoreQuantificationExcel buildItemExcel(CreditScoreQuantificationVO detail, CreditScoreCategoryVO category, CreditScoreItemVO item) {
		CreditScoreQuantificationExcel excel = new CreditScoreQuantificationExcel();
		excel.setTableName(detail.getName());
		excel.setRowType(ROW_TYPE_ITEM);
		excel.setCategoryName(category.getCategoryName());
		excel.setItemName(item.getItemName());
		excel.setScore(item.getScore());
		excel.setScoreDescription(item.getScoreDescription());
		excel.setOptionDescription(item.getOptionDescription());
		return excel;
	}

	private CreditScoreQuantificationExcel buildOptionExcel(CreditScoreQuantificationVO detail, CreditScoreCategoryVO category, CreditScoreItemVO item, CreditScoreItemOptionVO option) {
		CreditScoreQuantificationExcel excel = buildItemExcel(detail, category, item);
		excel.setRowType(ROW_TYPE_OPTION);
		excel.setOptionName(option.getOptionName());
		excel.setScore(option.getScore());
		return excel;
	}

	private CreditScoreQuantificationExcel buildStandardExcel(CreditScoreQuantificationVO detail, CreditRatingStandardVO standard) {
		CreditScoreQuantificationExcel excel = new CreditScoreQuantificationExcel();
		excel.setTableName(detail.getName());
		excel.setRowType(ROW_TYPE_STANDARD);
		excel.setCreditLevel(standard.getCreditLevel());
		excel.setScoreRateLower(standard.getScoreRateLower());
		excel.setScoreRateUpper(standard.getScoreRateUpper());
		excel.setScoreRateIncrease(standard.getScoreRateIncrease());
		excel.setCreditLimitLower(standard.getCreditLimitLower());
		excel.setCreditLimitUpper(standard.getCreditLimitUpper());
		excel.setCreditLimitIncrease(standard.getCreditLimitIncrease());
		excel.setRatingStandardDescription(standard.getStandardDescription());
		return excel;
	}

	private String statusName(Integer status) {
		if (Objects.equals(status, STATUS_NORMAL)) {
			return "正常";
		}
		if (Objects.equals(status, STATUS_DISABLED)) {
			return "停用";
		}
		return "草稿";
	}

	private void prepare(CreditScoreQuantificationVO quantification) {
		quantification.setName(trimToEmpty(quantification.getName()));
		quantification.setRemark(trimToNull(quantification.getRemark()));
		quantification.setStandardDescription(trimToNull(quantification.getStandardDescription()));
		if (Func.isEmpty(quantification.getStatus())) {
			quantification.setStatus(STATUS_DRAFT);
		}
		quantification.setCategories(normalizeCategories(quantification.getCategories()));
		quantification.setStandards(quantification.getStandards() == null ? new ArrayList<>() : quantification.getStandards());
	}

	private void validateBase(CreditScoreQuantificationVO quantification) {
		if (Func.isEmpty(quantification.getName())) {
			throw new ServiceException("评定表名称不能为空");
		}
		validateLength(quantification.getName(), NAME_MAX_LENGTH, "评定表名称最多30字符");
		validateLength(quantification.getRemark(), REMARK_MAX_LENGTH, "备注最多500字符");
		validateLength(quantification.getStandardDescription(), REMARK_MAX_LENGTH, "标准说明最多500字符");
		LambdaQueryWrapper<CreditScoreQuantification> queryWrapper = Wrappers.<CreditScoreQuantification>lambdaQuery()
			.eq(CreditScoreQuantification::getName, quantification.getName())
			.eq(CreditScoreQuantification::getIsDeleted, 0);
		if (Func.isNotEmpty(quantification.getId())) {
			queryWrapper.ne(CreditScoreQuantification::getId, quantification.getId());
		}
		if (count(queryWrapper) > 0L) {
			throw new ServiceException("评定表名称已存在");
		}
	}

	private void validateDetail(CreditScoreQuantificationVO quantification, boolean fullValidate) {
		Map<String, List<CreditScoreItemVO>> itemMap = new HashMap<>();
		for (CreditScoreCategoryVO category : quantification.getCategories()) {
			List<CreditScoreItemVO> items = category.getItems() == null ? new ArrayList<>() : category.getItems();
			validateItems(category, items, fullValidate);
			itemMap.put(category.getCategoryCode(), items);
		}
		if (fullValidate && Func.isEmpty(itemMap.get(CATEGORY_BASIC))) {
			throw new ServiceException("发布前至少配置1个基础得分项目");
		}
		validateStandards(quantification.getStandards(), fullValidate);
	}

	private void validateItems(CreditScoreCategoryVO category, List<CreditScoreItemVO> items, boolean fullValidate) {
		Set<String> itemNames = new HashSet<>();
		for (CreditScoreItemVO item : items) {
			item.setItemName(trimToEmpty(item.getItemName()));
			item.setScoreDescription(trimToNull(item.getScoreDescription()));
			item.setOptionDescription(trimToNull(item.getOptionDescription()));
			if (Func.isEmpty(item.getItemName())) {
				throw new ServiceException(category.getCategoryName() + "评分项目不能为空");
			}
			validateLength(item.getItemName(), ITEM_NAME_MAX_LENGTH, "评分项目最多20汉字");
			validateLength(item.getScoreDescription(), SCORE_DESCRIPTION_MAX_LENGTH, "得分说明最多300字符");
			validateLength(item.getOptionDescription(), OPTION_NAME_MAX_LENGTH, "选项描述最多100字符");
			if (Func.isEmpty(item.getScore())) {
				throw new ServiceException(item.getItemName() + "分值不能为空");
			}
			validateNonNegative(item.getScore(), item.getItemName() + "分值不能小于0");
			if (!itemNames.add(item.getItemName())) {
				throw new ServiceException(category.getCategoryName() + "下评分项目名称不能重复");
			}
			List<CreditScoreItemOptionVO> options = item.getOptions() == null ? new ArrayList<>() : item.getOptions();
			if (fullValidate && Func.isEmpty(options)) {
				throw new ServiceException(item.getItemName() + "至少存在1条档位选项");
			}
			for (CreditScoreItemOptionVO option : options) {
				option.setOptionName(trimToEmpty(option.getOptionName()));
				if (Func.isEmpty(option.getOptionName())) {
					throw new ServiceException(item.getItemName() + "选项描述不能为空");
				}
				validateLength(option.getOptionName(), OPTION_NAME_MAX_LENGTH, "选项描述最多100字符");
				if (Func.isEmpty(option.getScore())) {
					throw new ServiceException(item.getItemName() + "分值不能为空");
				}
				if (option.getScore().compareTo(BigDecimal.ZERO) < 0) {
					throw new ServiceException(item.getItemName() + "分值不能小于0");
				}
			}
		}
	}

	private void validateStandards(List<CreditRatingStandardVO> standards, boolean fullValidate) {
		if (!fullValidate && Func.isEmpty(standards)) {
			return;
		}
		if (Func.isEmpty(standards)) {
			throw new ServiceException("发布前至少配置1条完整信用等级评估标准");
		}
		Set<String> creditLevels = new HashSet<>();
		List<CreditRatingStandardVO> sortedStandards = standards.stream()
			.sorted(Comparator.comparing(CreditRatingStandardVO::getScoreRateLower))
			.toList();
		Integer previousUpper = null;
		for (CreditRatingStandardVO standard : sortedStandards) {
			prepareStandard(standard);
			if (!creditLevels.add(standard.getCreditLevel())) {
				throw new ServiceException("信用等级不能重复");
			}
			if (standard.getScoreRateLower() < 0 || standard.getScoreRateUpper() > 100) {
				throw new ServiceException("得分率区间必须在0到100之间");
			}
			if (standard.getScoreRateLower() >= standard.getScoreRateUpper()) {
				throw new ServiceException("得分率下限必须小于上限");
			}
			if (previousUpper != null && standard.getScoreRateLower() < previousUpper) {
				throw new ServiceException("信用等级得分率区间不能重叠");
			}
			previousUpper = standard.getScoreRateUpper();
			validateNonNegative(standard.getCreditLimitLower(), "最大资金使用额度下限不能小于0");
			validateNonNegative(standard.getCreditLimitUpper(), "最大资金使用额度上限不能小于0");
			validateNonNegative(standard.getCreditLimitIncrease(), "额度增加不能小于0");
			if (standard.getCreditLimitUpper().compareTo(standard.getCreditLimitLower()) < 0) {
				throw new ServiceException("最大资金使用额度上限不能小于下限");
			}
			validateLength(standard.getStandardDescription(), STANDARD_DESCRIPTION_MAX_LENGTH, "标准说明最多500字符");
		}
	}

	private void prepareStandard(CreditRatingStandardVO standard) {
		standard.setCreditLevel(trimToEmpty(standard.getCreditLevel()));
		standard.setStandardDescription(trimToNull(standard.getStandardDescription()));
		if (Func.isEmpty(standard.getCreditLevel())) {
			throw new ServiceException("信用等级不能为空");
		}
		validateLength(standard.getCreditLevel(), NAME_MAX_LENGTH, "信用等级最多30字符");
		if (Func.isEmpty(standard.getScoreRateLower()) || Func.isEmpty(standard.getScoreRateUpper())) {
			throw new ServiceException("得分率上下限不能为空");
		}
		if (Func.isEmpty(standard.getScoreRateIncrease()) || standard.getScoreRateIncrease() < 0) {
			throw new ServiceException("得分率每增加必须为大于等于0的整数");
		}
		if (Func.isEmpty(standard.getCreditLimitLower())) {
			standard.setCreditLimitLower(BigDecimal.ZERO);
		}
		if (Func.isEmpty(standard.getCreditLimitUpper())) {
			standard.setCreditLimitUpper(BigDecimal.ZERO);
		}
		if (Func.isEmpty(standard.getCreditLimitIncrease())) {
			standard.setCreditLimitIncrease(BigDecimal.ZERO);
		}
	}

	private void replaceDetail(Long quantificationId, CreditScoreQuantificationVO quantification) {
		deleteDetail(List.of(quantificationId));
		Map<String, Long> categoryIdMap = new HashMap<>();
		for (CreditScoreCategoryVO categoryVO : quantification.getCategories()) {
			Long categoryId = IdWorker.getId();
			categoryIdMap.put(categoryVO.getCategoryCode(), categoryId);
			CreditScoreCategory category = Objects.requireNonNull(BeanUtil.copyProperties(categoryVO, CreditScoreCategory.class));
			category.setId(categoryId);
			category.setQuantificationId(quantificationId);
			category.setStatus(STATUS_NORMAL);
			categoryMapper.insert(category);
		}
		for (CreditScoreCategoryVO categoryVO : quantification.getCategories()) {
			List<CreditScoreItemVO> items = categoryVO.getItems() == null ? new ArrayList<>() : categoryVO.getItems();
			for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
				CreditScoreItemVO itemVO = items.get(itemIndex);
				Long itemId = IdWorker.getId();
				CreditScoreItem item = Objects.requireNonNull(BeanUtil.copyProperties(itemVO, CreditScoreItem.class));
				item.setId(itemId);
				item.setQuantificationId(quantificationId);
				item.setCategoryId(categoryIdMap.get(categoryVO.getCategoryCode()));
				item.setCategoryCode(categoryVO.getCategoryCode());
				item.setSort(itemIndex + 1);
				item.setStatus(STATUS_NORMAL);
				itemMapper.insert(item);
				insertOptions(quantificationId, itemId, itemVO);
			}
		}
		insertStandards(quantificationId, quantification.getStandards());
	}

	private void insertOptions(Long quantificationId, Long itemId, CreditScoreItemVO itemVO) {
		List<CreditScoreItemOptionVO> options = itemVO.getOptions() == null ? new ArrayList<>() : itemVO.getOptions();
		for (int optionIndex = 0; optionIndex < options.size(); optionIndex++) {
			CreditScoreItemOption option = Objects.requireNonNull(BeanUtil.copyProperties(options.get(optionIndex), CreditScoreItemOption.class));
			option.setId(IdWorker.getId());
			option.setQuantificationId(quantificationId);
			option.setItemId(itemId);
			option.setSort(optionIndex + 1);
			option.setStatus(STATUS_NORMAL);
			optionMapper.insert(option);
		}
	}

	private void insertStandards(Long quantificationId, List<CreditRatingStandardVO> standards) {
		if (Func.isEmpty(standards)) {
			return;
		}
		List<CreditRatingStandardVO> sortedStandards = standards.stream()
			.sorted(Comparator.comparing(CreditRatingStandardVO::getScoreRateLower).reversed())
			.toList();
		for (int index = 0; index < sortedStandards.size(); index++) {
			CreditRatingStandard standard = Objects.requireNonNull(BeanUtil.copyProperties(sortedStandards.get(index), CreditRatingStandard.class));
			standard.setId(IdWorker.getId());
			standard.setQuantificationId(quantificationId);
			standard.setSort(index + 1);
			standard.setStatus(STATUS_NORMAL);
			standardMapper.insert(standard);
		}
	}

	private void deleteDetail(List<Long> quantificationIds) {
		if (Func.isEmpty(quantificationIds)) {
			return;
		}
		categoryMapper.update(null, Wrappers.<CreditScoreCategory>lambdaUpdate()
			.set(CreditScoreCategory::getIsDeleted, 1)
			.in(CreditScoreCategory::getQuantificationId, quantificationIds));
		itemMapper.update(null, Wrappers.<CreditScoreItem>lambdaUpdate()
			.set(CreditScoreItem::getIsDeleted, 1)
			.in(CreditScoreItem::getQuantificationId, quantificationIds));
		optionMapper.update(null, Wrappers.<CreditScoreItemOption>lambdaUpdate()
			.set(CreditScoreItemOption::getIsDeleted, 1)
			.in(CreditScoreItemOption::getQuantificationId, quantificationIds));
		standardMapper.update(null, Wrappers.<CreditRatingStandard>lambdaUpdate()
			.set(CreditRatingStandard::getIsDeleted, 1)
			.in(CreditRatingStandard::getQuantificationId, quantificationIds));
	}

	private List<CreditScoreCategoryVO> normalizeCategories(List<CreditScoreCategoryVO> categories) {
		Map<String, CreditScoreCategoryVO> categoryMap = categories == null ? new HashMap<>() : categories.stream()
			.filter(category -> Func.isNotEmpty(category.getCategoryCode()))
			.collect(Collectors.toMap(CreditScoreCategoryVO::getCategoryCode, category -> category, (first, second) -> first));
		List<CreditScoreCategoryVO> normalized = new ArrayList<>();
		normalized.add(normalizeCategory(categoryMap.get(CATEGORY_BASIC), CATEGORY_BASIC, "基础得分项", 1));
		normalized.add(normalizeCategory(categoryMap.get(CATEGORY_PLUS), CATEGORY_PLUS, "加分项目", 2));
		normalized.add(normalizeCategory(categoryMap.get(CATEGORY_MINUS), CATEGORY_MINUS, "减分项目", 3));
		return normalized;
	}

	private CreditScoreCategoryVO normalizeCategory(CreditScoreCategoryVO category, String code, String name, Integer sort) {
		CreditScoreCategoryVO normalized = category == null ? new CreditScoreCategoryVO() : category;
		normalized.setCategoryCode(code);
		normalized.setCategoryName(name);
		normalized.setSort(sort);
		if (normalized.getItems() == null) {
			normalized.setItems(new ArrayList<>());
		}
		return normalized;
	}

	private List<CreditScoreCategoryVO> defaultCategories() {
		return normalizeCategories(new ArrayList<>());
	}

	private void validateLength(String value, int maxLength, String message) {
		if (Func.isNotEmpty(value) && value.length() > maxLength) {
			throw new ServiceException(message);
		}
	}

	private void validateNonNegative(BigDecimal value, String message) {
		if (Func.isNotEmpty(value) && value.compareTo(BigDecimal.ZERO) < 0) {
			throw new ServiceException(message);
		}
	}

	private String trimToEmpty(String value) {
		return value == null ? "" : value.trim();
	}

	private String trimToNull(String value) {
		String trimValue = trimToEmpty(value);
		return trimValue.isEmpty() ? null : trimValue;
	}

}
