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

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yd.auth.core.dto.AuthUserDto;
import com.yd.auth.core.utils.SecurityUtil;
import com.yd.base.feign.client.exchangerate.ApiExchangeRateFeignClient;
import com.yd.common.constant.RedisConstants;
import com.yd.common.enums.*;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.common.utils.RedisUtil;
import com.yd.csf.api.dto.*;
import com.yd.csf.api.service.ApiBasicLawCalculateService;
import com.yd.csf.api.service.ApiExpectedFortuneLogService;
import com.yd.csf.api.service.ApiExpectedFortuneService;
import com.yd.csf.feign.request.expectedfortune.*;
import com.yd.csf.feign.response.expectedfortune.ApiExpectedFortunePageResponse;
import com.yd.csf.feign.response.expectedfortune.ApiGenerateExpectedFortuneResponse;
import com.yd.csf.service.dto.CommissionRuleBindingDto;
import com.yd.csf.service.dto.QueryPolicyAndBrokerDto;
import com.yd.csf.service.dto.QueryPolicyBrokerDto;
import com.yd.csf.service.enums.CurrencyEnum;
import com.yd.csf.service.enums.FortuneStatusEnum;
import com.yd.csf.service.helper.FeignResultHelper;
import com.yd.csf.service.model.*;
import com.yd.csf.service.service.*;
import com.yd.csf.service.vo.ExpectedFortuneExportDTO;
import com.yd.csf.service.vo.ExpectedFortuneStatisticsVO;
import com.yd.csf.service.vo.PayableReportVO;
import com.yd.product.feign.client.announcementcommissionratio.ApiAnnouncementCommissionRatioFeignClient;
import com.yd.product.feign.request.announcementcommissionratio.ApiAnnouncementCommissionRatioListRequest;
import com.yd.product.feign.response.announcementcommissionratio.ApiAnnouncementCommissionRatioListResponse;
import com.yd.user.feign.client.clientuser.ApiClientUserFeignClient;
import com.yd.user.feign.client.sysdict.ApiSysDictFeignClient;
import com.yd.user.feign.response.sysdict.GetDictItemListByDictTypeResponse;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
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.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ibm.icu.impl.ValidIdentifiers.Datatype.currency;

@Slf4j
@Service
public class ApiExpectedFortuneServiceImpl implements ApiExpectedFortuneService {
    @Autowired
    private PolicyService policyService;
    @Autowired
    private ApiClientUserFeignClient apiClientUserFeignClient;
    @Autowired
    private ICommissionRuleBindingService iCommissionRuleBindingService;
    @Autowired
    private ApiBasicLawCalculateService apiBasicLawCalculateService;
    @Autowired
    private IExpectedFortuneService iExpectedFortuneService;
    @Autowired
    private ApplicationContext applicationContext;

    private ApiExpectedFortuneService getSelf() {
        return applicationContext.getBean(ApiExpectedFortuneService.class);
    }
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private ApiExpectedFortuneLogService apiExpectedFortuneLogService;
    @Autowired
    private PolicyBrokerService policyBrokerService;
    @Autowired
    private ApiSysDictFeignClient apiSysDictFeignClient;
    @Resource
    private PolicyFollowService policyFollowService;
    @Resource
    private ApiExchangeRateFeignClient apiExchangeRateFeignClient;
    @Resource
    private CommissionExpectedService commissionExpectedService;
    @Resource
    private ApiAnnouncementCommissionRatioFeignClient apiAnnouncementCommissionRatioFeignClient;
    @Resource
    private FeignResultHelper feignResultHelper;
    @Resource
    private ApiAnnouncementCommissionRatioFeignClient ratioFeignClient;

