/**
 * 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>
 * Author: Chill Zhuang (bladejava@qq.com)
 */
package org.springblade.transport.service.impl;

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.secure.utils.AuthUtil;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.transport.excel.CustomerArchiveExcel;
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.mapper.CustomerArchiveMapper;
import org.springblade.transport.mapper.CustomerChangeRecordMapper;
import org.springblade.transport.mapper.CustomerContactMapper;
import org.springblade.transport.mapper.CustomerCreditScoreDetailMapper;
import org.springblade.transport.mapper.CustomerCreditScoreMapper;
import org.springblade.transport.mapper.CustomerInvoiceInfoMapper;
import org.springblade.transport.mapper.CustomerReceiptAccountMapper;
import org.springblade.transport.mapper.UserCustomerScopeMapper;
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.entity.CustomerArchive;
import org.springblade.transport.pojo.entity.CustomerChangeRecord;
import org.springblade.transport.pojo.entity.CustomerContact;
import org.springblade.transport.pojo.entity.CustomerCreditScore;
import org.springblade.transport.pojo.entity.CustomerCreditScoreDetail;
import org.springblade.transport.pojo.entity.CustomerInvoiceInfo;
import org.springblade.transport.pojo.entity.CustomerReceiptAccount;
import org.springblade.transport.pojo.vo.CustomerArchiveVO;
import org.springblade.transport.pojo.vo.CustomerChangeRecordVO;
import org.springblade.transport.pojo.vo.CustomerContactVO;
import org.springblade.transport.pojo.vo.CustomerCreditScoreDetailVO;
import org.springblade.transport.pojo.vo.CustomerCreditScoreVO;
import org.springblade.transport.pojo.vo.CreditRatingStandardVO;
import org.springblade.transport.pojo.vo.CustomerInvoiceInfoVO;
import org.springblade.transport.pojo.vo.CustomerReceiptAccountVO;
import org.springblade.transport.service.ICustomerArchiveService;
import org.springblade.system.pojo.entity.UserCustomerScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 客商档案 服务实现类
 *
 * @author Chill
 */
