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

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yd.common.constant.RedisConstants;
import com.yd.common.enums.CommonEnum;
import com.yd.common.enums.ProjectEnum;
import com.yd.common.enums.TenantEnum;
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.ApiExpectedFortunePageRequest;
import com.yd.csf.feign.request.expectedfortune.ApiGenerateExpectedFortuneRequest;
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.FortuneStatusEnum;
import com.yd.csf.service.model.CommissionRuleBinding;
import com.yd.csf.service.model.ExpectedFortune;
import com.yd.csf.service.model.PolicyBroker;
import com.yd.csf.service.service.ICommissionRuleBindingService;
import com.yd.csf.service.service.IExpectedFortuneService;
import com.yd.csf.service.service.PolicyBrokerService;
import com.yd.csf.service.service.PolicyService;
import com.yd.user.feign.client.clientuser.ApiClientUserFeignClient;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
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.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@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;

    /**
     * 生成预计发佣
     * @param request
     * @return
     */
    @Override
    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("绑定基本法数据不存在");
        }

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

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

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

        // 使用编程式事务，确保异步方法内的事务一致性
        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());
                        }
                    }
                }
                //执行成功完毕，也要销毁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());
            }
        });
    }

    /**
     * 执行 - 奖励（非销售佣金基本法）
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Result executeReward(ExecuteBillingDto 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(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());

        //生成保单预计发佣表记录 (非销售佣金基本法)
        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())
                //所属团队 TODO
//                .team()
                //所属团队业务ID TODO
//                .teamBizId()
                .build());

        return Result.success();
    }

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

    /**
     * 执行 - 销售佣金（销售佣金基本法）
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Result executeBilling(ExecuteBillingDto 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());

        //生成保单预计发佣表记录 (销售佣金基本法)
        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())
                //所属团队 TODO
//                .team()
                //所属团队业务ID TODO
//                .teamBizId()
                .build());

        return Result.success();
    }

    /**
     * 生成保单预计发佣表记录
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public Result generateExpectedFortune(GenerateExpectedFortuneDto fortuneDto) {
        List<ExpectedFortune> fortuneList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(fortuneDto.getAlgorithmResDtoList())) {
            //获得积分业务员绑定的基本法列表对应计算值结果集合
            for (AlgorithmResDto algorithmResDto : fortuneDto.getAlgorithmResDtoList()) {
                if (Objects.isNull(algorithmResDto.getCalculatedValue())
                        || algorithmResDto.getCalculatedValue().compareTo(BigDecimal.ZERO) == 0){
                    //判空判0
                    continue;
                }
                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.setBroker(fortuneDto.getBroker());
                //转介人业务ID (获得积分的业务员)
                fortune.setBrokerBizId(fortuneDto.getBrokerBizId());
                //出账项目名称 -> 基本法项目名称
                fortune.setFortuneName(algorithmResDto.getItemName());
                //保单预计发佣业务id
                fortune.setExpectedFortuneBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_EXPECTED_FORTUNE.getCode()));
                //基本法项目配置表唯一业务ID
                fortune.setRuleItemBizId(algorithmResDto.getRuleItemBizId());
                //发佣币种
                fortune.setCurrency(fortuneDto.getCurrency());
                //发佣期数
                fortune.setFortunePeriod(fortuneDto.getFortunePeriod());
                //发佣总期数
                fortune.setFortuneTotalPeriod(fortuneDto.getFortuneTotalPeriod());
                //保单号
                fortune.setPolicyNo(fortuneDto.getPolicyNo());

                // 标准发佣金额 -> 计算值
                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);
            }
        }

        if (!CollectionUtils.isEmpty(fortuneList)) {
            iExpectedFortuneService.saveOrUpdateBatch(fortuneList);
        }

        return Result.success();
    }

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

    /**
     * 查询保单是否生成过预计发佣
     * @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();
    }
}