    /**
     * 生成预计发佣
     *
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<ApiGenerateExpectedFortuneResponse> generate(ApiGenerateExpectedFortuneRequest request) {
        //查询当前保单号是否正在执行预计发佣的缓存，有值就说明正在执行，无值说明执行完毕或者没有执行。
        String value = redisUtil.getCacheObject(RedisConstants.EXPECTED_FORTUNE + request.getPolicyNo());
        if (StringUtil.isNotBlank(value)) {
            //有值正在执行，提示
            return Result.fail("生成预计发佣正在执行中，无需再次执行");
        } else {
            //无值设置值，表示正在执行
            redisUtil.setCacheObject(RedisConstants.EXPECTED_FORTUNE + request.getPolicyNo(), request.getPolicyNo());
        }
        List<ExpectedFortune> list = iExpectedFortuneService.queryList(request.getPolicyNo());
        if (!CollectionUtils.isEmpty(list)) {
            throw new BusinessException("当前保单已经生成过预计发佣数据，无需再次生成");
        }
        //根据保单号查询保单和转介人列表信息
        List<QueryPolicyAndBrokerDto> queryPolicyAndBrokerDtoList = policyService.queryPolicyBrokerList(request.getPolicyNo());
        if (CollectionUtils.isEmpty(queryPolicyAndBrokerDtoList)) {
            throw new BusinessException("保单和转介人信息不存在");
        }

        //查询所有绑定基本法的业务员(客户端用户表用户), 计算业务员绑定的所有基本法对应的计算值, 新增积分明细和发佣记录
        List<CommissionRuleBinding> commissionRuleBindingList = new ArrayList<>();
        //根据租户ID和项目ID查询客户端用户ID列表
        Result<List<String>> result = apiClientUserFeignClient.clientUserBizIdList(TenantEnum.YD.getTenantBizId(), ProjectEnum.CSF_MINI_PROGRAM.getProjectBizId());
        if (!CollectionUtils.isEmpty(result.getData())) {
            commissionRuleBindingList = iCommissionRuleBindingService.queryList(CommissionRuleBindingDto.builder()
                    .targetIdList(result.getData())
                    .build());
        }
        if (CollectionUtils.isEmpty(commissionRuleBindingList)) {
            throw new BusinessException("绑定基本法数据不存在");
        }

        //查询产品发佣配置，获取结算币种
        QueryPolicyAndBrokerDto queryPolicyAndBrokerDto = queryPolicyAndBrokerDtoList.get(0);
        ApiAnnouncementCommissionRatioListRequest announcementRatioListRequest = new ApiAnnouncementCommissionRatioListRequest();
        announcementRatioListRequest.setProductLaunchBizId(queryPolicyAndBrokerDto.getPlanBizId());
        announcementRatioListRequest.setEffectiveDate(Date.from(queryPolicyAndBrokerDto.getEffectiveDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
        announcementRatioListRequest.setPaymentTerm(String.valueOf(queryPolicyAndBrokerDto.getPaymentTerm()));

        List<ApiAnnouncementCommissionRatioListResponse> announcementRatioList;
        try {
            announcementRatioList = feignResultHelper.extractListData(
                    apiAnnouncementCommissionRatioFeignClient.list(announcementRatioListRequest),
                    ApiAnnouncementCommissionRatioListResponse.class
            );
        } catch (Exception e) {
            log.info("查询结算币种->保单号:{}，入参request：{}", request.getPolicyNo(), JSON.toJSONString(announcementRatioListRequest));

            throw new BusinessException("未查询到产品发佣配置，查询条件：" + JSON.toJSONString(announcementRatioListRequest));
        }

        //遍历保单转介人列表信息 -> 调用基本法算出预计发佣列表
        // 通过代理对象调用
        getSelf().execute(queryPolicyAndBrokerDtoList, commissionRuleBindingList, request.getPolicyNo(), announcementRatioList);

        return Result.success(null, "生成预计发佣正在处理....，稍后查看预计发佣列表");
    }

    /**
     * 同步生成预计发佣
     *
     * @param request
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<ApiGenerateExpectedFortuneResponse> generateSync(ApiGenerateExpectedFortuneRequest request) {
        //查询当前保单号是否正在执行预计发佣的缓存，有值就说明正在执行，无值说明执行完毕或者没有执行。
        String value = redisUtil.getCacheObject(RedisConstants.EXPECTED_FORTUNE + request.getPolicyNo());
        if (StringUtil.isNotBlank(value)) {
            //有值正在执行，提示
            return Result.fail("生成预计发佣正在执行中，无需再次执行");
        } else {
            //无值设置值，表示正在执行
            redisUtil.setCacheObject(RedisConstants.EXPECTED_FORTUNE + request.getPolicyNo(), request.getPolicyNo());
        }
        List<ExpectedFortune> list = iExpectedFortuneService.queryList(request.getPolicyNo());
        if (!CollectionUtils.isEmpty(list)) {
            throw new BusinessException("当前保单已经生成过预计发佣数据，无需再次生成");
        }
        //根据保单号查询保单和转介人列表信息
        List<QueryPolicyAndBrokerDto> queryPolicyAndBrokerDtoList = policyService.queryPolicyBrokerList(request.getPolicyNo());
        if (CollectionUtils.isEmpty(queryPolicyAndBrokerDtoList)) {
            throw new BusinessException("保单和转介人信息不存在");
        }

//        //查询所有绑定基本法的业务员(客户端用户表用户), 计算业务员绑定的所有基本法对应的计算值, 新增积分明细和发佣记录
//        List<CommissionRuleBinding> commissionRuleBindingList = new ArrayList<>();
//        //根据租户ID和项目ID查询客户端用户ID列表
//        Result<List<String>> result = apiClientUserFeignClient.clientUserBizIdList(TenantEnum.YD.getTenantBizId(), ProjectEnum.CSF_MINI_PROGRAM.getProjectBizId());
//        if (!CollectionUtils.isEmpty(result.getData())) {
//            commissionRuleBindingList = iCommissionRuleBindingService.queryList(CommissionRuleBindingDto.builder()
//                    .targetIdList(result.getData())
//                    .build());
//        }
//        if (CollectionUtils.isEmpty(commissionRuleBindingList)) {
//            throw new BusinessException("绑定基本法数据不存在");
//        }

        //查询产品发佣配置，获取结算币种
        QueryPolicyAndBrokerDto queryPolicyAndBrokerDto = queryPolicyAndBrokerDtoList.get(0);
        ApiAnnouncementCommissionRatioListRequest announcementRatioListRequest = new ApiAnnouncementCommissionRatioListRequest();
        announcementRatioListRequest.setProductLaunchBizId(queryPolicyAndBrokerDto.getPlanBizId());
        announcementRatioListRequest.setEffectiveDate(Date.from(queryPolicyAndBrokerDto.getEffectiveDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
        announcementRatioListRequest.setPaymentTerm(String.valueOf(queryPolicyAndBrokerDto.getPaymentTerm()));

        HttpServletRequest request1 = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request1.getHeader("Authorization");
        log.info("HttpServletRequest token==============,{}",token);
        List<ApiAnnouncementCommissionRatioListResponse> announcementRatioList;
        try {
            announcementRatioList = feignResultHelper.extractListData(
                    apiAnnouncementCommissionRatioFeignClient.list(announcementRatioListRequest),
                    ApiAnnouncementCommissionRatioListResponse.class
            );
        } catch (Exception e) {
            log.info("查询结算币种->保单号:{}，入参request：{}", request.getPolicyNo(), JSON.toJSONString(announcementRatioListRequest));
            throw new BusinessException("未查询到产品发佣配置，查询条件：" + JSON.toJSONString(announcementRatioListRequest));
        }

        log.info("查询产品发佣配置，获取结算币种==========>，{}",JSON.toJSONString(announcementRatioList));

        Integer paymentTerm = 0;
        PolicyFollow policyFollow = policyFollowService.queryOneByPolicyNo(request.getPolicyNo());
        if (policyFollow != null) {
            ApiAnnouncementCommissionRatioListRequest ratioListRequest = new ApiAnnouncementCommissionRatioListRequest();
            ratioListRequest.setEffectiveDate(policyFollow.getEffectiveDate());
            ratioListRequest.setPaymentTerm(policyFollow.getIssueNumber().toString()); // 保留原有过滤条件（可根据业务决定是否需要）
            ratioListRequest.setProductLaunchBizId(policyFollow.getProductLaunchBizId());
            Result<List<ApiAnnouncementCommissionRatioListResponse>> listResult = ratioFeignClient.list(ratioListRequest);
            if (listResult != null && !CollectionUtils.isEmpty(listResult.getData())) {
                // 取 endPeriod 的最大值作为总期数
                paymentTerm = listResult.getData().stream()
                        .map(item -> {
                            try {
                                return Integer.parseInt(item.getEndPeriod()); // 将字符串转为整数
                            } catch (NumberFormatException e) {
                                // 若转换失败，记录日志并返回0，避免影响最大值计算
                                log.warn("endPeriod 格式错误: {}", item.getEndPeriod());
                                return 0;
                            }
                        })
                        .max(Integer::compareTo)
                        .orElse(0); // 若无有效值，默认0
            }
        }


        //遍历保单转介人列表信息 -> 调用基本法算出预计发佣列表（同步执行）
        executeSync(queryPolicyAndBrokerDtoList, request.getPolicyNo(), announcementRatioList,paymentTerm);

        return Result.success(null, "生成预计发佣成功");
    }

    /**
     * 生成预计发佣（包含日志记录和Redis处理）
     * 用于内部服务调用，会自动保存日志和异常时销毁Redis缓存
     *
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<ApiGenerateExpectedFortuneResponse> generateWithLogAndRedis(ApiGenerateExpectedFortuneRequest request) {
        try {
            Result<ApiGenerateExpectedFortuneResponse> result = generate(request);
            // 保存预计发佣日志记录（成功）
            apiExpectedFortuneLogService.saveExpectedFortuneLog(ApiExpectedFortuneLogDto.builder()
                    .errorMsg(result.getMsg())
                    .policyNo(request.getPolicyNo())
                    .status(2)
                    .build());
            return result;
        } catch (Exception e) {
            // 抛出异常，销毁redis缓存
            try {
                redisUtil.deleteObject(RedisConstants.EXPECTED_FORTUNE + request.getPolicyNo());
            } catch (Exception redisEx) {
                log.error("删除Redis缓存失败: {}", redisEx.getMessage(), redisEx);
            }
            // 保存预计发佣日志记录（失败）
            try {
                apiExpectedFortuneLogService.saveExpectedFortuneLog(ApiExpectedFortuneLogDto.builder()
                        .errorMsg(e.getMessage())
                        .policyNo(request.getPolicyNo())
                        .status(1)
                        .build());
            } catch (Exception logEx) {
                log.error("保存预计发佣日志失败: {}", logEx.getMessage(), logEx);
            }
            // 重新抛出原始异常，确保上层事务能够回滚
            throw e;
        }
    }

    /**
     * 同步生成预计发佣（包含日志记录和Redis处理）
     * 用于需要同步等待完成的场景
     *
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<ApiGenerateExpectedFortuneResponse> generateSyncWithLogAndRedis(ApiGenerateExpectedFortuneRequest request) {
        try {
            Result<ApiGenerateExpectedFortuneResponse> result = generateSync(request);
            // 保存预计发佣日志记录（成功）
            apiExpectedFortuneLogService.saveExpectedFortuneLog(ApiExpectedFortuneLogDto.builder()
                    .errorMsg(result.getMsg())
                    .policyNo(request.getPolicyNo())
                    .status(2)
                    .build());
            return result;
        } catch (Exception e) {
            // 抛出异常，销毁redis缓存
            try {
                redisUtil.deleteObject(RedisConstants.EXPECTED_FORTUNE + request.getPolicyNo());
            } catch (Exception redisEx) {
                log.error("删除Redis缓存失败: {}", redisEx.getMessage(), redisEx);
            }
            // 保存预计发佣日志记录（失败）
            try {
                apiExpectedFortuneLogService.saveExpectedFortuneLog(ApiExpectedFortuneLogDto.builder()
                        .errorMsg(e.getMessage())
                        .policyNo(request.getPolicyNo())
                        .status(1)
                        .build());
            } catch (Exception logEx) {
                log.error("保存预计发佣日志失败: {}", logEx.getMessage(), logEx);
            }
            // 重新抛出原始异常，确保上层事务能够回滚
            throw e;
        }
    }

    /**
     * 异步处理-> 遍历保单转介人列表信息 -> 调用基本法算出预计发佣列表
     *
     * @param queryPolicyAndBrokerDtoList
     * @param commissionRuleBindingList
     * @return
     */
    @Override
    @Async("commonAsyncExecutor")
    @Transactional(rollbackFor = Exception.class)
    public Result execute(List<QueryPolicyAndBrokerDto> queryPolicyAndBrokerDtoList,
                          List<CommissionRuleBinding> commissionRuleBindingList,
                          String policyNo,
                          List<ApiAnnouncementCommissionRatioListResponse> announcementRatioList) {

        // 使用编程式事务，确保异步方法内的事务一致性
        return transactionTemplate.execute(status -> {
            try {
                for (QueryPolicyAndBrokerDto brokerDto : queryPolicyAndBrokerDtoList) {
                    Integer paymentTerm = brokerDto.getPaymentTerm();
                    if (Objects.isNull(paymentTerm)) {
                        throw new BusinessException("保单的供款年期不存在");
                    }

                    for (int i = 1; i <= paymentTerm; i++) {
                        executeBilling(ExecuteBillingDto.builder()
                                .name(brokerDto.getBrokerName())
                                .policyAndBrokerDto(brokerDto)
                                .issueNumber(i)
                                .build());

                        for (CommissionRuleBinding binding : commissionRuleBindingList) {
                            executeReward(ExecuteBillingDto.builder()
                                    .clientUserBizId(binding.getTargetId())
                                    .name(binding.getTargetName())
                                    .policyAndBrokerDto(brokerDto)
                                    .issueNumber(i)
                                    .build());
                        }
                    }
                }
                //批量设置应付款编号、发佣类型
                List<ExpectedFortune> expectedFortuneList = updatePayableNoBatch(policyNo);

                //默认结算汇率、港币金额
                String policyCurrency = queryPolicyAndBrokerDtoList.get(0).getCurrency();
                updateHkdAmountBatch(expectedFortuneList, policyCurrency, announcementRatioList);

                //执行成功完毕，也要销毁redis缓存
                redisUtil.deleteObject(RedisConstants.EXPECTED_FORTUNE + policyNo);

                //保存预计发佣日志记录
                //成功日志
                apiExpectedFortuneLogService.saveExpectedFortuneLog(ApiExpectedFortuneLogDto.builder()
                        .errorMsg("")
                        .policyNo(policyNo)
                        .status(0)
                        .build());
                return Result.success();
            } catch (Exception e) {
                //抛出异常，销毁redis缓存
                redisUtil.deleteObject(RedisConstants.EXPECTED_FORTUNE + policyNo);
                status.setRollbackOnly(); // 标记回滚
                log.error("异步执行预计发佣失败，已回滚所有操作", e);
                //保存预计发佣日志记录
                apiExpectedFortuneLogService.saveExpectedFortuneLog(ApiExpectedFortuneLogDto.builder()
                        .errorMsg(e.getMessage())
                        .policyNo(policyNo)
                        .status(1)
                        .build());
                throw new BusinessException("异步处理失败: " + e.getMessage());
            }
        });
    }