@Service
@RequiredArgsConstructor
public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveMapper, CustomerArchive> implements ICustomerArchiveService {

	private static final int STATUS_ENABLED = 1;
	private static final int STATUS_DISABLED = 2;
	private static final String ACCESS_TEMPORARY = "temporary";
	private static final String ACCESS_FORMAL = "formal";
	private static final String APPROVAL_DRAFT = "draft";
	private static final String APPROVAL_REVIEWING = "reviewing";
	private static final String APPROVAL_APPROVED = "approved";
	private static final String APPROVAL_REJECTED = "rejected";
	private static final String CUSTOMER_CODE_PREFIX = "KS";

	private final CustomerContactMapper contactMapper;
	private final CustomerReceiptAccountMapper receiptAccountMapper;
	private final CustomerInvoiceInfoMapper invoiceInfoMapper;
	private final CustomerCreditScoreMapper creditScoreMapper;
	private final CustomerCreditScoreDetailMapper creditScoreDetailMapper;
	private final CustomerChangeRecordMapper changeRecordMapper;
	private final CreditScoreQuantificationMapper quantificationMapper;
	private final CreditScoreCategoryMapper categoryMapper;
	private final CreditScoreItemMapper itemMapper;
	private final CreditScoreItemOptionMapper optionMapper;
	private final CreditRatingStandardMapper standardMapper;
	private final UserCustomerScopeMapper userCustomerScopeMapper;

	@Override
	public IPage<CustomerArchiveVO> selectCustomerArchivePage(IPage<CustomerArchiveVO> page, CustomerArchiveVO customer) {
		return page.setRecords(baseMapper.selectCustomerArchivePage(page, customer, AuthUtil.getUserId()));
	}

	@Override
	public CustomerArchiveVO detail(Long id) {
		if (Func.isEmpty(id)) {
			throw new ServiceException("主键不能为空");
		}
		CustomerArchive customer = getById(id);
		if (Func.isEmpty(customer) || Objects.equals(customer.getIsDeleted(), 1)) {
			throw new ServiceException("客商档案不存在");
		}
		if (baseMapper.selectCustomerPermissionCount(id, AuthUtil.getUserId()) == 0) {
			throw new ServiceException("无权访问该客商档案");
		}
		CustomerArchiveVO detail = Objects.requireNonNull(BeanUtil.copyProperties(customer, CustomerArchiveVO.class));
		detail.setContacts(loadContacts(id));
		detail.setReceiptAccounts(loadReceiptAccounts(id));
		detail.setInvoices(loadInvoices(id));
		detail.setScores(loadScores(id));
		detail.setChangeRecords(loadChangeRecords(id));
		return detail;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submit(CustomerArchiveVO customer) {
		prepare(customer);
		validateBase(customer);
		boolean created = Func.isEmpty(customer.getId());
		if (created) {
			customer.setCustomerCode(nextCustomerCode());
		}
		CustomerArchive entity = Objects.requireNonNull(BeanUtil.copyProperties(customer, CustomerArchive.class));
		boolean result = saveOrUpdate(entity);
		customer.setId(entity.getId());
		replaceDetail(entity.getId(), customer);
		if (created && result) {
			grantNewCustomerToIncludedUsers(entity);
		}
		addChangeRecord(entity.getId(), created ? "新增客商档案" : "修改客商档案");
		return result;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean submitApproval(Long id) {
		CustomerArchiveVO detail = detail(id);
		prepare(detail);
		validateBase(detail);
		validateApproval(detail);
		CustomerArchive update = new CustomerArchive();
		update.setId(id);
		update.setApprovalStatus(APPROVAL_REVIEWING);
		update.setCurrentNode("客商准入审批");
		update.setCurrentProcessor("待处理");
		addChangeRecord(id, "提交客商准入审批");
		return updateById(update);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean withdrawApproval(Long id) {
		CustomerArchive customer = getById(id);
		if (Func.isEmpty(customer) || Objects.equals(customer.getIsDeleted(), 1)) {
			throw new ServiceException("客商档案不存在");
		}
		if (!List.of(APPROVAL_DRAFT, APPROVAL_REVIEWING).contains(customer.getApprovalStatus())) {
			throw new ServiceException("仅未审核或审核中状态客商可撤回");
		}
		CustomerArchive update = new CustomerArchive();
		update.setId(id);
		update.setApprovalStatus(APPROVAL_DRAFT);
		update.setCurrentNode("草稿");
		update.setCurrentProcessor(AuthUtil.getUserName());
		addChangeRecord(id, "撤回客商准入审批");
		return updateById(update);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean approve(Long id) {
		CustomerArchiveVO detail = detail(id);
		prepare(detail);
		validateBase(detail);
		validateApproval(detail);
		CustomerArchive update = new CustomerArchive();
		update.setId(id);
		update.setAccessType(ACCESS_FORMAL);
		update.setApprovalStatus(APPROVAL_APPROVED);
		update.setCurrentNode("审核通过");
		update.setCurrentProcessor(AuthUtil.getUserName());
		update.setApprovedTime(LocalDateTime.now());
		addChangeRecord(id, "客商准入审核通过");
		return updateById(update);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean reject(Long id) {
		CustomerArchive update = new CustomerArchive();
		update.setId(id);
		update.setApprovalStatus(APPROVAL_REJECTED);
		update.setCurrentNode("审核不通过");
		update.setCurrentProcessor(AuthUtil.getUserName());
		addChangeRecord(id, "客商准入审核不通过");
		return updateById(update);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean changeStatus(Long id, Integer status) {
		if (!Objects.equals(status, STATUS_ENABLED) && !Objects.equals(status, STATUS_DISABLED)) {
			throw new ServiceException("启停状态不正确");
		}
		CustomerArchive customer = getById(id);
		if (Func.isEmpty(customer) || Objects.equals(customer.getIsDeleted(), 1)) {
			throw new ServiceException("客商档案不存在");
		}
		CustomerArchive update = new CustomerArchive();
		update.setId(id);
		update.setStatus(status);
		addChangeRecord(id, Objects.equals(status, STATUS_ENABLED) ? "启用客商档案" : "停用客商档案");
		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<CustomerArchive> customers = listByIds(idList);
		for (CustomerArchive customer : customers) {
			if (!Objects.equals(customer.getApprovalStatus(), APPROVAL_DRAFT)) {
				throw new ServiceException("仅草稿状态客商可删除");
			}
		}
		deleteDetail(idList);
		return deleteLogic(idList);
	}

	@Override
	public CustomerCreditScoreVO buildScoreTemplate(Long quantificationId) {
		CreditScoreQuantification quantification = resolveQuantification(quantificationId);
		CustomerCreditScoreVO score = new CustomerCreditScoreVO();
		score.setQuantificationId(quantification.getId());
		score.setScoreDate(LocalDate.now());
		score.setSelfStatus("未完成");
		score.setReviewStatus("未完成");
		score.setDetails(loadScoreTemplateDetails(quantification.getId()));
		return score;
	}

	@Override
	public List<CustomerArchiveExcel> exportCustomerArchive(Wrapper<CustomerArchive> queryWrapper) {
		return list(queryWrapper).stream().map(this::buildExcel).toList();
	}

	private void prepare(CustomerArchiveVO customer) {
		customer.setShortName(trimToNull(customer.getShortName()));
		customer.setFullName(trimToEmpty(customer.getFullName()));
		customer.setCustomerNature(trimToEmpty(customer.getCustomerNature()));
		customer.setUnifiedCreditCode(trimToEmpty(customer.getUnifiedCreditCode()));
		customer.setCustomerType(trimToEmpty(customer.getCustomerType()));
		customer.setBusinessScope(trimToNull(customer.getBusinessScope()));
		customer.setInvoiceTaxRate(normalizeSentinelAmount(customer.getInvoiceTaxRate()));
		customer.setRegisteredCapital(normalizeSentinelAmount(customer.getRegisteredCapital()));
		customer.setRegisteredRegionName(trimToNull(customer.getRegisteredRegionName()));
		customer.setRegisteredDetailAddress(trimToNull(customer.getRegisteredDetailAddress()));
		customer.setRegisteredAddress(buildRegisteredAddress(customer));
		customer.setLegalPerson(trimToEmpty(customer.getLegalPerson()));
		customer.setContactPhone(trimToEmpty(customer.getContactPhone()));
		customer.setDeptIds(trimToNull(customer.getDeptIds()));
		customer.setDeptName(trimToEmpty(customer.getDeptName()));
		customer.setAccessType(Func.isEmpty(customer.getAccessType()) ? ACCESS_TEMPORARY : customer.getAccessType());
		customer.setApprovalStatus(Func.isEmpty(customer.getApprovalStatus()) ? APPROVAL_DRAFT : customer.getApprovalStatus());
		if (Func.isEmpty(customer.getStatus())) {
			customer.setStatus(STATUS_ENABLED);
		}
		customer.setContacts(customer.getContacts() == null ? new ArrayList<>() : customer.getContacts());
		customer.setReceiptAccounts(customer.getReceiptAccounts() == null ? new ArrayList<>() : customer.getReceiptAccounts());
		customer.setInvoices(customer.getInvoices() == null ? new ArrayList<>() : customer.getInvoices());
		customer.setScores(customer.getScores() == null ? new ArrayList<>() : customer.getScores());
	}

	private void validateBase(CustomerArchiveVO customer) {
		if (Func.isEmpty(customer.getFullName())) {
			throw new ServiceException("客商全称不能为空");
		}
		if (Func.isEmpty(customer.getCustomerNature())) {
			throw new ServiceException("客商性质不能为空");
		}
		if (Func.isEmpty(customer.getUnifiedCreditCode())) {
			throw new ServiceException("统一社会信用代码不能为空");
		}
		if (!customer.getUnifiedCreditCode().matches("^[A-Z0-9]{18}$")) {
			throw new ServiceException("统一社会信用代码必须为18位大写字母或数字");
		}
		if (Func.isEmpty(customer.getCustomerType())) {
			throw new ServiceException("客商类型不能为空");
		}
		validateCustomerType(customer.getCustomerType());
		if (Func.isEmpty(customer.getLegalPerson())) {
			throw new ServiceException("法人/负责人不能为空");
		}
		if (Func.isEmpty(customer.getContactPhone())) {
			throw new ServiceException("联系电话不能为空");
		}
		if (!customer.getContactPhone().matches("^1\\d{10}$")) {
			throw new ServiceException("联系电话必须为11位手机号");
		}
		if (Func.isEmpty(customer.getDeptName())) {
			throw new ServiceException("所属组织不能为空");
		}
		if (Objects.equals(customer.getBusinessTermType(), "固定期限") && Func.isEmpty(customer.getBusinessEndDate())) {
			throw new ServiceException("固定营业期限必须填写营业期限截止日");
		}
		validateLength(customer.getShortName(), 20, "客商简称最多20个汉字");
		validateLength(customer.getFullName(), 100, "客商全称最多100个字符");
		validateLength(customer.getLegalPerson(), 10, "法人/负责人最多10个汉字");
		validateLength(customer.getContactPhone(), 11, "联系电话必须为11位手机号");
		validateLength(customer.getRegisteredRegionName(), 100, "省市区最多100个字符");
		validateLength(customer.getRegisteredDetailAddress(), 255, "详细地址最多255个字符");
		validateLength(customer.getRegisteredAddress(), 400, "地址最多400个字符");
		validateLength(customer.getBusinessScope(), 500, "经营范围最多500个字符");
		validateLength(customer.getRemark(), 500, "备注最多500个字符");
		validateNonNegative(customer.getInvoiceTaxRate(), "开票税点不能小于0");
		validateMax(customer.getInvoiceTaxRate(), new BigDecimal("13"), "开票税点必须在0到13之间");
		validateNonNegative(customer.getRegisteredCapital(), "注册资金不能小于0");
		validateNonNegative(customer.getMaxCreditLimit(), "最大资金使用额度不能小于0");
		validateNonNegative(customer.getApplyCreditLimit(), "申请总资金使用额度不能小于0");
		validateUnique(customer);
	}

	private void validateCustomerType(String customerType) {
		List<String> typeList = Func.toStrList(customerType.replace("，", ","))
			.stream()
			.map(String::trim)
			.filter(Func::isNotEmpty)
			.toList();
		if (Func.isEmpty(typeList)) {
			throw new ServiceException("客商类型不能为空");
		}
	}

	private void validateApproval(CustomerArchiveVO customer) {
		if (Objects.equals(customer.getAccessType(), ACCESS_FORMAL)) {
			if (Func.isEmpty(customer.getQualificationAttachments())) {
				throw new ServiceException("正式客商提交审核前必须上传客商材料");
			}
			boolean hasCompletedScore = customer.getScores().stream()
				.anyMatch(score -> "已完成".equals(score.getSelfStatus()) || "已完成".equals(score.getReviewStatus()) || Func.isNotEmpty(score.getFinalScore()));
			if (!hasCompletedScore) {
				throw new ServiceException("正式客商提交审核前必须完成信用评分");
			}
		}
	}

	private void validateUnique(CustomerArchiveVO customer) {
		if (count(Wrappers.<CustomerArchive>lambdaQuery()
			.eq(CustomerArchive::getUnifiedCreditCode, customer.getUnifiedCreditCode())
			.eq(CustomerArchive::getIsDeleted, 0)
			.ne(Func.isNotEmpty(customer.getId()), CustomerArchive::getId, customer.getId())) > 0L) {
			throw new ServiceException("统一社会信用代码已存在");
		}
		if (Func.isNotEmpty(customer.getCustomerCode()) && count(Wrappers.<CustomerArchive>lambdaQuery()
			.eq(CustomerArchive::getCustomerCode, customer.getCustomerCode())
			.eq(CustomerArchive::getIsDeleted, 0)
			.ne(Func.isNotEmpty(customer.getId()), CustomerArchive::getId, customer.getId())) > 0L) {
			throw new ServiceException("客商编号已存在");
		}
	}

	private void replaceDetail(Long customerId, CustomerArchiveVO customer) {
		deleteDetail(List.of(customerId), false);
		insertContacts(customerId, customer.getContacts());
		insertReceiptAccounts(customerId, customer.getReceiptAccounts());
		insertInvoices(customerId, customer.getInvoices());
		if (Func.isNotEmpty(customer.getScores())) {
			deleteScores(List.of(customerId));
			insertScores(customerId, customer.getScores());
		}
	}

	private void insertContacts(Long customerId, List<CustomerContactVO> contacts) {
		for (CustomerContactVO contactVO : contacts) {
			if (Func.isEmpty(contactVO.getContactName()) && Func.isEmpty(contactVO.getContactPhone())) {
				continue;
			}
			CustomerContact contact = Objects.requireNonNull(BeanUtil.copyProperties(contactVO, CustomerContact.class));
			contact.setId(IdWorker.getId());
			contact.setCustomerId(customerId);
			contact.setStatus(STATUS_ENABLED);
			contactMapper.insert(contact);
		}
	}

	private void insertReceiptAccounts(Long customerId, List<CustomerReceiptAccountVO> accounts) {
		for (CustomerReceiptAccountVO accountVO : accounts) {
			if (Func.isEmpty(accountVO.getAccountName()) && Func.isEmpty(accountVO.getBankAccount())) {
				continue;
			}
			CustomerReceiptAccount account = Objects.requireNonNull(BeanUtil.copyProperties(accountVO, CustomerReceiptAccount.class));
			account.setId(IdWorker.getId());
			account.setCustomerId(customerId);
			account.setStatus(STATUS_ENABLED);
			receiptAccountMapper.insert(account);
		}
	}

	private void insertInvoices(Long customerId, List<CustomerInvoiceInfoVO> invoices) {
		for (CustomerInvoiceInfoVO invoiceVO : invoices) {
			if (Func.isEmpty(invoiceVO.getInvoiceTitle()) && Func.isEmpty(invoiceVO.getTaxNo())) {
				continue;
			}
			invoiceVO.setRegisteredAddress(buildAddress(invoiceVO.getRegisteredRegionName(), invoiceVO.getRegisteredDetailAddress(), invoiceVO.getRegisteredAddress()));
			invoiceVO.setReceiverAddress(buildAddress(invoiceVO.getReceiverRegionName(), invoiceVO.getReceiverDetailAddress(), invoiceVO.getReceiverAddress()));
			CustomerInvoiceInfo invoice = Objects.requireNonNull(BeanUtil.copyProperties(invoiceVO, CustomerInvoiceInfo.class));
			invoice.setId(IdWorker.getId());
			invoice.setCustomerId(customerId);
			invoice.setStatus(STATUS_ENABLED);
			invoiceInfoMapper.insert(invoice);
		}
	}

	private void insertScores(Long customerId, List<CustomerCreditScoreVO> scores) {
		for (CustomerCreditScoreVO scoreVO : scores) {
			if (Func.isEmpty(scoreVO.getQuantificationId()) && Func.isEmpty(scoreVO.getScoreDate())) {
				continue;
			}
			Long scoreId = IdWorker.getId();
			scoreVO.setTempApplyCreditLimit(normalizeOptionalScoreAmount(scoreVO.getTempApplyCreditLimit()));
			scoreVO.setProofAttachments(normalizeScoreProofAttachments(scoreVO));
			CustomerCreditScore score = Objects.requireNonNull(BeanUtil.copyProperties(scoreVO, CustomerCreditScore.class));
			score.setId(scoreId);
			score.setCustomerId(customerId);
			score.setTenantId(AuthUtil.getTenantId());
			score.setStatus(STATUS_ENABLED);
			score.setIsDeleted(0);
			creditScoreMapper.insert(score);
			List<CustomerCreditScoreDetailVO> details = scoreVO.getDetails() == null ? new ArrayList<>() : scoreVO.getDetails();
			for (int detailIndex = 0; detailIndex < details.size(); detailIndex++) {
				CustomerCreditScoreDetailVO detailVO = details.get(detailIndex);
				if (Func.isEmpty(detailVO.getScore())) {
					detailVO.setScore(resolveFullScore(detailVO.getOptionsJson()));
				}
				CustomerCreditScoreDetail detail = Objects.requireNonNull(BeanUtil.copyProperties(detailVO, CustomerCreditScoreDetail.class));
				detail.setId(IdWorker.getId());
				detail.setScoreId(scoreId);
				detail.setQuantificationId(score.getQuantificationId());
				detail.setSort(detailIndex + 1);
				detail.setStatus(STATUS_ENABLED);
				detail.setIsDeleted(0);
				creditScoreDetailMapper.insert(detail);
			}
		}
	}

	private void deleteDetail(List<Long> customerIds) {
		deleteDetail(customerIds, true);
	}

	private void deleteDetail(List<Long> customerIds, boolean deleteScores) {
		contactMapper.update(null, Wrappers.<CustomerContact>lambdaUpdate().in(CustomerContact::getCustomerId, customerIds).set(CustomerContact::getIsDeleted, 1));
		receiptAccountMapper.update(null, Wrappers.<CustomerReceiptAccount>lambdaUpdate().in(CustomerReceiptAccount::getCustomerId, customerIds).set(CustomerReceiptAccount::getIsDeleted, 1));
		invoiceInfoMapper.update(null, Wrappers.<CustomerInvoiceInfo>lambdaUpdate().in(CustomerInvoiceInfo::getCustomerId, customerIds).set(CustomerInvoiceInfo::getIsDeleted, 1));
		if (deleteScores) {
			deleteScores(customerIds);
		}
	}

	private void deleteScores(List<Long> customerIds) {
		List<CustomerCreditScore> scores = creditScoreMapper.selectList(Wrappers.<CustomerCreditScore>lambdaQuery()
			.in(CustomerCreditScore::getCustomerId, customerIds)
			.and(wrapper -> wrapper.ne(CustomerCreditScore::getIsDeleted, 1).or().isNull(CustomerCreditScore::getIsDeleted)));
		if (Func.isNotEmpty(scores)) {
			List<Long> scoreIds = scores.stream().map(CustomerCreditScore::getId).toList();
			creditScoreDetailMapper.update(null, Wrappers.<CustomerCreditScoreDetail>lambdaUpdate().in(CustomerCreditScoreDetail::getScoreId, scoreIds).set(CustomerCreditScoreDetail::getIsDeleted, 1));
			creditScoreMapper.update(null, Wrappers.<CustomerCreditScore>lambdaUpdate().in(CustomerCreditScore::getId, scoreIds).set(CustomerCreditScore::getIsDeleted, 1));
		}
	}

	private List<CustomerContactVO> loadContacts(Long customerId) {
		return contactMapper.selectList(Wrappers.<CustomerContact>lambdaQuery()
				.eq(CustomerContact::getCustomerId, customerId)
				.eq(CustomerContact::getIsDeleted, 0)
				.orderByDesc(CustomerContact::getIsDefault)
				.orderByAsc(CustomerContact::getCreateTime))
			.stream().map(contact -> Objects.requireNonNull(BeanUtil.copyProperties(contact, CustomerContactVO.class))).toList();
	}

	private List<CustomerReceiptAccountVO> loadReceiptAccounts(Long customerId) {
		return receiptAccountMapper.selectList(Wrappers.<CustomerReceiptAccount>lambdaQuery()
				.eq(CustomerReceiptAccount::getCustomerId, customerId)
				.eq(CustomerReceiptAccount::getIsDeleted, 0)
				.orderByDesc(CustomerReceiptAccount::getIsDefault)
				.orderByAsc(CustomerReceiptAccount::getCreateTime))
			.stream().map(account -> Objects.requireNonNull(BeanUtil.copyProperties(account, CustomerReceiptAccountVO.class))).toList();
	}

	private List<CustomerInvoiceInfoVO> loadInvoices(Long customerId) {
		return invoiceInfoMapper.selectList(Wrappers.<CustomerInvoiceInfo>lambdaQuery()
				.eq(CustomerInvoiceInfo::getCustomerId, customerId)
				.eq(CustomerInvoiceInfo::getIsDeleted, 0)
				.orderByDesc(CustomerInvoiceInfo::getIsDefault)
				.orderByAsc(CustomerInvoiceInfo::getCreateTime))
			.stream().map(invoice -> Objects.requireNonNull(BeanUtil.copyProperties(invoice, CustomerInvoiceInfoVO.class))).toList();
	}

	private List<CustomerCreditScoreVO> loadScores(Long customerId) {
		List<CustomerCreditScore> scores = creditScoreMapper.selectList(Wrappers.<CustomerCreditScore>lambdaQuery()
			.eq(CustomerCreditScore::getCustomerId, customerId)
			.and(wrapper -> wrapper.ne(CustomerCreditScore::getIsDeleted, 1).or().isNull(CustomerCreditScore::getIsDeleted))
			.orderByDesc(CustomerCreditScore::getScoreDate)
			.orderByDesc(CustomerCreditScore::getCreateTime));
		if (Func.isEmpty(scores)) {
			return new ArrayList<>();
		}
		List<Long> scoreIds = scores.stream().map(CustomerCreditScore::getId).toList();
		Map<Long, List<CustomerCreditScoreDetailVO>> detailMap = creditScoreDetailMapper.selectList(Wrappers.<CustomerCreditScoreDetail>lambdaQuery()
				.in(CustomerCreditScoreDetail::getScoreId, scoreIds)
				.and(wrapper -> wrapper.ne(CustomerCreditScoreDetail::getIsDeleted, 1).or().isNull(CustomerCreditScoreDetail::getIsDeleted))
				.orderByAsc(CustomerCreditScoreDetail::getSort))
			.stream()
			.map(detail -> Objects.requireNonNull(BeanUtil.copyProperties(detail, CustomerCreditScoreDetailVO.class)))
			.collect(Collectors.groupingBy(CustomerCreditScoreDetailVO::getScoreId));
		return scores.stream().map(score -> {
			CustomerCreditScoreVO scoreVO = Objects.requireNonNull(BeanUtil.copyProperties(score, CustomerCreditScoreVO.class));
			scoreVO.setAttachments(parseScoreAttachments(score.getProofAttachments()));
			scoreVO.setDetails(detailMap.getOrDefault(score.getId(), new ArrayList<>()));
			scoreVO.setStandards(loadStandards(score.getQuantificationId()));
			return scoreVO;
		}).toList();
	}

	private String normalizeScoreProofAttachments(CustomerCreditScoreVO scoreVO) {
		if (Func.isNotEmpty(scoreVO.getProofAttachments())) {
			return trimToNull(scoreVO.getProofAttachments());
		}
		Object attachments = scoreVO.getAttachments();
		if (Func.isEmpty(attachments)) {
			return null;
		}
		if (attachments instanceof String attachmentText) {
			return trimToNull(attachmentText);
		}
		return JsonUtil.toJson(attachments);
	}

	private BigDecimal normalizeOptionalScoreAmount(BigDecimal value) {
		return value != null && value.compareTo(BigDecimal.ZERO) < 0 ? null : value;
	}

	private BigDecimal normalizeSentinelAmount(BigDecimal value) {
		return value != null && value.compareTo(BigDecimal.ONE.negate()) == 0 ? null : value;
	}

	private Object parseScoreAttachments(String value) {
		if (Func.isEmpty(value)) {
			return new ArrayList<>();
		}
		try {
			return JsonUtil.parse(value, List.class);
		} catch (Exception exception) {
			return new ArrayList<>();
		}
	}

	private List<CustomerChangeRecordVO> loadChangeRecords(Long customerId) {
		return changeRecordMapper.selectList(Wrappers.<CustomerChangeRecord>lambdaQuery()
				.eq(CustomerChangeRecord::getCustomerId, customerId)
				.eq(CustomerChangeRecord::getIsDeleted, 0)
				.orderByDesc(CustomerChangeRecord::getChangeTime))
			.stream().map(record -> Objects.requireNonNull(BeanUtil.copyProperties(record, CustomerChangeRecordVO.class))).toList();
	}

	private CreditScoreQuantification resolveQuantification(Long quantificationId) {
		CreditScoreQuantification quantification;
		if (Func.isNotEmpty(quantificationId)) {
			quantification = quantificationMapper.selectById(quantificationId);
		} else {
			quantification = quantificationMapper.selectList(Wrappers.<CreditScoreQuantification>lambdaQuery()
					.eq(CreditScoreQuantification::getStatus, STATUS_ENABLED)
					.eq(CreditScoreQuantification::getIsDeleted, 0)
					.orderByDesc(CreditScoreQuantification::getCreateTime))
				.stream().findFirst().orElse(null);
		}
		if (Func.isEmpty(quantification) || !Objects.equals(quantification.getStatus(), STATUS_ENABLED) || Objects.equals(quantification.getIsDeleted(), 1)) {
			throw new ServiceException("请先维护并启用评分量化表");
		}
		return quantification;
	}

	private List<CustomerCreditScoreDetailVO> loadScoreTemplateDetails(Long quantificationId) {
		List<CreditScoreCategory> categories = categoryMapper.selectList(Wrappers.<CreditScoreCategory>lambdaQuery()
			.eq(CreditScoreCategory::getQuantificationId, quantificationId)
			.eq(CreditScoreCategory::getIsDeleted, 0)
			.orderByAsc(CreditScoreCategory::getSort));
		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<CreditScoreItemOption>> optionMap = options.stream().collect(Collectors.groupingBy(CreditScoreItemOption::getItemId));
		Map<Long, CreditScoreCategory> categoryMap = categories.stream().collect(Collectors.toMap(CreditScoreCategory::getId, category -> category));
		List<CustomerCreditScoreDetailVO> details = new ArrayList<>();
		for (CreditScoreItem item : items) {
			CreditScoreCategory category = categoryMap.get(item.getCategoryId());
			CustomerCreditScoreDetailVO detail = new CustomerCreditScoreDetailVO();
			detail.setQuantificationId(quantificationId);
			detail.setItemId(item.getId());
			detail.setCategoryCode(category == null ? item.getCategoryCode() : category.getCategoryCode());
			detail.setCategoryName(category == null ? item.getCategoryCode() : category.getCategoryName());
			detail.setItemName(item.getItemName());
			detail.setOptionDescription(item.getOptionDescription());
			detail.setScoreDescription(item.getScoreDescription());
			List<CreditScoreItemOption> itemOptions = optionMap.getOrDefault(item.getId(), new ArrayList<>());
			detail.setScore(resolveFullScore(itemOptions));
			detail.setOptionsJson(buildOptionsJson(itemOptions));
			detail.setSelfScore(BigDecimal.ZERO);
			detail.setReviewScore(BigDecimal.ZERO);
			details.add(detail);
		}
		return details;
	}

	private String buildOptionsJson(List<CreditScoreItemOption> options) {
		List<Map<String, Object>> optionList = options.stream()
			.sorted(Comparator.comparing(CreditScoreItemOption::getSort, Comparator.nullsLast(Integer::compareTo)))
			.map(option -> {
				Map<String, Object> map = new LinkedHashMap<>();
				map.put("label", option.getOptionName());
				map.put("value", option.getOptionName());
				map.put("score", option.getScore());
				return map;
			}).toList();
		return JsonUtil.toJson(optionList);
	}

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

	private BigDecimal resolveFullScore(List<CreditScoreItemOption> options) {
		return options.stream()
			.map(CreditScoreItemOption::getScore)
			.filter(Objects::nonNull)
			.max(BigDecimal::compareTo)
			.orElse(BigDecimal.ZERO);
	}

	private BigDecimal resolveFullScore(String optionsJson) {
		if (Func.isEmpty(optionsJson)) {
			return BigDecimal.ZERO;
		}
		try {
			Object value = JsonUtil.parse(optionsJson, List.class);
			if (!(value instanceof List<?> options)) {
				return BigDecimal.ZERO;
			}
			return options.stream()
				.map(this::resolveOptionScore)
				.filter(Objects::nonNull)
				.max(BigDecimal::compareTo)
				.orElse(BigDecimal.ZERO);
		} catch (Exception exception) {
			return BigDecimal.ZERO;
		}
	}

	private BigDecimal resolveOptionScore(Object option) {
		if (!(option instanceof Map<?, ?> optionMap)) {
			return null;
		}
		Object score = optionMap.get("score");
		if (score == null) {
			return null;
		}
		try {
			return new BigDecimal(String.valueOf(score));
		} catch (NumberFormatException exception) {
			return null;
		}
	}

	private void grantNewCustomerToIncludedUsers(CustomerArchive customer) {
		baseMapper.selectIncludeNewCustomerUserIds(customer.getTenantId()).forEach(userId -> {
			UserCustomerScope scope = new UserCustomerScope();
			scope.setUserId(userId);
			scope.setCustomerId(customer.getId());
			userCustomerScopeMapper.insert(scope);
		});
	}

	private String nextCustomerCode() {
		CustomerArchive latest = list(Wrappers.<CustomerArchive>lambdaQuery()
				.likeRight(CustomerArchive::getCustomerCode, CUSTOMER_CODE_PREFIX)
				.eq(CustomerArchive::getIsDeleted, 0)
				.orderByDesc(CustomerArchive::getCustomerCode))
			.stream().findFirst().orElse(null);
		int nextNumber = 1;
		if (Func.isNotEmpty(latest) && Func.isNotEmpty(latest.getCustomerCode())) {
			String number = latest.getCustomerCode().replace(CUSTOMER_CODE_PREFIX, "");
			if (number.matches("^\\d+$")) {
				nextNumber = Integer.parseInt(number) + 1;
			}
		}
		return CUSTOMER_CODE_PREFIX + String.format("%06d", nextNumber);
	}

	private void addChangeRecord(Long customerId, String content) {
		CustomerChangeRecord record = new CustomerChangeRecord();
		record.setId(IdWorker.getId());
		record.setCustomerId(customerId);
		record.setChangeTime(LocalDateTime.now());
		record.setChangeContent(content);
		record.setChangeUserName(AuthUtil.getUserName());
		record.setStatus(STATUS_ENABLED);
		changeRecordMapper.insert(record);
	}

	private CustomerArchiveExcel buildExcel(CustomerArchive customer) {
		CustomerArchiveExcel excel = new CustomerArchiveExcel();
		excel.setCustomerCode(customer.getCustomerCode());
		excel.setShortName(customer.getShortName());
		excel.setFullName(customer.getFullName());
		excel.setCustomerType(customer.getCustomerType());
		excel.setCustomerNature(customer.getCustomerNature());
		excel.setUnifiedCreditCode(customer.getUnifiedCreditCode());
		excel.setDeptName(customer.getDeptName());
		excel.setAccessTypeName(accessTypeName(customer.getAccessType()));
		excel.setApprovalStatusName(approvalStatusName(customer.getApprovalStatus()));
		excel.setCurrentNode(customer.getCurrentNode());
		excel.setCurrentProcessor(customer.getCurrentProcessor());
		excel.setApprovedTime(customer.getApprovedTime());
		excel.setStatusName(Objects.equals(customer.getStatus(), STATUS_ENABLED) ? "启用" : "停用");
		excel.setCustomerLevel(customer.getCustomerLevel());
		excel.setMaxCreditLimit(customer.getMaxCreditLimit());
		excel.setApplyCreditLimit(customer.getApplyCreditLimit());
		excel.setContactPhone(customer.getContactPhone());
		excel.setLegalPerson(customer.getLegalPerson());
		excel.setCreateTime(customer.getCreateTime());
		return excel;
	}

	private String accessTypeName(String accessType) {
		return Objects.equals(accessType, ACCESS_FORMAL) ? "正式" : "临时";
	}

	private String approvalStatusName(String approvalStatus) {
		if (Objects.equals(approvalStatus, APPROVAL_REVIEWING)) {
			return "审核中";
		}
		if (Objects.equals(approvalStatus, APPROVAL_APPROVED)) {
			return "审核通过";
		}
		if (Objects.equals(approvalStatus, APPROVAL_REJECTED)) {
			return "审核不通过";
		}
		return "草稿";
	}

	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 void validateMax(BigDecimal value, BigDecimal max, String message) {
		if (Func.isNotEmpty(value) && value.compareTo(max) > 0) {
			throw new ServiceException(message);
		}
	}

	private String buildRegisteredAddress(CustomerArchiveVO customer) {
		return buildAddress(customer.getRegisteredRegionName(), customer.getRegisteredDetailAddress(), customer.getRegisteredAddress());
	}

	private String buildAddress(String regionNameValue, String detailAddressValue, String fallbackAddress) {
		String regionName = trimToEmpty(regionNameValue);
		String detailAddress = trimToEmpty(detailAddressValue);
		if (Func.isNotEmpty(regionName) || Func.isNotEmpty(detailAddress)) {
			return regionName + detailAddress;
		}
		return trimToNull(fallbackAddress);
	}

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

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

}
