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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.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.*;
import com.yd.csf.service.enums.FortuneAccountStatusEnum;
import com.yd.csf.service.model.FortuneAccount;
import com.yd.csf.service.model.PolicyFollow;
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 com.yd.insurance.base.feign.client.usersaleexpand.ApiUserSaleExpandFeignClient;
import com.yd.insurance.base.feign.request.usersaleexpand.ApiUserSaleExpandListRequest;
import com.yd.insurance.base.feign.response.usersaleexpand.ApiUserSaleExpandDetailResponse;
import com.yd.oss.feign.client.ApiExcelFeignClient;
import com.yd.oss.feign.dto.ExportResult;
import com.yd.oss.feign.dto.SheetExportConfig;
import com.yd.oss.feign.request.MultiSheetExportRequest;
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.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
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;

    @Resource
    private ApiUserSaleExpandFeignClient apiUserSaleExpandFeignClient;

    @Autowired
    private ApiExcelFeignClient apiExcelFeignClient;

    /**
     * 分页查询-制作薪资单列表信息
     * @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);
        iPage = setPage(iPage);
        return Result.success(iPage);
    }

    /**
     * 设置分页结果返参
     * @param iPage
     * @return
     */
    public IPage<ApiSalaryPageResponse> setPage(IPage<ApiSalaryPageResponse> iPage) {
        if (CollectionUtils.isEmpty(iPage.getRecords())) {
            return iPage;
        }
        List<String> clientUserBizIdList = iPage.getRecords()
                .stream()
                .map(ApiSalaryPageResponse::getBrokerBizId)
                .distinct()
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(clientUserBizIdList)) {
            return iPage;
        }
        ApiUserSaleExpandListRequest request = new ApiUserSaleExpandListRequest();
        request.setClientUserBizIdList(clientUserBizIdList);
        Result<List<ApiUserSaleExpandDetailResponse>> result = apiUserSaleExpandFeignClient.list(request);
        if (CollectionUtils.isEmpty(result.getData())) {
            return iPage;
        }
        Map<String, ApiUserSaleExpandDetailResponse> policyFollowMap = result.getData().stream()
                .collect(Collectors.toMap(ApiUserSaleExpandDetailResponse::getClientUserBizId, Function.identity()));
        iPage.getRecords().forEach(d -> {
            ApiUserSaleExpandDetailResponse response = policyFollowMap.get(d.getBrokerBizId());
            if (response != null) {
                d.setTeamName(response.getTeamName());
                d.setInternalNumber(response.getInternalNumber());
            }
        });
        return 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 request
     * @return
     */
    @Override
    public Result<ApiSalaryConditionListResponse> conditionList(ApiSalaryConditionListRequest request) {
        // 1. 根据手机号查询转介人信息
        ApiUserSaleExpandListRequest listRequest = new ApiUserSaleExpandListRequest();
        listRequest.setPhone(request.getMobile());
        Result<List<ApiUserSaleExpandDetailResponse>> result = apiUserSaleExpandFeignClient.list(listRequest);
        if (CollectionUtils.isEmpty(result.getData())) {
            // 无转介人信息，返回空列表
            ApiSalaryConditionListResponse response = new ApiSalaryConditionListResponse();
            response.setCount(0);
            response.setConditionResponseList(Collections.emptyList());
            return Result.success(response);
        }
        // 取第一个转介人（通常一个手机号对应一个转介人）
        ApiUserSaleExpandDetailResponse userDetail = result.getData().get(0);
        String brokerBizId = userDetail.getClientUserBizId();
        if (StringUtils.isBlank(brokerBizId)) {
            // 转介人业务ID为空，返回空列表
            ApiSalaryConditionListResponse response = new ApiSalaryConditionListResponse();
            response.setCount(0);
            response.setConditionResponseList(Collections.emptyList());
            return Result.success(response);
        }

        // 2. 根据转介人业务ID和年份查询薪资单
        String year = request.getYear();
        LambdaQueryWrapper<Salary> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Salary::getBrokerBizId, brokerBizId)
                .likeRight(Salary::getMonth, year)   // month 为 yyyyMM，匹配前四位年份
                .eq(Salary::getIsDeleted, 0)
                .eq(Salary::getStatus, SalaryStatusEnum.YTS.getItemValue()) //已推送状态
                .orderByDesc(Salary::getMonth);      // 按月份降序排列（最近的在前面）

        List<Salary> salaryList = iSalaryService.list(wrapper);
        if (CollectionUtils.isEmpty(salaryList)) {
            ApiSalaryConditionListResponse response = new ApiSalaryConditionListResponse();
            response.setCount(0);
            response.setConditionResponseList(Collections.emptyList());
            return Result.success(response);
        }

        // 3. 转换为响应对象
        List<ApiSalaryConditionResponse> conditionResponseList = salaryList.stream()
                .map(salary -> {
                    ApiSalaryConditionResponse resp = new ApiSalaryConditionResponse();
                    resp.setSalaryBizId(salary.getSalaryBizId());
                    // 格式化月份为 "xxxx年MM月"
                    String month = salary.getMonth();
                    String yearMonth = formatYearMonth(month);
                    resp.setYearMonth(yearMonth);
                    resp.setPaidAmount(salary.getPaidAmount());
                    // 状态名称转换
//                    resp.setStatusName(getStatusDesc(salary.getStatus()));
                    resp.setStatusName("已发放");
                    return resp;
                })
                .collect(Collectors.toList());

        // 4. 组装最终响应
        ApiSalaryConditionListResponse response = new ApiSalaryConditionListResponse();
        response.setCount(conditionResponseList.size());
        response.setConditionResponseList(conditionResponseList);
        return Result.success(response);
    }

    /**
     * 将 yyyyMM 格式的月份转为 xxxx年MM月
     */
    private String formatYearMonth(String month) {
        if (StringUtils.isBlank(month) || month.length() != 6) {
            return month;
        }
        try {
            String year = month.substring(0, 4);
            String mm = month.substring(4, 6);
            return year + "年" + mm + "月";
        } catch (Exception e) {
            return month;
        }
    }

    /**
     * 电子薪资单-导出
     * @param request
     * @return
     */
    @Override
    public Result<ApiSalaryExportResponse> export(ApiSalaryExportRequest request) {
        // 1. 构建查询条件
        LambdaQueryWrapper<Salary> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Salary::getIsDeleted, 0);
        if (StringUtils.isNotBlank(request.getBrokerName())) {
            wrapper.like(Salary::getBrokerName, request.getBrokerName());
        }
        if (StringUtils.isNotBlank(request.getMonth())) {
            wrapper.eq(Salary::getMonth, request.getMonth());
        }
        if (StringUtils.isNotBlank(request.getStatus())) {
            wrapper.eq(Salary::getStatus, request.getStatus());
        }
        // 按创建时间排序（可选）
        wrapper.orderByAsc(Salary::getCreateTime);

        // 2. 查询薪资单列表
        List<Salary> salaryList = iSalaryService.list(wrapper);
        if (CollectionUtils.isEmpty(salaryList)) {
            throw new BusinessException("未查询到符合条件的薪资单数据");
        }

        // 3. 收集所有转介人业务ID，批量查询团队和内部编号（避免循环调用Feign）
        List<String> brokerBizIds = salaryList.stream()
                .map(Salary::getBrokerBizId)
                .filter(StringUtils::isNotBlank)
                .distinct()
                .collect(Collectors.toList());

        Map<String, ApiUserSaleExpandDetailResponse> expandMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(brokerBizIds)) {
            ApiUserSaleExpandListRequest expandRequest = new ApiUserSaleExpandListRequest();
            expandRequest.setClientUserBizIdList(brokerBizIds);
            Result<List<ApiUserSaleExpandDetailResponse>> expandResult =
                    apiUserSaleExpandFeignClient.list(expandRequest);
            if (expandResult != null && CollectionUtils.isNotEmpty(expandResult.getData())) {
                expandMap = expandResult.getData().stream()
                        .collect(Collectors.toMap(
                                ApiUserSaleExpandDetailResponse::getClientUserBizId,
                                Function.identity(),
                                (v1, v2) -> v1
                        ));
            }
        }

        // 4. 批量查询所有汇款明细（按 salaryBizId 分组）
        List<String> salaryBizIds = salaryList.stream()
                .map(Salary::getSalaryBizId)
                .collect(Collectors.toList());
        Map<String, List<SalaryRemittance>> remittanceMap =
                iSalaryRemittanceService.queryListBySalaryBizIds(salaryBizIds);

        // 5. 构建汇总总表数据（每个Salary一行）
        List<Map<String, Object>> summaryList = new ArrayList<>();
        // 6. 构建打款明细数据（每个Remittance一行）
        List<Map<String, Object>> detailList = new ArrayList<>();

        for (Salary salary : salaryList) {
            // 获取团队和内部编号
            String brokerBizId = salary.getBrokerBizId();
            String teamName = "";
            String internalNumber = salary.getInternalNumber() != null ? salary.getInternalNumber() : "";
            if (StringUtils.isNotBlank(brokerBizId) && expandMap.containsKey(brokerBizId)) {
                ApiUserSaleExpandDetailResponse detail = expandMap.get(brokerBizId);
                teamName = detail.getTeamName() != null ? detail.getTeamName() : "";
                if (StringUtils.isBlank(internalNumber)) {
                    internalNumber = detail.getInternalNumber() != null ? detail.getInternalNumber() : "";
                }
            }

            // --- 汇总行 ---
            Map<String, Object> summaryRow = new LinkedHashMap<>();
            summaryRow.put("发放编号", salary.getSalaryNo());
            summaryRow.put("所属团队", teamName);
            summaryRow.put("姓名", salary.getBrokerName());
            summaryRow.put("内部编号", internalNumber);
            summaryRow.put("薪资月份", formatMonth(salary.getMonth()));
            summaryRow.put("应发金额(HKD)", nullToZero(salary.getGrossAmount()));
            summaryRow.put("MPF(HK)", nullToZero(salary.getMpfAmount()));
            summaryRow.put("其他调整(HKD)", nullToZero(salary.getOtherAmount()));
            summaryRow.put("实发金额(HKD)", nullToZero(salary.getPaidAmount()));
            summaryRow.put("扣款合计", BigDecimal.ZERO); // 暂无扣款合计字段
            summaryRow.put("其他调整(HKD)", nullToZero(salary.getOtherAmount())); // 第二个
            summaryRow.put("备注", salary.getRemark() != null ? salary.getRemark() : "");
            List<SalaryRemittance> rems = remittanceMap.getOrDefault(salary.getSalaryBizId(), Collections.emptyList());
            summaryRow.put("打款账户数", rems.size());
            summaryRow.put("发放状态", getStatusDesc(salary.getStatus()));
            summaryRow.put("备注", salary.getRemark() != null ? salary.getRemark() : ""); // 第二个备注
            summaryRow.put("制作人", salary.getCreatorName() != null ? salary.getCreatorName() : "");
            summaryRow.put("制作时间", salary.getCreateTime() != null ?
                    salary.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) : "");
            summaryList.add(summaryRow);

            // --- 明细行 ---
            for (SalaryRemittance rem : rems) {
                Map<String, Object> detailRow = new LinkedHashMap<>();
                detailRow.put("发放编号", salary.getSalaryNo());
                detailRow.put("所属团队", teamName);
                detailRow.put("姓名", salary.getBrokerName());
                detailRow.put("内部编号", internalNumber);
                detailRow.put("薪资月份", formatMonth(salary.getMonth()));
                detailRow.put("收款银行", rem.getBank() != null ? rem.getBank() : "");
                detailRow.put("账户尾号", rem.getAccountEndNo() != null ? rem.getAccountEndNo() : "");
                detailRow.put("币种", rem.getCurrency() != null ? rem.getCurrency() : "");
                detailRow.put("发放金额", nullToZero(rem.getAmount()));
                detailRow.put("汇率", nullToZero(rem.getExchangeRate()));
                detailRow.put("备注", rem.getRemark() != null ? rem.getRemark() : "");
                detailList.add(detailRow);
            }
        }

        // 7. 组装多 Sheet 导出请求
        MultiSheetExportRequest exportRequest = new MultiSheetExportRequest();

        // Sheet1: 汇总总表
        SheetExportConfig sheet1 = new SheetExportConfig();
        sheet1.setSheetName("汇总总表");
        sheet1.setFieldNames(Arrays.asList(
                "发放编号", "所属团队", "姓名", "内部编号", "薪资月份",
                "应发金额(HKD)", "MPF(HK)", "其他调整(HKD)", "实发金额(HKD)",
                "扣款合计", "其他调整(HKD)", "备注", "打款账户数", "发放状态",
                "备注", "制作人", "制作时间"
        ));
        sheet1.setDataList(summaryList);

        // Sheet2: 打款明细
        SheetExportConfig sheet2 = new SheetExportConfig();
        sheet2.setSheetName("打款明细");
        sheet2.setFieldNames(Arrays.asList(
                "发放编号", "所属团队", "姓名", "内部编号", "薪资月份",
                "收款银行", "账户尾号", "币种", "发放金额", "汇率", "备注"
        ));
        sheet2.setDataList(detailList);

        exportRequest.setSheets(Arrays.asList(sheet1, sheet2));
        exportRequest.setFileName("电子薪资单_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
        exportRequest.setUploadToOss(true);

        // 8. 调用 Feign 导出
        Result<ExportResult> feignResult = apiExcelFeignClient.exportMultiSheet(exportRequest);
        if (feignResult == null || feignResult.getCode() != 200) {
            String errMsg = feignResult != null ? feignResult.getMsg() : "Feign调用失败";
            throw new BusinessException("导出失败: " + errMsg);
        }

        // 9. 返回 URL
        ApiSalaryExportResponse response = new ApiSalaryExportResponse();
        response.setUrl(feignResult.getData().getOssUrl());
        return Result.success(response);
    }

    /**
     * 将 yyyyMM 格式转为 yyyy-MM（便于阅读）
     */
    private String formatMonth(String month) {
        if (StringUtils.isBlank(month) || month.length() != 6) {
            return month;
        }
        try {
            return month.substring(0, 4) + "-" + month.substring(4, 6);
        } catch (Exception e) {
            return month;
        }
    }

    private BigDecimal nullToZero(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO;
    }

    /**
     * 状态码转中文描述
     * @param status 状态值（如 "1"）
     * @return 中文描述
     */
    private String getStatusDesc(String status) {
        if (status == null) {
            return "";
        }
        // 根据数据库注释：1-待提交 2-待核对 3-已核对 4-退回 5-已推送 6-已取消
        switch (status) {
            case "1": return "待提交";
            case "2": return "待核对";
            case "3": return "已核对";
            case "4": return "退回";
            case "5": return "已推送";
            case "6": return "已取消";
            default: return status;
        }
    }

    /**
     * 查询薪资单转介人列表信息
     * @return
     */
    @Override
    public Result<List<ApiSalaryBrokerListResponse>> brokerList(String name) {
        // 构造查询条件：状态为“完成出账”(2) 且 未删除
        QueryWrapper<FortuneAccount> wrapper = new QueryWrapper<>();
        wrapper.eq("status", FortuneAccountStatusEnum.SENT.getItemValue())
                .like(StringUtils.isNotBlank(name),"broker",name)
                .eq("is_deleted", 0);

        // 查询所有符合条件的出账记录
        List<FortuneAccount> accountList = fortuneAccountService.list(wrapper);
        if (CollectionUtils.isEmpty(accountList)) {
            return Result.success(Collections.emptyList());
        }

        // 按 brokerBizId 分组，收集月份（去重）
        Map<String, Set<String>> brokerMonthMap = new LinkedHashMap<>();
        Map<String, String> brokerNameMap = new HashMap<>();
        Set<String> bizIdSet = new HashSet<>();
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyyMM");

        for (FortuneAccount account : accountList) {
            String bizId = account.getBrokerBizId();
            String brokerName = account.getBroker();
            Date date = account.getFortuneAccountDate();

            if (StringUtils.isBlank(bizId) || StringUtils.isBlank(brokerName) || date == null) {
                continue; // 跳过无效数据
            }

            // 转换为 yyyyMM 格式
            LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            String month = localDate.format(monthFormatter);

            // 存储转介人名称（首次出现）
            brokerNameMap.putIfAbsent(bizId, brokerName);
            bizIdSet.add(bizId);

            // 收集月份（自动去重）
            brokerMonthMap.computeIfAbsent(bizId, k -> new LinkedHashSet<>()).add(month);
        }

        //获取转介人详细详细列表
        List<String> bizIdList = bizIdSet.stream().collect(Collectors.toList());
        Map<String, ApiUserSaleExpandDetailResponse> policyFollowMap = null;
        if (CollectionUtils.isNotEmpty(bizIdList)) {
            ApiUserSaleExpandListRequest request = new ApiUserSaleExpandListRequest();
            request.setClientUserBizIdList(bizIdList);
            Result<List<ApiUserSaleExpandDetailResponse>> result = apiUserSaleExpandFeignClient.list(request);
            if (CollectionUtils.isNotEmpty(result.getData())) {
                policyFollowMap = result.getData().stream()
                        .collect(Collectors.toMap(ApiUserSaleExpandDetailResponse::getClientUserBizId, Function.identity()));
            }
        }
        // 组装响应对象
        List<ApiSalaryBrokerListResponse> resultList = new ArrayList<>();
        for (Map.Entry<String, Set<String>> entry : brokerMonthMap.entrySet()) {
            String bizId = entry.getKey();
            Set<String> monthSet = entry.getValue();

            ApiSalaryBrokerListResponse response = new ApiSalaryBrokerListResponse();
            if (policyFollowMap != null) {
                ApiUserSaleExpandDetailResponse detailResponse = policyFollowMap.get(bizId);
                if (detailResponse != null) {
                    //内部编号
                    response.setInternalNumber(detailResponse.getInternalNumber());
                }
            }
            response.setBrokerBizId(bizId);
            response.setBroker(brokerNameMap.getOrDefault(bizId, ""));
            // 月份列表按时间升序排序
            List<String> sortedMonths = new ArrayList<>(monthSet);
            Collections.sort(sortedMonths);
            response.setMonthList(sortedMonths);

            resultList.add(response);
        }

        // 最终按转介人名称排序
        resultList.sort(Comparator.comparing(ApiSalaryBrokerListResponse::getBroker,
                Comparator.nullsLast(String::compareTo)));

        return Result.success(resultList);
    }

    /**
     * 批量校验同一转介人同一年月只能有一条薪资单记录
     * @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;
    }

}