    @Transactional(rollbackFor = Exception.class)
    private void updateHkdAmountBatch(List<ExpectedFortune> expectedFortuneList, String policyCurrency, List<ApiAnnouncementCommissionRatioListResponse> announcementRatioList) {

        log.info("------------------------开始计算默认结算汇率、港币金额----------------------");

        if (!CollectionUtils.isEmpty(announcementRatioList)) {
            ApiAnnouncementCommissionRatioListResponse announcementRatio = announcementRatioList.get(0);
            String currency = announcementRatio.getCurrency();

            log.info("结算币种：{}", currency);

            BigDecimal exchangeRate = queryExchangeRateByFeign(policyCurrency, currency);

            log.info("结算汇率：{}", exchangeRate);

            for (ExpectedFortune expectedFortune : expectedFortuneList) {
                //设置结算货币
                expectedFortune.setCurrency(currency);
                //设置默认结算汇率
                expectedFortune.setDefaultExchangeRate(exchangeRate);
                //设置港币金额（标准发佣金额 * 默认结算汇率）
                expectedFortune.setHkdAmount(expectedFortune.getAmount().multiply(exchangeRate));
                //默认值
                expectedFortune.setPaidAmount(BigDecimal.ZERO);
                expectedFortune.setPaidRatio(BigDecimal.ZERO);
                expectedFortune.setUnpaidAmount(expectedFortune.getHkdAmount());
                expectedFortune.setUnpaidRatio(BigDecimal.valueOf(100));

                iExpectedFortuneService.updateById(expectedFortune);
            }
        }
    }

    @Override
    public Result executeSync(List<QueryPolicyAndBrokerDto> queryPolicyAndBrokerDtoList, List<CommissionRuleBinding> commissionRuleBindingList, String policyNo) {
        return null;
    }

