package com.yd.csf.api.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yd.base.feign.client.exchangerate.ApiExchangeRateFeignClient;
import com.yd.common.enums.CommonEnum;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result;
import com.yd.common.utils.CodeGenerator;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.csf.api.service.ApiSalaryRemittanceService;
import com.yd.csf.api.service.ApiSalaryService;
import com.yd.csf.feign.dto.salary.ApiSalaryBatchAddDTO;
import com.yd.csf.feign.dto.salary.CalculateTotalAmountDTO;
import com.yd.csf.feign.dto.salaryremittance.ApiSalaryRemittanceDTO;
import com.yd.csf.feign.dto.salaryremittance.ApiSalaryRemittanceFzDTO;
import com.yd.csf.feign.enums.SalaryStatusEnum;
import com.yd.csf.feign.request.salary.*;
import com.yd.csf.feign.response.salary.ApiSalaryDetailResponse;
import com.yd.csf.feign.response.salary.ApiSalaryPageResponse;
import com.yd.csf.feign.response.salary.ApiSalaryPushPageResponse;
import com.yd.csf.service.enums.FortuneAccountStatusEnum;
import com.yd.csf.service.model.FortuneAccount;
import com.yd.csf.service.model.Salary;
import com.yd.csf.service.model.SalaryRemittance;
import com.yd.csf.service.service.FortuneAccountService;
import com.yd.csf.service.service.ISalaryRemittanceService;
import com.yd.csf.service.service.ISalaryService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ApiSalaryServiceImpl implements ApiSalaryService {

    @Autowired
    private ISalaryService iSalaryService;

    @Autowired
    private ApiSalaryRemittanceService apiSalaryRemittanceService;

    @Autowired
    private ISalaryRemittanceService iSalaryRemittanceService;

    @Autowired
    private FortuneAccountService fortuneAccountService;

    @Autowired
    private ApiExchangeRateFeignClient apiExchangeRateFeignClient;

    /**
     * 分页查询-制作薪资单列表信息
     * @param request
     * @return
     */
    @Override
    public Result<IPage<ApiSalaryPageResponse>> page(ApiSalaryPageRequest request) {
        Page<ApiSalaryPageResponse> page = new Page<>(request.getPageNo(), request.getPageSize());
        IPage<ApiSalaryPageResponse> iPage = iSalaryService.page(page, request);
        return Result.success(iPage);
    }

    /**
     * 分页查询-电子薪资单列表信息（推送列表信息）
     * @param request
     * @return
     */
    @Override
    public Result<IPage<ApiSalaryPushPageResponse>> pushPage(ApiSalaryPushPageRequest request) {
        Page<ApiSalaryPushPageResponse> page = new Page<>(request.getPageNo(), request.getPageSize());
        IPage<ApiSalaryPushPageResponse> iPage = iSalaryService.pushPage(page, request);
        return Result.success(iPage);
    }

    /**
     * 详情-薪资单信息
     * @param salaryBizId
     * @return
     */
    @Override
    public Result<ApiSalaryDetailResponse> detail(String salaryBizId) {
        //校验薪资单信息是否存在
        Salary salary = checkIsExist(salaryBizId);
        ApiSalaryDetailResponse response = new ApiSalaryDetailResponse();
        BeanUtils.copyProperties(salary,response);
        //薪资汇款明细封装的DTO
        ApiSalaryRemittanceFzDTO fzDTO = new ApiSalaryRemittanceFzDTO();
        fzDTO.setTotalAmount(salary.getPaidAmount());
        List<ApiSalaryRemittanceDTO> salaryRemittanceDTOList = new ArrayList<>();
        List<SalaryRemittance> salaryRemittanceList = iSalaryRemittanceService.queryList(salaryBizId);
        if (CollectionUtils.isNotEmpty(salaryRemittanceList)) {
            salaryRemittanceDTOList = salaryRemittanceList.stream().map(dto -> {
                ApiSalaryRemittanceDTO remittanceDTO = new ApiSalaryRemittanceDTO();
                BeanUtils.copyProperties(dto,remittanceDTO);
                return remittanceDTO;
            }).collect(Collectors.toList());
        }
        fzDTO.setSalaryRemittanceDTOList(salaryRemittanceDTOList);
        response.setApiSalaryRemittanceFzDTO(fzDTO);
        return Result.success(response);
    }

    /**
     * 新增-薪资单信息
     * @param request
     * @return
     */
    @Override
    public Result add(ApiSalaryAddRequest request) {
        ApiSalaryRemittanceFzDTO fzDTO = request.getApiSalaryRemittanceFzDTO();
        if (fzDTO == null) {
            fzDTO = new ApiSalaryRemittanceFzDTO();
        }
        //校验金额
        checkAmount(request.getPaidAmount(),fzDTO.getTotalAmount());
        //校验同一转介人同一薪资年月只能有一条薪资单记录
        checkUniqueByBrokerMonth(request.getBrokerBizId(),request.getMonth(),"",false);
        //校验：当前转介人对应的薪资月没有完成出账状态的记录，不能保存薪资单
        checkIsBilling(request.getBrokerBizId(),request.getMonth());

        Salary salary = new Salary();
        BeanUtils.copyProperties(request,salary);
        //发放编号
        salary.setSalaryNo(CodeGenerator.generate10Code("SN"));
        //薪资单表唯一业务ID
        salary.setSalaryBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_SALARY.getCode()));
        //1-待提交
        salary.setStatus(SalaryStatusEnum.DTJ.getItemValue());
        iSalaryService.saveOrUpdate(salary);

        //保存薪资汇款明细
        apiSalaryRemittanceService.batchSave(salary.getSalaryBizId(),fzDTO);
        return Result.success();
    }

    /**
     * 修改-薪资单信息
     * @param request
     * @return
     */
    @Override
    public Result edit(ApiSalaryEditRequest request) {
        ApiSalaryRemittanceFzDTO fzDTO = request.getApiSalaryRemittanceFzDTO();
        if (fzDTO == null) {
            fzDTO = new ApiSalaryRemittanceFzDTO();
        }
        //校验金额
        checkAmount(request.getPaidAmount(),fzDTO.getTotalAmount());
        //校验薪资单信息是否存在
        Salary salary = checkIsExist(request.getSalaryBizId());
        //校验同一转介人同一薪资年月只能有一条薪资单记录
        checkUniqueByBrokerMonth(request.getBrokerBizId(),request.getMonth(),
                request.getSalaryBizId(),true);
        //校验：当前转介人对应的薪资月没有完成出账状态的记录，不能保存薪资单
        checkIsBilling(request.getBrokerBizId(),request.getMonth());

        BeanUtils.copyProperties(request,salary);
        iSalaryService.saveOrUpdate(salary);

        //保存薪资汇款明细
        apiSalaryRemittanceService.batchSave(salary.getSalaryBizId(),fzDTO);
        return Result.success();
    }

    /**
     * 提交核对
     * @param request
     * @return
     */
    @Override
    public Result submitCheck(ApiSalarySubmitCheckRequest request) {
        Salary salary = iSalaryService.queryOne(request.getSalaryBizId());
        if (salary == null) {
            throw new BusinessException("薪资单信息不存在");
        }
        if (SalaryStatusEnum.DTJ.getItemValue().equals(salary.getStatus())
                || SalaryStatusEnum.TH.getItemValue().equals(salary.getStatus())) {
            throw new BusinessException("非待提交或者退回状态不能提交核对");
        }
        //重新提交->待核对状态
        salary.setStatus(SalaryStatusEnum.DHD.getItemValue());
        iSalaryService.saveOrUpdate(salary);
        return Result.success();
    }

    /**
     * 核对
     * @param request
     * @return
     */
    @Override
    public Result check(ApiSalaryCheckRequest request) {
        Salary salary = iSalaryService.queryOne(request.getSalaryBizId());
        if (salary == null) {
            throw new BusinessException("薪资单信息不存在");
        }
        if (!SalaryStatusEnum.DHD.getItemValue().equals(salary.getStatus())) {
            throw new BusinessException("非待核对状态不能核对");
        }
        salary.setStatus(request.getStatus() == 1 ? SalaryStatusEnum.YHD.getItemValue() : SalaryStatusEnum.TH.getItemValue());
        salary.setReturnRemark(request.getReturnRemark());
        iSalaryService.saveOrUpdate(salary);
        return Result.success();
    }

    /**
     * 推送
     * @param request
     * @return
     */
    @Override
    public Result push(ApiSalaryPushRequest request) {
        Salary salary = iSalaryService.queryOne(request.getSalaryBizId());
        if (salary == null) {
            throw new BusinessException("薪资单信息不存在");
        }
        if (!SalaryStatusEnum.YHD.getItemValue().equals(salary.getStatus())) {
            throw new BusinessException("非已核对状态不能推送");
        }
        salary.setStatus(SalaryStatusEnum.YTS.getItemValue());
        iSalaryService.saveOrUpdate(salary);
        return Result.success();
    }

    /**
     * 取消薪资
     * @param request
     * @return
     */
    @Override
    public Result cancel(ApiSalaryCancelRequest request) {
        Salary salary = iSalaryService.queryOne(request.getSalaryBizId());
        if (salary == null) {
            throw new BusinessException("薪资单信息不存在");
        }
        salary.setStatus(SalaryStatusEnum.YQX.getItemValue());
        salary.setCancelRemark(request.getCancelRemark());
        iSalaryService.saveOrUpdate(salary);
        return Result.success();
    }

    /**
     * 批量新增-薪资单信息
     * @param request
     * @return
     */
    @Override
    public Result batchAdd(ApiSalaryBatchAddRequest request) {
        List<ApiSalaryBatchAddDTO> apiSalaryBatchAddDTOList = request.getApiSalaryBatchAddDTOList();
        if (CollectionUtils.isEmpty(apiSalaryBatchAddDTOList)) {
            throw new BusinessException("批量新增的数据列表不能为空");
        }
        //批量校验同一转介人同一年月只能有一条薪资单记录
        batchCheckUniqueByBrokerMonth(apiSalaryBatchAddDTOList);
        //批量校验薪资单信息金额字段信息
        batchAddCheckAmount(apiSalaryBatchAddDTOList);
        //批量校验：当前转介人对应的薪资月没有完成出账状态的记录，不能保存薪资单
        batchCheckIsBilling(apiSalaryBatchAddDTOList);

        Map<Salary,List<SalaryRemittance>> map = new HashMap<>();
        apiSalaryBatchAddDTOList.forEach(dto -> {
            Salary salary = new Salary();
            BeanUtils.copyProperties(dto,salary);
            //发放编号
            salary.setSalaryNo(CodeGenerator.generate10Code("SN"));
            salary.setSalaryBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_SALARY.getCode()));
            salary.setStatus(SalaryStatusEnum.DTJ.getItemValue());
            List<SalaryRemittance> salaryRemittanceList = new ArrayList<>();
            SalaryRemittance remittance1 = new SalaryRemittance();
            remittance1.setSalaryBizId(salary.getSalaryBizId());
            remittance1.setSalaryRemittanceBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_SALARY_REMITTANCE.getCode()));
            remittance1.setBank(dto.getBank1());
            remittance1.setAccountEndNo(dto.getAccountEndNo1());
            remittance1.setCurrency(dto.getCurrency1());
            remittance1.setAmount(dto.getAmount1());
            remittance1.setExchangeRate(dto.getExchangeRate1());
            remittance1.setRemark(dto.getRemark1());
            salaryRemittanceList.add(remittance1);
            SalaryRemittance remittance2 = new SalaryRemittance();
            remittance2.setSalaryBizId(salary.getSalaryBizId());
            remittance2.setSalaryRemittanceBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_SALARY_REMITTANCE.getCode()));
            remittance2.setBank(dto.getBank2());
            remittance2.setAccountEndNo(dto.getAccountEndNo2());
            remittance2.setCurrency(dto.getCurrency2());
            remittance2.setAmount(dto.getAmount2());
            remittance2.setExchangeRate(dto.getExchangeRate2());
            remittance2.setRemark(dto.getRemark2());
            salaryRemittanceList.add(remittance2);
            map.put(salary,salaryRemittanceList);
        });
        // 提取所有的 Salary 对象
        List<Salary> salaryList = new ArrayList<>(map.keySet());
        // 提取所有的 SalaryRemittance 对象（将 Map 中所有 List 打平）
        List<SalaryRemittance> salaryRemittanceList = map.values()
                .stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());
        //批量保存salaryList
        iSalaryService.saveOrUpdateBatch(salaryList);
        //批量保存salaryRemittanceList
        iSalaryRemittanceService.saveOrUpdateBatch(salaryRemittanceList);
        return Result.success();
    }

    /**
     * 删除-薪资单汇款明细信息
     * @param salaryRemittanceBizId
     * @return
     */
    @Override
    public Result delSalaryRemittance(String salaryRemittanceBizId) {
        SalaryRemittance salaryRemittance = iSalaryRemittanceService.queryOne(salaryRemittanceBizId);
        if (salaryRemittance == null) {
            throw new BusinessException("薪资单汇款明细信息不存在");
        }
        iSalaryRemittanceService.removeById(salaryRemittance.getId());
        return Result.success();
    }

    /**
     * 根据转介人和薪资年月查询应发总金额
     * @param request
     * @return
     */
    @Override
    public Result<BigDecimal> getGrossAmount(ApiSalaryGrossAmountRequest request) {
        //根据转介人和薪资年月查询是否有完成出账的记录
        List<FortuneAccount> fortuneAccountList = fortuneAccountService.queryList(request.getBrokerBizId(),request.getMonth(), FortuneAccountStatusEnum.SENT.getItemValue());
        if (CollectionUtils.isEmpty(fortuneAccountList)) {
            throw new BusinessException("当前转介人对应的薪资月没有完成出账状态的记录，不能保存发放薪资");
        }
        //累加
        BigDecimal totalAmount = fortuneAccountList.stream()
                .map(dto -> {
                    BigDecimal hkdAmount = ObjectUtils.defaultIfNull(dto.getHkdAmount(), BigDecimal.ZERO);
                    return hkdAmount;
                })
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return Result.success(totalAmount);
    }

    /**
     * 薪资单信息-汇款明细-查询汇率
     * @param request
     * @return
     */
    @Override
    public Result<BigDecimal> getExchangeRate(ApiSalaryExchangeRateRequest request) {
        return apiExchangeRateFeignClient.getExchangeRate(request.getFromCurrency(),request.getToCurrency(),"");
    }

    /**
     * 计算-实发金额
     * @param request
     * @return
     */
    @Override
    public Result<BigDecimal> calculatePaidAmount(ApiSalaryCalculatePaidAmountRequest request) {
        //应发总金额
        BigDecimal grossAmount = ObjectUtils.defaultIfNull(request.getGrossAmount(), BigDecimal.ZERO);
        //MPF事项总金额
        BigDecimal mpfAmount = ObjectUtils.defaultIfNull(request.getMpfAmount(), BigDecimal.ZERO);
        //其他调整总金额
        BigDecimal otherAmount = ObjectUtils.defaultIfNull(request.getOtherAmount(), BigDecimal.ZERO);
        //实发金额 = 应发金额-MPF事项+其他调整
        BigDecimal paidAmount = grossAmount.subtract(mpfAmount).add(otherAmount);
        return Result.success(paidAmount);
    }

    /**
     * 计算-薪资汇款明细合计发放金额（单位：HKD）
     * @param request
     * @return
     */
    @Override
    public Result<BigDecimal> calculateTotalAmount(ApiSalaryCalculateTotalAmountRequest request) {
        List<CalculateTotalAmountDTO> calculateTotalAmountDTOList = request.getCalculateTotalAmountDTOList();
        if (CollectionUtils.isEmpty(calculateTotalAmountDTOList)) {
            throw new BusinessException("计算入参的值不能为空");
        }
        //累加
        BigDecimal totalAmount = calculateTotalAmountDTOList.stream()
                .map(dto -> {
                    BigDecimal amount = ObjectUtils.defaultIfNull(dto.getAmount(), BigDecimal.ZERO);
                    BigDecimal exchangeRate = ObjectUtils.defaultIfNull(dto.getExchangeRate(), BigDecimal.ZERO);
                    return amount.multiply(exchangeRate);
                })
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return Result.success(totalAmount);
    }

    /**
     * 批量校验同一转介人同一年月只能有一条薪资单记录
     * @param apiSalaryBatchAddDTOList 批量新增DTO列表
     */
    public void batchCheckUniqueByBrokerMonth(List<ApiSalaryBatchAddDTO> apiSalaryBatchAddDTOList) {
        if (CollectionUtils.isEmpty(apiSalaryBatchAddDTOList)) {
            return;
        }

        // 构建 key -> brokerName 映射（用于错误提示）
        Map<String, String> keyToBrokerName = new HashMap<>();
        for (ApiSalaryBatchAddDTO dto : apiSalaryBatchAddDTOList) {
            String key = dto.getBrokerBizId() + "|" + dto.getMonth();
            if (!keyToBrokerName.containsKey(key)) {
                keyToBrokerName.put(key, dto.getBrokerName());
            }
        }

        // 校验列表内部是否有重复
        Set<String> keySet = new HashSet<>();
        List<String> duplicateKeys = new ArrayList<>();
        for (ApiSalaryBatchAddDTO dto : apiSalaryBatchAddDTOList) {
            String key = dto.getBrokerBizId() + "|" + dto.getMonth();
            if (!keySet.add(key)) {
                duplicateKeys.add(key);
            }
        }
        if (!duplicateKeys.isEmpty()) {
            List<String> duplicateDescriptions = duplicateKeys.stream()
                    .distinct()
                    .map(key -> {
                        String name = keyToBrokerName.getOrDefault(key, "未知");
                        String month = key.split("\\|")[1];
                        return name + "-" + month;
                    })
                    .collect(Collectors.toList());
            throw new BusinessException("批量数据中存在重复的转介人与薪资月份组合: " + String.join(", ", duplicateDescriptions));
        }

        // 查询数据库中已存在的相同组合
        List<String> brokerBizIdList = apiSalaryBatchAddDTOList.stream()
                .map(ApiSalaryBatchAddDTO::getBrokerBizId)
                .distinct()
                .collect(Collectors.toList());
        List<String> monthList = apiSalaryBatchAddDTOList.stream()
                .map(ApiSalaryBatchAddDTO::getMonth)
                .distinct()
                .collect(Collectors.toList());

        LambdaQueryWrapper<Salary> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Salary::getIsDeleted, 0)
                .in(Salary::getBrokerBizId, brokerBizIdList)
                .in(Salary::getMonth, monthList);
        List<Salary> existingSalaries = iSalaryService.list(wrapper);

        Set<String> existingKeys = existingSalaries.stream()
                .map(s -> s.getBrokerBizId() + "|" + s.getMonth())
                .collect(Collectors.toSet());

        // 找出冲突的组合
        List<String> conflictKeys = apiSalaryBatchAddDTOList.stream()
                .map(dto -> dto.getBrokerBizId() + "|" + dto.getMonth())
                .filter(existingKeys::contains)
                .distinct()
                .collect(Collectors.toList());

        if (!conflictKeys.isEmpty()) {
            List<String> conflictDescriptions = conflictKeys.stream()
                    .map(key -> {
                        String name = keyToBrokerName.getOrDefault(key, "未知");
                        String month = key.split("\\|")[1];
                        return name + "-" + month;
                    })
                    .collect(Collectors.toList());
            throw new BusinessException("以下转介人与薪资月份组合已存在薪资单记录: " + String.join(", ", conflictDescriptions));
        }
    }

    /**
     * 批量新增薪资单-校验金额
     * @param apiSalaryBatchAddDTOList
     */
    public void batchAddCheckAmount(List<ApiSalaryBatchAddDTO> apiSalaryBatchAddDTOList) {
        if (CollectionUtils.isEmpty(apiSalaryBatchAddDTOList)) {
            return;
        }
        // 构建 key -> brokerName 映射（用于错误提示）
        Map<String, String> keyToBrokerName = new HashMap<>();
        for (ApiSalaryBatchAddDTO dto : apiSalaryBatchAddDTOList) {
            String key = dto.getBrokerBizId() + "|" + dto.getMonth();
            if (!keyToBrokerName.containsKey(key)) {
                keyToBrokerName.put(key, dto.getBrokerName());
            }
        }

        //创建需要校验的key值
        List<String> checkKeys = new ArrayList<>();
        for (ApiSalaryBatchAddDTO addDTO : apiSalaryBatchAddDTOList) {
            //获取2条汇款明细的发放金额总和
            BigDecimal totalAmount = statisticsAmountByBatchAdd(addDTO);
            //校验totalAmount和实发金额是否相等
            BigDecimal paidAmount = ObjectUtils.defaultIfNull(addDTO.getPaidAmount(), BigDecimal.ZERO);
            if (paidAmount.compareTo(totalAmount) != 0){
                //汇款明细合计总金额和实发总金额必须相等，记录key值
                String key = addDTO.getBrokerBizId() + "|" + addDTO.getMonth();
                checkKeys.add(key);
            }
        }

        if (!checkKeys.isEmpty()) {
            List<String> duplicateDescriptions = checkKeys.stream()
                    .distinct()
                    .map(key -> {
                        String name = keyToBrokerName.getOrDefault(key, "未知");
                        String month = key.split("\\|")[1];
                        return name + "-" + month;
                    })
                    .collect(Collectors.toList());
            throw new BusinessException("批量数据中，汇款明细金额合计值和实发金额不相等的转介人与薪资月份组合: " + String.join(", ", duplicateDescriptions));
        }

    }


    /**
     * 批量校验：当前转介人对应的薪资月没有完成出账状态的记录，不能保存薪资单
     * @param apiSalaryBatchAddDTOList 批量新增薪资单DTO列表
     */
    public void batchCheckIsBilling(List<ApiSalaryBatchAddDTO> apiSalaryBatchAddDTOList) {
        if (CollectionUtils.isEmpty(apiSalaryBatchAddDTOList)) {
            return;
        }

        // 1. 构建 pairs 列表（包含所有组合，不去重，因为查询时 OR 条件会自动去重，但也可以去重提升性能）
        List<Map.Entry<String, String>> pairs = new ArrayList<>();
        Map<String, ApiSalaryBatchAddDTO> keyToDto = new HashMap<>();
        for (ApiSalaryBatchAddDTO dto : apiSalaryBatchAddDTOList) {
            String brokerBizId = dto.getBrokerBizId();
            String month = dto.getMonth();
            if (StringUtils.isBlank(brokerBizId) || StringUtils.isBlank(month)) {
                continue;
            }
            pairs.add(new AbstractMap.SimpleEntry<>(brokerBizId, month));
            keyToDto.put(brokerBizId + "|" + month, dto);
        }
        if (pairs.isEmpty()) {
            return;
        }

        // 2. 调用新方法查询已出账记录
        List<FortuneAccount> existingAccounts = fortuneAccountService.queryList(pairs, FortuneAccountStatusEnum.SENT.getItemValue());

        // 3. 构建已存在组合的 Set
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM");
        Set<String> existingKeys = existingAccounts.stream()
                .map(fa -> {
                    // Date -> LocalDate
                    LocalDate date = fa.getFortuneAccountDate().toInstant()
                            .atZone(ZoneId.systemDefault()).toLocalDate();
                    return fa.getBrokerBizId() + "|" + date.format(monthFormatter);
                })
                .collect(Collectors.toSet());

        // 4. 找出缺失的组合
        List<String> missingKeys = pairs.stream()
                .map(pair -> pair.getKey() + "|" + pair.getValue())
                .filter(key -> !existingKeys.contains(key))
                .distinct()
                .collect(Collectors.toList());

        if (!missingKeys.isEmpty()) {
            List<String> missingDescriptions = missingKeys.stream()
                    .map(key -> {
                        ApiSalaryBatchAddDTO dto = keyToDto.get(key);
                        String name = dto != null && StringUtils.isNotBlank(dto.getBrokerName()) ? dto.getBrokerName() : key.split("\\|")[0];
                        String month = key.split("\\|")[1];
                        return name + "-" + month;
                    })
                    .collect(Collectors.toList());
            throw new BusinessException("以下转介人与薪资月份组合没有完成出账状态的记录: " + String.join(", ", missingDescriptions));
        }
    }

    /**
     * 统计批量新增的汇款明细对象发放金额合计值
     * @return
     */
    public BigDecimal statisticsAmountByBatchAdd(ApiSalaryBatchAddDTO addDTO) {
        //获取2条汇款明细的发放金额总和
        //汇款金额1
        BigDecimal amount1 = ObjectUtils.defaultIfNull(addDTO.getAmount1(), BigDecimal.ZERO);
        //汇率1
        BigDecimal exchangeRate1 = ObjectUtils.defaultIfNull(addDTO.getExchangeRate1(), BigDecimal.ZERO);
        //目标发放金额1 = 汇款金额1 * 汇率1
        BigDecimal targetAmount1 = amount1.multiply(exchangeRate1);

        //汇款金额2
        BigDecimal amount2 = ObjectUtils.defaultIfNull(addDTO.getAmount2(), BigDecimal.ZERO);
        //汇率2
        BigDecimal exchangeRate2 = ObjectUtils.defaultIfNull(addDTO.getExchangeRate2(), BigDecimal.ZERO);
        //目标发放金额2 = 汇款金额2 * 汇率2
        BigDecimal targetAmount2 = amount2.multiply(exchangeRate2);

        //合计发放金额 = targetAmount1 + targetAmount2
        BigDecimal totalAmount = targetAmount1.add(targetAmount2);
        return totalAmount;
    }

    /**
     * 校验同一转介人同一年月只能有一条薪资单记录
     * @param brokerBizId 转介人业务id
     * @param month 薪资月份（同出账月，如：202605）
     * @param salaryBizId 薪资单信息唯一业务ID
     * @param isExcludeMy 是否排除自己
     */
    public void checkUniqueByBrokerMonth(String brokerBizId,String month,
                                         String salaryBizId,Boolean isExcludeMy) {
        Salary salary = iSalaryService.queryOne(brokerBizId,month,salaryBizId,isExcludeMy);
        if (salary != null) {
            throw new BusinessException("同一转介人同一薪资年月只能有一条薪资单记录");
        }
    }

    /**
     * 校验薪资单信息金额字段信息
     * @param paidAmount
     * @param totalAmount
     * @return
     */
    public void checkAmount(BigDecimal paidAmount,BigDecimal totalAmount) {
        paidAmount = ObjectUtils.defaultIfNull(paidAmount, BigDecimal.ZERO);
        totalAmount = ObjectUtils.defaultIfNull(totalAmount, BigDecimal.ZERO);
        if (paidAmount.compareTo(totalAmount) != 0) {
            //汇款明细合计总金额和实发总金额必须相等
            throw new BusinessException("汇款明细合计总金额和实发总金额必须相等");
        }
    }

    /**
     * 校验：当前转介人对应的薪资月没有完成出账状态的记录，不能保存薪资单
     * @return
     */
    public void checkIsBilling(String brokerBizId,String month) {
        List<FortuneAccount> fortuneAccountList = fortuneAccountService.queryList(brokerBizId,month, FortuneAccountStatusEnum.SENT.getItemValue());
        if (CollectionUtils.isEmpty(fortuneAccountList)) {
            throw new BusinessException("当前转介人对应的薪资月没有完成出账状态的记录，不能保存薪资单");
        }
    }

    /**
     * 校验薪资单信息是否存在
     * @param salaryBizId
     */
    public Salary checkIsExist(String salaryBizId) {
        Salary salary = iSalaryService.queryOne(salaryBizId);
        if (salary == null) {
            throw new BusinessException("薪资单信息不存在");
        }
        return salary;
    }

}