    /**
     * 同步处理-> 遍历保单转介人列表信息 -> 调用基本法算出预计发佣列表
     *
     * @param queryPolicyAndBrokerDtoList
     * @param policyNo
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Result executeSync(List<QueryPolicyAndBrokerDto> queryPolicyAndBrokerDtoList,
                              String policyNo, List<ApiAnnouncementCommissionRatioListResponse> announcementRatioList,Integer paymentTerm) {
        log.info("同步处理-> 遍历保单转介人列表信息 -> 调用基本法算出预计发佣列表->入参queryPolicyAndBrokerDtoList：{}",JSON.toJSONString(queryPolicyAndBrokerDtoList));
        log.info("同步处理-> 遍历保单转介人列表信息 -> 调用基本法算出预计发佣列表->入参policyNo：{}",policyNo);
        // 使用编程式事务，确保方法内的事务一致性
        return transactionTemplate.execute(status -> {
            try {

                for (QueryPolicyAndBrokerDto brokerDto : queryPolicyAndBrokerDtoList) {
//                    Integer paymentTerm = brokerDto.getPaymentTerm();
                    if (paymentTerm == null || paymentTerm == 0) {
                        throw new BusinessException("保单的供款年期不存在");
                    }

                    List<CommissionRuleBinding> commissionRuleBindingList = brokerRelUserIdList(brokerDto);
                    log.info("同步处理-> 遍历保单转介人列表信息 -> 调用基本法算出预计发佣列表->commissionRuleBindingList：{}",JSON.toJSONString(commissionRuleBindingList));
                    for (int i = 1; i <= paymentTerm; i++) {
                        executeBilling(ExecuteBillingDto.builder()
                                .name(brokerDto.getBrokerName())
                                .policyAndBrokerDto(brokerDto)
                                .issueNumber(i)
                                .build());

                        if (!CollectionUtils.isEmpty(commissionRuleBindingList)) {
                            for (CommissionRuleBinding binding : commissionRuleBindingList) {
                                executeReward(ExecuteBillingDto.builder()
                                        .clientUserBizId(binding.getTargetId())
                                        .name(binding.getTargetName())
                                        .policyAndBrokerDto(brokerDto)
                                        .issueNumber(i)
                                        .build());
                            }
                        }
                    }
                }
                //批量设置应付款编号
                List<ExpectedFortune> expectedFortuneList = updatePayableNoBatch(policyNo);

                //默认结算汇率、港币金额
                String policyCurrency = queryPolicyAndBrokerDtoList.get(0).getCurrency();
                updateHkdAmountBatch(expectedFortuneList, policyCurrency, announcementRatioList);;

                //执行成功完毕，也要销毁redis缓存
                redisUtil.deleteObject(RedisConstants.EXPECTED_FORTUNE + policyNo);

                //保存预计发佣日志记录
                //成功日志
                apiExpectedFortuneLogService.saveExpectedFortuneLog(ApiExpectedFortuneLogDto.builder()
                        .errorMsg("")
                        .policyNo(policyNo)
                        .status(0)
                        .build());
                return Result.success();
            } catch (Exception e) {
                //抛出异常，销毁redis缓存
                redisUtil.deleteObject(RedisConstants.EXPECTED_FORTUNE + policyNo);
                status.setRollbackOnly(); // 标记回滚
                log.error("同步执行预计发佣失败，已回滚所有操作", e);
                //保存预计发佣日志记录
                apiExpectedFortuneLogService.saveExpectedFortuneLog(ApiExpectedFortuneLogDto.builder()
                        .errorMsg(e.getMessage())
                        .policyNo(policyNo)
                        .status(1)
                        .build());
                throw new BusinessException("同步处理失败: " + e.getMessage());
            }
        });
    }

    /**
     * 查询和当前转介人相关关系（推荐，一级管理，二级管理，辅导）的客户端用户ID列表数据
     * @param brokerDto
     * @return
     */
    public List<CommissionRuleBinding> brokerRelUserIdList(QueryPolicyAndBrokerDto brokerDto) {
        List<CommissionRuleBinding> commissionRuleBindingList = new ArrayList<>();
        List<String> userIdList = policyBrokerService.selectRelatedBrokerBizIds(brokerDto.getBrokerBizId());
        if (!CollectionUtils.isEmpty(userIdList)) {
            commissionRuleBindingList = iCommissionRuleBindingService.queryList(CommissionRuleBindingDto.builder()
                    .targetIdList(userIdList)
                    .build());
        }
        return commissionRuleBindingList;
     }

    @Transactional(rollbackFor = Exception.class)
    private List<ExpectedFortune> updatePayableNoBatch(String policyNo) {
        // 查询最新一条有 payableNo 记录
        ExpectedFortune latest = iExpectedFortuneService.getOne(
                new QueryWrapper<ExpectedFortune>().isNotNull("payable_no").orderByDesc("id").last("LIMIT 1"),
                true
        );
        //查询当前保单号的所有预计发佣记录
        List<ExpectedFortune> expectedFortuneList = iExpectedFortuneService.queryList(policyNo);

        //查询redis缓存的字典列表信息
        List<GetDictItemListByDictTypeResponse> dictTypeResponses = redisUtil.getCacheObject(RedisConstants.DICT_LIST);

        //获取当前序号作为起点
        int currentSeq = 0;
        if (!Objects.isNull(latest)) {
            currentSeq = Integer.parseInt(latest.getPayableNo().substring(12));
        }

        //批量更新应付款编号，每个item递增
        ExpectedFortune expectedFortune;
        for (int i = 0; i < expectedFortuneList.size(); i++) {
            expectedFortune = expectedFortuneList.get(i);
            expectedFortune.setPayableNo(this.createPayableNo("R", currentSeq + i + 1));
            expectedFortune.setFortuneType(GetDictItemListByDictTypeResponse.getItemValue(dictTypeResponses,
                    "csf_fortune_type", expectedFortune.getFortuneName()));
        }
        iExpectedFortuneService.updateBatchById(expectedFortuneList);

        return expectedFortuneList;
    }

    /**
     * 执行 - 奖励（非销售佣金基本法）
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Result executeReward(ExecuteBillingDto dto) {
        QueryPolicyAndBrokerDto brokerDto = dto.getPolicyAndBrokerDto();
        //构造销售佣金基本法项目的顺序下标值执行
        List<Integer> executionOrderList = new ArrayList<>();
        executionOrderList.add(1);
        log.info("执行获得积分用户的非销售佣金基本法项目->入参：{}",JSON.toJSONString(dto));
        //执行获得积分用户的非销售佣金基本法项目
        Result<List<AlgorithmResDto>> result = apiBasicLawCalculateService.policyBrokerAlgorithm(AlgorithmDto.builder()
                .brokerBizId(brokerDto.getBrokerBizId())
                .clientUserBizId(dto.getClientUserBizId())
                .sqlTemplateParamDto(SqlTemplateParamDto.builder()
                        .brokerBizId(brokerDto.getBrokerBizId())
                        .clientUserBizId(dto.getClientUserBizId())
                        .policyNo(brokerDto.getPolicyNo())
                        .productBizId(brokerDto.getPlanBizId())
                        .productCode(brokerDto.getProductCode())
                        .term(brokerDto.getPaymentTerm())
                        .issueNumber(dto.getIssueNumber())
                        .build())
                .executionOrderList(executionOrderList)
                .isNegateExecutionOrderList(true)
                .build());
        log.info("执行获得积分用户的非销售佣金基本法项目->出参：{}",JSON.toJSONString(result));

        //生成保单预计发佣表记录 (非销售佣金基本法)
        generateExpectedFortune(GenerateExpectedFortuneDto.builder()
                //获得积分的用户绑定的基本法列表对应计算值
                .algorithmResDtoList(result.getData())
                //保单发佣批次ID
                //获得积分的用户姓名
                .broker(dto.getName())
                //获得积分的用户业务ID
                .brokerBizId(dto.getClientUserBizId())
                //发佣币种
                .currency(brokerDto.getCurrency())
                //发佣期数
                .fortunePeriod(dto.getIssueNumber())
                //发佣总期数
                .fortuneTotalPeriod(brokerDto.getPaymentTerm())
                //发佣类型 TODO
//                .fortuneType()
                //发佣日期 保单生效日期 + 3个月
                .payoutDate(calculateExpectedPayoutDate(brokerDto.getEffectiveDate(), dto.getIssueNumber()))
                //保单号
                .policyNo(brokerDto.getPolicyNo())
                //佣金发放状态 0=待出账 1=可出账 2=已出账
                .status(FortuneStatusEnum.WAIT.getItemValue())
                //期交保费
                .paymentPremium(brokerDto.getPaymentPremium())
                //所属团队
                .team(brokerDto.getTeam())
                //所属团队业务ID
                .teamBizId(brokerDto.getTeamBizId())
                //产品业务id
                .productLaunchBizId(brokerDto.getPlanBizId())
                //保险公司业务id
                .insuranceCompanyBizId(brokerDto.getInsuranceCompanyBizId())
                .source(2)
                .build());

        return Result.success();
    }

    private static LocalDate calculateExpectedPayoutDate(LocalDate effectiveDate, Integer issueNumber) {
        return effectiveDate.plusMonths(2).plusYears(issueNumber - 1);
    }

    /**
     * 执行 - 销售佣金（销售佣金基本法）
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Result executeBilling(ExecuteBillingDto dto) {
        log.info("执行 - 销售佣金（销售佣金基本法）——>入参：{}", JSON.toJSONString(dto));
        QueryPolicyAndBrokerDto brokerDto = dto.getPolicyAndBrokerDto();
        //构造销售佣金基本法项目的顺序下标值执行
        List<Integer> executionOrderList = new ArrayList<>();
        executionOrderList.add(1);

        //保单出账先执行销售佣金基本法项目，入完到积分明细表里后，再执行保单绑定人的其他基本法项目
        Result<List<AlgorithmResDto>> result = apiBasicLawCalculateService.policyBrokerAlgorithm(AlgorithmDto.builder()
                .brokerBizId(brokerDto.getBrokerBizId())
                .clientUserBizId(brokerDto.getBrokerBizId())
                .sqlTemplateParamDto(SqlTemplateParamDto.builder()
                        .brokerBizId(brokerDto.getBrokerBizId())
                        .policyNo(brokerDto.getPolicyNo())
                        .productBizId(brokerDto.getPlanBizId())
                        .productCode(brokerDto.getProductCode())
                        .term(brokerDto.getPaymentTerm())
                        .issueNumber(dto.getIssueNumber())
                        .build())
                .executionOrderList(executionOrderList)
                .isNegateExecutionOrderList(false)
                .build());

        log.info("执行 - 销售佣金（销售佣金基本法）——>brokerDto：{}", JSON.toJSONString(brokerDto));
        log.info("执行 - 销售佣金（销售佣金基本法）——>policyBrokerAlgorithm——>result：{}", JSON.toJSONString(result));
        //生成保单预计发佣表记录 (销售佣金基本法)
        generateExpectedFortune(GenerateExpectedFortuneDto.builder()
                //转介人绑定的基本法列表对应计算值
                .algorithmResDtoList(result.getData())
                //保单发佣批次ID
                //获得积分的转介人（业务员）姓名
                .broker(dto.getName())
                //获得积分的转介人（业务员）业务ID
                .brokerBizId(brokerDto.getBrokerBizId())
                //发佣币种
                .currency(brokerDto.getCurrency())
                //发佣期数
                .fortunePeriod(dto.getIssueNumber())
                //发佣总期数
                .fortuneTotalPeriod(brokerDto.getPaymentTerm())
                //发佣类型 TODO
//                .fortuneType()
                //发佣日期
                .payoutDate(calculateExpectedPayoutDate(brokerDto.getEffectiveDate(), dto.getIssueNumber()))
                //保单号
                .policyNo(brokerDto.getPolicyNo())
                //佣金发放状态 0=待出账 1=可出账 2=已出账
                .status(FortuneStatusEnum.WAIT.getItemValue())
                //期交保费
                .paymentPremium(brokerDto.getPaymentPremium())
                //所属团队
                .team(brokerDto.getTeam())
                //所属团队业务ID
                .teamBizId(brokerDto.getTeamBizId())
                //产品业务id
                .productLaunchBizId(brokerDto.getPlanBizId())
                //保险公司业务id
                .insuranceCompanyBizId(brokerDto.getInsuranceCompanyBizId())
                .source(1)
                .build());

        return Result.success();
    }

    /**
     * 生成保单预计发佣表记录
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Result generateExpectedFortune(GenerateExpectedFortuneDto fortuneDto) {
        log.info("生成保单预计发佣表记录——>入参：{}", JSON.toJSONString(fortuneDto));
        List<ExpectedFortune> fortuneList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(fortuneDto.getAlgorithmResDtoList())) {
            //获得积分业务员绑定的基本法列表对应计算值结果集合
            for (AlgorithmResDto algorithmResDto : fortuneDto.getAlgorithmResDtoList()) {
                if (fortuneDto.getSource() == 2 && (Objects.isNull(algorithmResDto.getCalculatedValue())
                        || algorithmResDto.getCalculatedValue().compareTo(BigDecimal.ZERO) == 0)) {
                    //2-非销售佣金并且计算金额为0，走这里
                    //判空判0
                    continue;
                }
                log.info("生成保单预计发佣表记录——>入参algorithmResDto：{}", JSON.toJSONString(algorithmResDto));
                PolicyBroker policyBroker = policyBrokerService.queryOne(QueryPolicyBrokerDto.builder()
                        .policyNo(fortuneDto.getPolicyNo())
                        .brokerBizId(fortuneDto.getBrokerBizId())
                        .build());
                ExpectedFortune fortune = new ExpectedFortune();
                //发佣金额 -> 计算值 - sqlAlgorithmResultDtoList集合里面的计算值和值
//                fortune.setAmount(algorithmResDto.getCalculatedValue());
                //保单发佣批次ID
//                fortune.setBatchBizId(fortuneDto.getBatchBizId());
                fortune.setFortuneBizType("R");
                fortune.setPolicyCurrency(fortuneDto.getCurrency());
                fortune.setPremium(fortuneDto.getPaymentPremium());
                //转介人姓名 (获得积分的业务员)
                fortune.setBroker(fortuneDto.getBroker());
                //转介人业务ID (获得积分的业务员)
                fortune.setBrokerBizId(fortuneDto.getBrokerBizId());
                //所属团队
                fortune.setTeam(fortuneDto.getTeam());
                //所属团队业务ID
                fortune.setTeamBizId(fortuneDto.getTeamBizId());
                //产品业务id
                fortune.setProductLaunchBizId(fortuneDto.getProductLaunchBizId());
                //保险公司业务id
                fortune.setInsuranceCompanyBizId(fortuneDto.getInsuranceCompanyBizId());
                //出账项目名称 -> 基本法项目名称
                fortune.setFortuneName(algorithmResDto.getItemName());
                //保单预计发佣业务id
                fortune.setExpectedFortuneBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_EXPECTED_FORTUNE.getCode()));
                //基本法项目配置表唯一业务ID
                fortune.setRuleItemBizId(algorithmResDto.getRuleItemBizId());
                //保单币种
                fortune.setPolicyCurrency(fortuneDto.getCurrency());
                //发佣期数
                fortune.setFortunePeriod(fortuneDto.getFortunePeriod());
                //发佣总期数
                fortune.setFortuneTotalPeriod(fortuneDto.getFortuneTotalPeriod());
                //保单号
                fortune.setPolicyNo(fortuneDto.getPolicyNo());
                //预计发佣日期
                fortune.setPayoutDate(fortuneDto.getPayoutDate());

                // 标准发佣金额 -> 计算值
                BigDecimal standardAmount = algorithmResDto.getCalculatedValue();
                fortune.setStandardAmount(standardAmount);

                // 转介人介绍费占比
                String brokerRatioStr = !Objects.isNull(policyBroker) ? policyBroker.getBrokerRatio() : "100";
                fortune.setBrokerRatio(brokerRatioStr);

                try {
                    // 将百分比字符串转换为小数
                    BigDecimal ratio = new BigDecimal(brokerRatioStr);
                    // 除以100
                    BigDecimal decimalRatio = ratio.divide(new BigDecimal("100"), 4, BigDecimal.ROUND_HALF_UP);

                    // 计算实际预计发佣金额 = 标准发佣金额 × decimalRatio
                    BigDecimal actualAmount = standardAmount.multiply(decimalRatio);
                    fortune.setAmount(actualAmount);

                } catch (NumberFormatException e) {
                    // 如果比例不是有效的数字，使用标准金额
                    fortune.setAmount(standardAmount);
                } catch (Exception e) {
                    // 其他异常处理
                    log.error("计算发佣金额异常: {}", brokerRatioStr, e);
                    fortune.setAmount(standardAmount);
                }

                fortuneList.add(fortune);
            }
        }

        log.info("生成保单预计发佣表记录——>出参fortuneList：{}", JSON.toJSONString(fortuneList));
        if (!CollectionUtils.isEmpty(fortuneList)) {
            iExpectedFortuneService.saveOrUpdateBatch(fortuneList);
        }

        return Result.success();
    }

    @Override
    public Result<ApiExpectedFortunePageResponseVO> list(ApiExpectedFortunePageRequest request) {
        Page<ExpectedFortune> page = new Page<>(request.getPageNo(), request.getPageSize());

        QueryWrapper<ExpectedFortune> queryWrapper = this.getQueryWrapper(request);
        IPage<ExpectedFortune> iPage = iExpectedFortuneService.page(page, queryWrapper);
        // 查询统计数据
        List<ExpectedFortune> fortuneList = iExpectedFortuneService.list(queryWrapper);
        ExpectedFortuneStatisticsVO statisticsVO = this.getStatistics(fortuneList.stream().map(ExpectedFortune::getId).collect(Collectors.toList()));

        // 组装返回结果
        ApiExpectedFortunePageResponseVO response = new ApiExpectedFortunePageResponseVO();
        response.setStatisticsVO(statisticsVO);
        response.setPage(iExpectedFortuneService.getVOPage(iPage));
        return Result.success(response);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(List<ExpectedFortuneAddRequest> fortuneAddRequestList) {
        if (CollectionUtils.isEmpty(fortuneAddRequestList)) {
            return true;
        }
        validateAdd(fortuneAddRequestList);

        // 查询保单信息
        Set<String> policyNoSet = fortuneAddRequestList.stream()
                .map(ExpectedFortuneAddRequest::getPolicyNo)
                .filter(StringUtils::isNotBlank).collect(Collectors.toSet());

        Map<String, Policy> policyMap = new HashMap<>();
        if (CollUtil.isNotEmpty(policyNoSet)) {
            List<Policy> policyList = policyService.lambdaQuery().in(Policy::getPolicyNo, policyNoSet).list();
            // 保单映射
            policyMap = policyList.stream().collect(Collectors.toMap(Policy::getPolicyNo, Function.identity()));
        }

        // 查询最新一条有 payableNo 记录
        ExpectedFortune latest = iExpectedFortuneService.getOne(
                new QueryWrapper<ExpectedFortune>().isNotNull("payable_no").orderByDesc("id").last("LIMIT 1"),
                true
        );
        //获取当前序号作为起点
        int currentSeq = 0;
        if (!Objects.isNull(latest)) {
            currentSeq = Integer.parseInt(latest.getPayableNo().substring(12));
        }

        List<ExpectedFortune> fortuneList = new ArrayList<>();
        for (ExpectedFortuneAddRequest expectedFortuneDto : fortuneAddRequestList) {
            ExpectedFortune expectedFortune = new ExpectedFortune();
            BeanUtil.copyProperties(expectedFortuneDto, expectedFortune);

            if ("R".equals(expectedFortuneDto.getFortuneBizType())) {
                Policy policy = policyMap.get(expectedFortuneDto.getPolicyNo());
                if (ObjectUtils.isEmpty(policy)) {
                    throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "保单号为" + expectedFortuneDto.getPolicyNo() + "的保单不存在");
                }

                // 设置关联字段
                expectedFortune.setInsuranceCompanyBizId(policy.getInsuranceCompanyBizId());
                expectedFortune.setProductLaunchBizId(policy.getProductLaunchBizId());
                expectedFortune.setPremium(policy.getPaymentPremium());
                expectedFortune.setPolicyCurrency(policy.getCurrency());
            }

            // 预计发佣业务id
            expectedFortune.setExpectedFortuneBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_EXPECTED_FORTUNE.getCode()));
            // 预计发佣类型名称
            expectedFortune.setFortuneName(queryByDict(expectedFortuneDto.getFortuneType()));
            // 应付款编号（序号递增）
            expectedFortune.setPayableNo(this.createPayableNo(expectedFortune.getFortuneBizType(), ++currentSeq));
            // 默认结算汇率
            expectedFortune.setDefaultExchangeRate(
                    queryExchangeRateByFeign(expectedFortune.getPolicyCurrency(), expectedFortune.getCurrency()));
            // 计算港币金额
            expectedFortune.setHkdAmount(expectedFortune.getAmount().multiply(expectedFortune.getDefaultExchangeRate()));
            // 已出帐金额、待出帐金额、已出帐比例、待出帐比例
            expectedFortune.setPaidAmount(BigDecimal.ZERO);
            // 转介人比例默认100%
            expectedFortune.setBrokerRatio("100");
            expectedFortune.setUnpaidAmount(expectedFortune.getAmount());
            expectedFortune.setPaidRatio(BigDecimal.ZERO);
            expectedFortune.setUnpaidRatio(BigDecimal.valueOf(100));
            fortuneList.add(expectedFortune);
        }
        iExpectedFortuneService.saveOrUpdateBatch(fortuneList);
        return true;
    }

    private String queryByDict(String fortuneType) {
        //查询redis缓存的字典列表信息
        List<GetDictItemListByDictTypeResponse> dictTypeResponses = redisUtil.getCacheObject(RedisConstants.DICT_LIST);
        String fortuneName = GetDictItemListByDictTypeResponse.getItemLabel(dictTypeResponses,
                "csf_fortune_type", fortuneType);
        if (ObjectUtils.isNotEmpty(fortuneName)) {
            return fortuneName;
        }
        Result<List<GetDictItemListByDictTypeResponse>> result = apiSysDictFeignClient.getDictItemListByDictType("csf_fortune_type");
        if (ObjectUtils.isNotEmpty(result.getData())) {
            for (GetDictItemListByDictTypeResponse dictItem : result.getData()) {
                if (StringUtils.equalsIgnoreCase(dictItem.getItemValue(), fortuneType)) {
                    return dictItem.getItemLabel();
                }
            }
        }
        return null;
    }

    /**
     * 保单币种对预计来佣的结算币种的默认汇率
     * @param policyCurrency 保单币种
     * @param currency 预计来佣的结算币种
     * @return 汇率
     */
    private BigDecimal queryExchangeRateByFeign(String policyCurrency, String currency) {
        if (policyCurrency.equalsIgnoreCase(currency)) {
            return BigDecimal.ONE;
        }
        // 调用Feign客户端查询汇率
        Result<BigDecimal> result = apiExchangeRateFeignClient.getExchangeRate(policyCurrency, currency, "");
        if (result != null && result.getData() != null) {
            return result.getData();
        }
        return null;
    }

    private void validateAdd(List<ExpectedFortuneAddRequest> fortuneAddRequestList) {
        for (ExpectedFortuneAddRequest expectedFortuneDto : fortuneAddRequestList) {
            if (Objects.isNull(expectedFortuneDto.getAmount())) {
                throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "预计出账金额不能为空");
            }
            if (Objects.isNull(expectedFortuneDto.getCurrency())) {
                throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "预计出账币种不能为空");
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void exportExpectedFortune(ApiExpectedFortunePageRequest request, HttpServletResponse response) {
        // 查询数据
        QueryWrapper<ExpectedFortune> queryWrapper = this.getQueryWrapper(request);
        List<ExpectedFortune> fortuneList = iExpectedFortuneService.list(queryWrapper);
        // 先转换为VO, VO 补充了关联信息（产品计划、产品险种、保险公司、用户）
        List<ApiExpectedFortunePageResponse> expectedFortuneVOList = iExpectedFortuneService.toVOList(fortuneList);
        // 转换为导出DTO
        List<ExpectedFortuneExportDTO> exportDataList = expectedFortuneVOList.stream()
                .map(ExpectedFortuneExportDTO::convertToExportDTO)
                .collect(Collectors.toList());

        // 使用EasyExcel导出
        try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExpectedFortuneExportDTO.class).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet("应收款数据").build();
            excelWriter.write(exportDataList, writeSheet);
        } catch (Exception e) {
            throw new RuntimeException("导出应收款数据失败", e);
        }
    }

    @Override
    public Result<PayableReportResponse> payableReport(ApiExpectedFortunePageRequest request) {
        // 构建查询条件
        QueryWrapper<ExpectedFortune> queryWrapper = this.getQueryWrapper(request);

        // 先查询所有符合条件的记录ID（用于统计）
        List<ExpectedFortune> allFortuneList = iExpectedFortuneService.list(queryWrapper);
        List<Long> allFortuneIdList = allFortuneList.stream().map(ExpectedFortune::getId).collect(Collectors.toList());

        // 查询统计数据（基于所有符合条件的记录）
        ExpectedFortuneStatisticsVO statisticsVO = this.getStatistics(allFortuneIdList);

        // 应付款报表分页查询 - 按保单号和期数维度统计
        Page<PayableReportVO> reportPage = new Page<>(request.getPageNo(), request.getPageSize());
        IPage<PayableReportVO> payableReportPage = new Page<>(request.getPageNo(), request.getPageSize());
        if (!CollectionUtils.isEmpty(allFortuneIdList)) {
            payableReportPage = iExpectedFortuneService.payableReportPage(reportPage, allFortuneIdList);
        }

        payableReportPage = convertPayableReportVO(payableReportPage);
        // 组装返回结果
        PayableReportResponse response = new PayableReportResponse();
        response.setStatisticsVO(statisticsVO);
        response.setPage(payableReportPage);
        return Result.success(response);
    }

    public IPage<PayableReportVO> convertPayableReportVO(IPage<PayableReportVO> payableReportPage) {
        if (!CollectionUtils.isEmpty(payableReportPage.getRecords())) {
            List<String> policyNoList = payableReportPage.getRecords().stream().map(PayableReportVO::getPolicyNo).collect(Collectors.toList());
            List<Policy> policyList = new ArrayList<>();
            List<PolicyFollow> policyFollowList = new ArrayList<>();
            if (!CollectionUtils.isEmpty(policyNoList)) {
                policyList = policyService.lambdaQuery()
                        .in(Policy::getPolicyNo, policyNoList)
                        .list();
                policyFollowList = policyFollowService.lambdaQuery()
                        .in(PolicyFollow::getPolicyNo, policyNoList)
                        .list();
            }
            Map<String, Policy> policyMap = policyList.stream()
                    .collect(Collectors.toMap(Policy::getPolicyNo, Function.identity()));
            Map<String, PolicyFollow> policyFollowMap = policyFollowList.stream()
                    .collect(Collectors.toMap(PolicyFollow::getPolicyNo, Function.identity()));
            List<PayableReportVO> voList = payableReportPage.getRecords().stream().map(vo -> {
                Policy policy = policyMap.get(vo.getPolicyNo());
                PolicyFollow policyFollow= policyFollowMap.get(vo.getPolicyNo());

                vo.setCurrency(CurrencyEnum.toLabel(vo.getCurrency()));
                if (policy != null) {
                    vo.setPolicyCurrency(CurrencyEnum.toLabel(policy.getCurrency()));
                }
                if (policyFollow != null) {
                    vo.setProductName(policyFollow.getProductName());
                    vo.setProductLaunchBizId(policyFollow.getProductLaunchBizId());
                }
                return vo;
            }).collect(Collectors.toList());
            payableReportPage.setRecords(voList);
        }
        return payableReportPage;
    }

    /**
     * 创建应付款编号 应付款类型-CSF-年份后两位-6位数字（不重复）
     *
     * @param fortuneBizType 预计发佣业务类型
     * @param seq            序号
     * @return
     */
    private String createPayableNo(String fortuneBizType, int seq) {
        return String.format("%s%s%s",
                fortuneBizType + "-CSF",
                LocalDate.now().getYear() % 100,
                String.format("%06d", seq));
    }

    /**
     * 分页查询 - 预计发佣
     *
     * @param request
     * @return
     */
    @Override
    public Result<IPage<ApiExpectedFortunePageResponse>> page(ApiExpectedFortunePageRequest request) {
        Page<ExpectedFortune> page = new Page<>(request.getPageNo(), request.getPageSize());

        QueryWrapper<ExpectedFortune> queryWrapper = this.getQueryWrapper(request);
        IPage<ExpectedFortune> iPage = iExpectedFortuneService.page(page, queryWrapper);

        return Result.success(iPage.convert(ApiExpectedFortuneServiceImpl::transform));
    }

    public static ApiExpectedFortunePageResponse transform(ExpectedFortune expectedFortune) {
        ApiExpectedFortunePageResponse response = new ApiExpectedFortunePageResponse();
        BeanUtil.copyProperties(expectedFortune, response);
        return response;
    }

    @Override
    public QueryWrapper<ExpectedFortune> getQueryWrapper(ApiExpectedFortunePageRequest request) {
        QueryWrapper<ExpectedFortune> queryWrapper = new QueryWrapper<>();
        if (request == null) {
            return queryWrapper;
        }
        // 获取参数
        String payableNo = request.getPayableNo();
        String policyNo = request.getPolicyNo();
        LocalDate payoutDateStart = request.getPayoutDateStart();
        LocalDate payoutDateEnd = request.getPayoutDateEnd();
        List<String> statusList = request.getStatusList();
        Integer fortunePeriod = request.getFortunePeriod();
        String fortuneType = request.getFortuneType();
        String team = request.getTeam();
        List<String> teamBizIdList = request.getTeamBizIdList();
        List<String> insuranceCompanyBizIdList = request.getInsuranceCompanyBizIdList();
        List<String> productLaunchBizIdList = request.getProductLaunchBizIdList();
        List<String> brokerBizIdList = request.getBrokerBizIdList();
        List<String> signerBizIdList = request.getSignerBizIdList();
        String fortuneBizType = request.getFortuneBizType();

        // 按保单号模糊查询
        queryWrapper.eq(ObjectUtils.isNotEmpty(payableNo), "payable_no", payableNo);
        queryWrapper.like(StringUtils.isNotBlank(policyNo), "policy_no", policyNo);
        queryWrapper.ge(ObjectUtils.isNotEmpty(payoutDateStart), "payout_date", payoutDateStart);
        queryWrapper.le(ObjectUtils.isNotEmpty(payoutDateEnd), "payout_date", payoutDateEnd);
        queryWrapper.in(ObjectUtils.isNotEmpty(statusList), "status", statusList);
        queryWrapper.eq(ObjectUtils.isNotEmpty(fortunePeriod), "fortune_period", fortunePeriod);
        queryWrapper.eq(StringUtils.isNotBlank(fortuneType), "fortune_type", fortuneType);
        queryWrapper.like(StringUtils.isNotBlank(team), "team", team);
        queryWrapper.in(ObjectUtils.isNotEmpty(teamBizIdList), "team_biz_id", teamBizIdList);
        queryWrapper.in(ObjectUtils.isNotEmpty(insuranceCompanyBizIdList), "insurance_company_biz_id", insuranceCompanyBizIdList);
        queryWrapper.in(ObjectUtils.isNotEmpty(productLaunchBizIdList), "product_launch_biz_id", productLaunchBizIdList);
        queryWrapper.in(ObjectUtils.isNotEmpty(brokerBizIdList), "broker_biz_id", brokerBizIdList);
        queryWrapper.in(ObjectUtils.isNotEmpty(signerBizIdList), "signer_biz_id", signerBizIdList);
        queryWrapper.eq(StringUtils.isNotBlank(fortuneBizType), "fortune_biz_type", fortuneBizType);

        // 按id升序排序
        queryWrapper.orderByAsc("id");
        return queryWrapper;
    }

    /**
     * 查询保单是否生成过预计发佣
     *
     * @param policyNo
     * @return
     */
    @Override
    public Result<Boolean> isGenerate(String policyNo) {
        List<ExpectedFortune> list = iExpectedFortuneService.queryList(policyNo);
        if (!CollectionUtils.isEmpty(list)) {
            throw new BusinessException("当前保单已经生成过预计发佣数据，无需再次生成");
        }
        return Result.success();
    }

    /**
     * 根据保单号批量删除预计发佣数据
     *
     * @param policyNo
     * @return
     */
    @Override
    public Result batchDelByPolicyNo(String policyNo) {
        iExpectedFortuneService.batchDelByPolicyNo(policyNo);
        return Result.success();
    }

    @Override
    public ExpectedFortuneStatisticsVO getStatistics(List<Long> expectedFortuneIds) {
        if (CollectionUtils.isEmpty(expectedFortuneIds)) {
            ExpectedFortuneStatisticsVO statisticsVO = new ExpectedFortuneStatisticsVO();
            statisticsVO.setTotalExpectedAmount(BigDecimal.ZERO);
            statisticsVO.setTotalPaidAmount(BigDecimal.ZERO);
            statisticsVO.setTotalUnpaidAmount(BigDecimal.ZERO);
            statisticsVO.setPaidAmountRatio(BigDecimal.ZERO);
            statisticsVO.setTotalPolicyCount(0);
            return statisticsVO;
        }
        // 自定义统计数据
        ExpectedFortuneStatisticsVO statisticsVO = iExpectedFortuneService.getStatistics(expectedFortuneIds);

        BigDecimal totalAmount = statisticsVO.getTotalExpectedAmount();
        BigDecimal totalPaidAmount = statisticsVO.getTotalPaidAmount();
        // 计算待出账金额
        statisticsVO.setTotalUnpaidAmount(totalAmount.subtract(totalPaidAmount));
        // 计算已出账比例（已出账金额/预计发佣金额）
        BigDecimal divided = BigDecimal.ZERO;
        if (totalAmount.compareTo(BigDecimal.ZERO) > 0) {
            divided = totalPaidAmount.divide(totalAmount, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100));
        }
        statisticsVO.setPaidAmountRatio(divided);

        return statisticsVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(ExpectedFortuneUpdateRequest request) {
        String expectedFortuneBizId = request.getExpectedFortuneBizId();
        ExpectedFortune expectedFortune = iExpectedFortuneService.getOne(new QueryWrapper<ExpectedFortune>().eq("expected_fortune_biz_id", expectedFortuneBizId));
        if (expectedFortune == null) {
            throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "预计发佣数据不存在");
        }
        if (expectedFortune.getPaidAmount().compareTo(BigDecimal.ZERO) > 0) {
            throw new BusinessException(ResultCode.FAIL.getCode(), "当前应付款已有出账金额，不能修改");
        }

        ExpectedFortune updateExpectedFortune = new ExpectedFortune();
        BeanUtils.copyProperties(request, updateExpectedFortune);
        updateExpectedFortune.setId(expectedFortune.getId());
        // 默认结算汇率
        if (updateExpectedFortune.getDefaultExchangeRate() == null) {
            updateExpectedFortune.setDefaultExchangeRate(
                    queryExchangeRateByFeign(
                            updateExpectedFortune.getPolicyCurrency(),
                            updateExpectedFortune.getCurrency()
                    ));
        }
        // 计算港币金额
        updateExpectedFortune.setHkdAmount(updateExpectedFortune.getAmount().multiply(updateExpectedFortune.getDefaultExchangeRate()));

        // 获取当前登录用户
        AuthUserDto currentLoginUser = SecurityUtil.getCurrentLoginUser();
        String loginUserId = currentLoginUser.getId().toString();

        updateExpectedFortune.setUpdaterId(loginUserId);
        updateExpectedFortune.setUpdateTime(LocalDateTime.now());
        iExpectedFortuneService.updateById(updateExpectedFortune);

        return true;
    }
}
