package com.tzld.piaoquan.ad.engine.service.score.strategy;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.tzld.piaoquan.ad.engine.commons.dto.AdPlatformCreativeDTO;
import com.tzld.piaoquan.ad.engine.commons.enums.CrowdLayerEnum;
import com.tzld.piaoquan.ad.engine.commons.enums.FilterTypeEnum;
import com.tzld.piaoquan.ad.engine.commons.enums.RedisPrefixEnum;
import com.tzld.piaoquan.ad.engine.commons.helper.CreativeKFinalDataHelper;
import com.tzld.piaoquan.ad.engine.commons.helper.LayerAgentFlowControlDataHelper;
import com.tzld.piaoquan.ad.engine.commons.helper.LayerCustomerFlowControlDataHelper;
import com.tzld.piaoquan.ad.engine.commons.param.RankRecommendRequestParam;
import com.tzld.piaoquan.ad.engine.commons.redis.AdRedisHelper;
import com.tzld.piaoquan.ad.engine.commons.redis.AlgorithmRedisHelper;
import com.tzld.piaoquan.ad.engine.commons.score.ScoreParam;
import com.tzld.piaoquan.ad.engine.commons.thread.ThreadPoolFactory;
import com.tzld.piaoquan.ad.engine.commons.util.DateUtils;
import com.tzld.piaoquan.ad.engine.commons.util.ObjUtil;
import com.tzld.piaoquan.ad.engine.commons.util.RootSessionIdExpConfig;
import com.tzld.piaoquan.ad.engine.commons.util.RootSessionIdTailUtil;
import com.tzld.piaoquan.ad.engine.service.entity.*;
import com.tzld.piaoquan.ad.engine.service.feature.Feature;
import com.tzld.piaoquan.ad.engine.service.feature.FeatureService;
import com.tzld.piaoquan.recommend.feature.domain.ad.base.AdRankItem;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
public abstract class RankStrategyBasic implements RankStrategy {

    @ApolloJsonValue("${no.postback.conversion.adverids:[]}")
    private Set<String> noPostbackConversionAdVerIds;

    @ApolloJsonValue("${creative.model.score.coefficient:{}}")
    private Map<Long, Double> creativeScoreCoefficient;

    @ApolloJsonValue("${h5.score.suppress:{}}")
    private Map<String, Double> h5ScoreSuppress;

    @ApolloJsonValue("${rerank.strategy.flow.ctl.customer:{}}")
    private Map<String, Double> flowCtlCustomerParams;

    @ApolloJsonValue("${rerank.strategy.flow.ctl.agent:{}}")
    private Map<String, Double> flowCtlAgentParams;

    @ApolloJsonValue("${rerank.root.session.id.tail.config:[]}")
    private List<RootSessionIdExpConfig> rerankRootSessionIdConfig;

    @ApolloJsonValue("${h5.score.suppress.customer.white:[]}")
    private Set<Long> customerIdWhiteSet;

    @Value("${rank.strategy.default.cxr:0.000001}")
    protected Double defaultCxr;

    @Value("${guarantee.exp:742}")
    protected String guaranteeExp;

    @ApolloJsonValue("${alpha:1.0}")
    protected Double alpha;

    @Value("${calibration.ctcvr.exp:779}")
    protected String calibrationCtcvrExp;

    @Value("${correct.cpa.exp.1:}")
    protected String correctCpaExp1;

    @Value("${correct.cpa.exp.2:}")
    protected String correctCpaExp2;

    @Value("${correct.cpa.alpha.1:1.0}")
    protected Double correctCpaAlpha1;

    @Value("${correct.cpa.beta.1:1.0}")
    protected Double correctCpaBeta1;

    @Value("${correct.cpa.alpha.2:0.7}")
    protected Double correctCpaAlpha2;

    @Value("${correct.cpa.beta.2:0.2}")
    protected Double correctCpaBeta2;

    @Value("${correct.cpa.view.hour:300}")
    protected Integer correctCpaViewHour;

    @Value("${correct.cpa.view.day:300}")
    protected Integer correctCpaViewDay;

    @Value("${correct.cpa.min.ctcvr:0.000001}")
    protected Double minCtcvr;

    @Value("${exclude.exp:772}")
    protected String excludeExp;

    @Value("${target.crowd.exclude.exp:785}")
    protected String targetCrowdExcludeExp;


    @Value("${calibration.view:2000}")
    protected Integer calibrationView;

    @Value("${calibration.alpha:0.5}")
    protected Double calibrationAlpha;

    @Value("${guarantee.weight:30}")
    protected Integer guaranteeWeight;

    @Value("${guarantee.switching.time:1754236800000}")
    protected Long guaranteeSwitchingTime;

    // 保量人群加权系数配置
    @Value("${guarantee.crowd.weight.coefficient:1.0}")
    protected Double guaranteeCrowdWeightCoefficient;

    @Value("${checkout.ecpm.exp:790}")
    protected String checkoutEcpmExp;

    @Value("${filter.ecpm:60}")
    protected String filterEcpm;

    @Value("${calibration.profession.exp:792}")
    protected String calibrationProfessionExp;

    @Value("${checkout.layer:false}")
    protected Boolean checkoutLayer;

    @Value("${filter.config.value:[]}")
    protected String filterConfigValue;

    @Value("${feature.branch.config.value:500}")
    protected Integer featureBranchConfigValue;

    @Autowired
    private FeatureService featureService;
    @Autowired
    protected AdRedisHelper adRedisHelper;
    @Autowired
    protected AlgorithmRedisHelper algRedisHelper;

    String adPlatformGuaranteeKey = "ad:platform:guarantee:data:{date}:{adverId}";

    String realLayerCtcvrKey = "ad:platform:real:ctcvr:{model}:{layer}:{class}";

    String realLayerCtcvrV2Key = "ad:platform:real:ctcvr:v2:{model}:{layer}:{class}";

    String adLayerHourKey = "ad:platform:ad:hour:{layer}:{clazz}:{adId}";

    String adLayerDayKey = "ad:platform:ad:day:{layer}:{clazz}:{adId}";

    String userLayerDataKey = "ad:platform:{layer}:{class}:{model}:{type}:{value}";

    String userLayerDataV2Key = "ad:platform:v2:{layer}:{class}:{model}:{type}:{value}";


    private static final double DEFAULT_CORRECTION = 1.0;


    protected static final List<String> hasChannelScenes = new ArrayList<String>() {{
        add("DaiTou");
        add("GzhTouLiu");
        add("daitou");
        add("dyyjs");
        add("dyyqw");
        add("fwhdyy");
        add("gzhhz");
        add("gzhhzzx");
        add("shequn");
        add("touliu");
        add("xcxdt");
    }};


//    protected Feature getFeature(ScoreParam param, RankRecommendRequestParam request) {
//        List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
//        List<String> cidList = adIdList.stream()
//                .map(AdPlatformCreativeDTO::getCreativeId)
//                .map(Object::toString)
//                .collect(Collectors.toList());
//
//        List<String> adVerIdList = adIdList.stream()
//                .map(AdPlatformCreativeDTO::getAdVerId)
//                .filter(StringUtils::isNotBlank)
//                .distinct()
//                .collect(Collectors.toList());
//
//        List<Long> skuIdList = adIdList.stream()
//                .map(AdPlatformCreativeDTO::getSkuId)
//                .filter(Objects::nonNull)
//                .distinct()
//                .collect(Collectors.toList());
//        return featureService.getFeature(cidList, adVerIdList, skuIdList, param);
//    }

    protected Feature getFeature(ScoreParam param, RankRecommendRequestParam request) {
        List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
        log.info("getFeature adIdListSize:{}", adIdList.size());
        Feature finalFeature = null;

        // 分批处理 AdPlatformCreativeDTO 列表
        List<List<AdPlatformCreativeDTO>> adIdBatches = partitionList(adIdList, featureBranchConfigValue);

        // 使用 ThreadPoolFactory 获取 DEFAULT 线程池
        ExecutorService executorService = ThreadPoolFactory.defaultPool();  // 使用 DEFAULT 线程池
        List<Callable<Feature>> tasks = new ArrayList<>();

        for (List<AdPlatformCreativeDTO> batch : adIdBatches) {
            // 提取批次中的 cidList、adVerIdList 和 skuIdList
            List<String> cidList = batch.stream()
                    .map(AdPlatformCreativeDTO::getCreativeId)
                    .map(Object::toString)
                    .collect(Collectors.toList());

            List<String> adVerIdList = batch.stream()
                    .map(AdPlatformCreativeDTO::getAdVerId)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.toList());

            List<Long> skuIdList = batch.stream()
                    .map(AdPlatformCreativeDTO::getSkuId)
                    .filter(Objects::nonNull)
                    .distinct()
                    .collect(Collectors.toList());

            // 将每个批次的请求任务封装为 Callable
            tasks.add(() -> featureService.getFeature(cidList, adVerIdList, skuIdList, param));
        }

        try {
            // 执行所有的任务并等待所有任务完成
            List<Future<Feature>> futures = executorService.invokeAll(tasks);

            // 等待所有任务完成并合并结果
            for (Future<Feature> future : futures) {
                Feature batchFeature = future.get();  // 获取每个任务的结果
                if (finalFeature == null) {
                    finalFeature = batchFeature;
                } else {
                    // 合并特征
                    finalFeature.merge(batchFeature);
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            log.error("getFeature error", e);
        }
        return finalFeature;
    }

    protected Feature getFeatureV2(String userLayer, ScoreParam param, RankRecommendRequestParam request) {
        List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
        log.info("getFeatureV2 adIdListSize:{}", adIdList.size());
        Feature finalFeature = null;

        // 分批处理 AdPlatformCreativeDTO 列表
        List<List<AdPlatformCreativeDTO>> adIdBatches = partitionList(adIdList, featureBranchConfigValue);

        // 使用 ThreadPoolFactory 获取 DEFAULT 线程池
        ExecutorService executorService = ThreadPoolFactory.defaultPool();  // 使用 DEFAULT 线程池
        List<Callable<Feature>> tasks = new ArrayList<>();

        for (List<AdPlatformCreativeDTO> batch : adIdBatches) {
            // 提取批次中的 cidList、adVerIdList 和 skuIdList
            List<String> cidList = batch.stream()
                    .map(AdPlatformCreativeDTO::getCreativeId)
                    .map(Object::toString)
                    .collect(Collectors.toList());

            List<String> adVerIdList = batch.stream()
                    .map(AdPlatformCreativeDTO::getAdVerId)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.toList());

            List<Long> skuIdList = batch.stream()
                    .map(AdPlatformCreativeDTO::getSkuId)
                    .filter(Objects::nonNull)
                    .distinct()
                    .collect(Collectors.toList());

            List<String> custList = batch.stream()
                    .map(AdPlatformCreativeDTO::getCustomerId)
                    .filter(Objects::nonNull)
                    .map(String::valueOf)
                    .distinct()
                    .collect(Collectors.toList());
            List<String> profList = batch.stream()
                    .map(AdPlatformCreativeDTO::getProfession)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.toList());
            List<String> cateList = batch.stream()
                    .map(AdPlatformCreativeDTO::getCategoryName)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.toList());
            List<String> landingList = batch.stream()
                    .map(AdPlatformCreativeDTO::getLandingPageType)
                    .filter(Objects::nonNull)
                    .map(String::valueOf)
                    .distinct()
                    .collect(Collectors.toList());
            List<Pair<String, String>> landingAdvList = batch.stream()
                    .filter(ad -> ad.getLandingPageType() != null && ad.getAdVerId() != null)
                    .map(ad -> Pair.of(String.valueOf(ad.getLandingPageType()), ad.getAdVerId()))
                    .distinct()
                    .collect(Collectors.toList());
            List<Pair<String, String>> landingCustList = batch.stream()
                    .filter(ad -> ad.getLandingPageType() != null && ad.getCustomerId() != null)
                    .map(ad -> Pair.of(String.valueOf(ad.getLandingPageType()), String.valueOf(ad.getCustomerId())))
                    .distinct()
                    .collect(Collectors.toList());
            List<Pair<String, String>> landingProfList = batch.stream()
                    .filter(ad -> ad.getLandingPageType() != null && ad.getProfession() != null)
                    .map(ad -> Pair.of(String.valueOf(ad.getLandingPageType()), ad.getProfession()))
                    .distinct()
                    .collect(Collectors.toList());
            List<Pair<String, String>> landingCateList = batch.stream()
                    .filter(ad -> ad.getLandingPageType() != null && ad.getCategoryName() != null)
                    .map(ad -> Pair.of(String.valueOf(ad.getLandingPageType()), ad.getCategoryName()))
                    .distinct()
                    .collect(Collectors.toList());

            // 将每个批次的请求任务封装为 Callable
            tasks.add(() -> featureService.getFeatureV2(cidList, adVerIdList, skuIdList, param,
                    userLayer, custList, profList, cateList, landingList,
                    landingAdvList, landingCustList, landingProfList, landingCateList));
        }

        try {
            // 执行所有的任务并等待所有任务完成
            List<Future<Feature>> futures = executorService.invokeAll(tasks);

            // 等待所有任务完成并合并结果
            for (Future<Feature> future : futures) {
                Feature batchFeature = future.get();  // 获取每个任务的结果
                if (finalFeature == null) {
                    finalFeature = batchFeature;
                } else {
                    // 合并特征
                    finalFeature.merge(batchFeature);
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            log.error("getFeature error", e);
        }
        return finalFeature;
    }

    // 辅助方法：将列表分成指定大小的批次
    private <T> List<List<T>> partitionList(List<T> originalList, Integer batchSize) {
        List<List<T>> batches = new ArrayList<>();
        for (int i = 0; i < originalList.size(); i += batchSize) {
            batches.add(originalList.subList(i, Math.min(i + batchSize, originalList.size())));
        }
        return batches;
    }

    protected Set<String> getNoApiAdVerIds() {
        Set<String> noApiAdVerIds = adRedisHelper.sMembers(RedisPrefixEnum.ADVER_IS_API_EQ_0_IDS.getPrefix());
        log.info("Redis中缓存的没有回传广告主ID集合: {}", noApiAdVerIds);
        if (CollectionUtils.isEmpty(noApiAdVerIds)) {
            noApiAdVerIds = noPostbackConversionAdVerIds;
        }
        return noApiAdVerIds;
    }

    protected Map<Long, Double> getCreativeScoreCoefficient() {
        if (Objects.isNull(creativeScoreCoefficient)) {
            return new HashMap<>();
        }
        return creativeScoreCoefficient;
    }

    protected Map<String, String> getReqFeature(ScoreParam scoreParam, RankRecommendRequestParam request) {
        Map<String, String> map = new HashMap<>();
        map.put("brand", scoreParam.getRequestContext().getMachineinfoBrand().equalsIgnoreCase("-1") ?
                "" : scoreParam.getRequestContext().getMachineinfoBrand().toUpperCase());
        map.put("region", Objects.equals(scoreParam.getRequestContext().getRegion(), "-1") ?
                "" : scoreParam.getRequestContext().getRegion());
        map.put("city", Objects.equals(scoreParam.getRequestContext().getCity(), "-1") ?
                "" : scoreParam.getRequestContext().getCity());
        map.put("vid", String.valueOf(request.getVideoId()));
        map.put("apptype", String.valueOf(request.getAppType()));
        String rootSessionId = request.getRootSessionId();
        String sessionId = request.getSessionId();
        String subSessionId = request.getSubSessionId();
        String rootSourceId = request.getRootSourceId();
        int isFirstLayer;
        if (Objects.equals(rootSessionId, subSessionId) || Objects.equals(rootSessionId, sessionId)) {
            isFirstLayer = 1;
        } else {
            isFirstLayer = 2;
        }
        map.put("is_first_layer", String.valueOf(isFirstLayer));
        if (StringUtils.isNotEmpty(rootSourceId)) {
            String rootSourceScene = rootSourceId.split("_")[0];
            map.put("root_source_scene", rootSourceScene);
            if (hasChannelScenes.contains(rootSourceScene) && rootSourceId.split("_").length > 1) {
                String rootSourceChannel = rootSourceId.split("_")[1];
                map.put("root_source_channel", rootSourceChannel);
            }
        }

        Map<String, String> userLayer = getUserLayer(request.getMid());
        String layer = userLayer.getOrDefault("layer", "无曝光");
        String clazz = userLayer.getOrDefault("class", "近期未出现");
        if (request.getIsFilterUser()) {
            layer = layer + "-炸";
        }
        map.put("layer", layer);
        map.put("clazz", clazz);
        String layerL4 = userLayer.getOrDefault("layer_l4", "无曝光");
        String clazzL4 = userLayer.getOrDefault("class_l4", "近期未出现");
        map.put("layer_l4", layerL4);
        map.put("clazz_l4", clazzL4);
        String layerL6 = userLayer.getOrDefault("layer_l6", "无曝光");
        map.put("layer_l6", layerL6);
        map.put("rootSessionId", rootSessionId);
        return map;
    }

    protected Map<String, String> handleSceneFeature(long ts) {
        Map<String, String> sceneFeatureMap = new HashMap<>();
        sceneFeatureMap.put("hour_" + DateUtils.getHourByTimestamp(ts), "0.1");
        sceneFeatureMap.put("dayofweek_" + DateUtils.getDayOrWeekByTimestamp(ts), "0.1");
        sceneFeatureMap.put("hour", String.valueOf(DateUtils.getHourByTimestamp(ts)));
        sceneFeatureMap.put("hour_quarter", String.valueOf(DateUtils.getHourQuarter(ts)));
        sceneFeatureMap.put("ts", String.valueOf(ts));
        return sceneFeatureMap;
    }


    protected static class Tuple5 {
        public String f1;
        public String f2;
        public String f3;
        public String f4;
        public String f5;

        public Tuple5(String f1, String f2, String f3, String f4, String f5) {
            this.f1 = f1;
            this.f2 = f2;
            this.f3 = f3;
            this.f4 = f4;
            this.f5 = f5;
        }
    }

    protected static class TupleMapEntry<T> {
        public String key;
        public T value;

        public TupleMapEntry(String key, T value) {
            this.key = key;
            this.value = value;
        }
    }

    protected static class Tuple2<F1, F2> {
        public F1 f1;

        public F2 f2;

        public Tuple2(F1 first, F2 name) {
            this.f1 = first;
            this.f2 = name;
        }

    }

    protected static class Tuple3<F1, F2, F3> {
        public F1 f1;
        public F2 f2;
        public F3 f3;

        public Tuple3(F1 f1, F2 f2, F3 f3) {
            this.f1 = f1;
            this.f2 = f2;
            this.f3 = f3;
        }
    }

    protected void filterRequestAdList(RankRecommendRequestParam request, ScoreParam scoreParam) {
        Map<String, String> userLayer = this.getUserLayer(request.getMid());
        String layer;
        String clazz;
        if (!checkoutLayer) {
            layer = userLayer.getOrDefault("layer", "无曝光");
            clazz = userLayer.getOrDefault("class", "近期未出现");
        } else {
            layer = userLayer.getOrDefault("layer_l4", "无曝光");
            clazz = userLayer.getOrDefault("class_l4", "近期未出现");
        }

        //有转化中医层，中医和兴趣教育同时存在过滤兴趣教育行业
        if (Objects.equals(layer, "有转化") && Objects.equals(clazz, "中医") && scoreParam.getExpCodeSet().contains(excludeExp)) {
            List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
            Set<String> professions = adIdList.stream().map(AdPlatformCreativeDTO::getProfession).collect(Collectors.toSet());
            //同时存在中医行业和兴趣教育行业
            if (professions.contains("中医") && professions.contains("兴趣教育")) {
                List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream()
                        .filter(e -> !Objects.equals(e.getProfession(), "兴趣教育")).collect(Collectors.toList());
                request.setAdIdList(filteredAdList);
            }
            log.info("excludeExp filtered request={}", JSONObject.toJSONString(request));
        }

        List<FilterConfig> filterConfigs = JSONArray.parseArray(filterConfigValue).toJavaList(FilterConfig.class);
        if (!CollectionUtils.isEmpty(filterConfigs)) {
            for (FilterConfig filterConfig : filterConfigs) {
                if (filterConfig.getExpId() == null || !scoreParam.getExpCodeSet().contains(filterConfig.getExpId())) {
                    continue;
                }
                if (!Objects.equals(layer, filterConfig.getLayer()) || !Objects.equals(clazz, filterConfig.getClazz())) {
                    continue;
                }
                if (Objects.equals(FilterTypeEnum.CREATIVE.getCode(), filterConfig.getType())) {
                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getCreativeId() == null || !e.getCreativeId().equals(Long.valueOf(filterConfig.getValue()))).collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
                        request.setAdIdList(filteredAdList);
                    }
                }
                if (Objects.equals(FilterTypeEnum.AD.getCode(), filterConfig.getType())) {
                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getAdId() == null || !e.getAdId().equals(Long.valueOf(filterConfig.getValue()))).collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
                        request.setAdIdList(filteredAdList);
                    }
                }
                if (Objects.equals(FilterTypeEnum.ADVERTISER.getCode(), filterConfig.getType())) {
                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getAdVerId() == null || !e.getAdVerId().equals(filterConfig.getValue())).collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
                        request.setAdIdList(filteredAdList);
                    }
                }
                if (Objects.equals(FilterTypeEnum.PROFESSION.getCode(), filterConfig.getType())) {
                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getProfession() == null || !e.getProfession().equals(filterConfig.getValue())).collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
                        request.setAdIdList(filteredAdList);
                    }
                }
                if (Objects.equals(FilterTypeEnum.CUSTOMER.getCode(), filterConfig.getType())) {
                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getCustomerId() == null || !e.getCustomerId().equals(Long.valueOf(filterConfig.getValue()))).collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
                        request.setAdIdList(filteredAdList);
                    }
                }
                log.info("targetCrowdExcludeExp filtered filterConfig={} request={}", filterConfig, JSONObject.toJSONString(request));
            }
        }
    }

    protected boolean getIsGuaranteedFlow(ScoreParam scoreParam) {
        // 817实验不要保量
        if (scoreParam.getEngineInfo() != null && "817".equals(scoreParam.getEngineInfo().get("model"))) {
            return false;
        }
        // 819实验不要保量
        if (valid819Ab(scoreParam)) {
            return false;
        }
        if (System.currentTimeMillis() < guaranteeSwitchingTime) {
            return scoreParam.getExpCodeSet().contains(guaranteeExp);
        }
        Random random = new Random();
        int i = random.nextInt(100);
        return i < guaranteeWeight;
    }

    private boolean valid819Ab(ScoreParam scoreParam) {
        // 819实验判断
        boolean in819Ab = scoreParam.getEngineInfo() != null
                && "819".equals(scoreParam.getEngineInfo().get("ecpm_model"))
                && scoreParam.getEngineInfo().get("ecpm_value") != null
                && Double.parseDouble(scoreParam.getEngineInfo().get("ecpm_value").toString()) >= 0;
        return in819Ab;
    }

    protected Map<String, GuaranteeView> getGuaranteeViewMap(RankRecommendRequestParam request, boolean isGuaranteedFlow) {
        Map<String, GuaranteeView> map = new HashMap<>();
        try {
            if (isGuaranteedFlow) {
                String thatDayDateString = DateUtils.getThatDayDateString();
                String redisKey = adPlatformGuaranteeKey.replace("{date}", thatDayDateString);
                List<String> adVerIds = request.getAdIdList().stream().map(AdPlatformCreativeDTO::getAdVerId).distinct()
                        .filter(Objects::nonNull).collect(Collectors.toList());
                List<String> redisKeys = adVerIds.stream().map(e -> redisKey.replace("{adverId}", e)).collect(Collectors.toList());
                List<String> values = adRedisHelper.mget(redisKeys);
                if (CollectionUtils.isNotEmpty(values)) {
                    for (int i = 0; i < redisKeys.size(); i++) {
                        String value = values.get(i);
                        if (value != null) {
                            GuaranteeView guaranteeView = JSONObject.parseObject(value, GuaranteeView.class);
                            map.put(adVerIds.get(i), guaranteeView);
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("getGuaranteeViewMap error", e);
        }

        return map;
    }

    protected void setGuaranteeWeight(Map<String, GuaranteeView> map, String adVerId, Map<String, Object> ext, boolean isGuaranteedFlow, Map<String, String> reqFeature) {
        if (isGuaranteedFlow && MapUtils.isNotEmpty(map)) {
            GuaranteeView guaranteeView = map.get(adVerId);
            if (guaranteeView != null) {
                double guaranteeWeight = calculateGuaranteeWeightWithCrowd(guaranteeView, reqFeature);
                boolean isGuaranteed = isGuaranteed(guaranteeView);
                ext.put("guaranteeView", guaranteeView.toString());
                ext.put("guaranteeWeight", guaranteeWeight);
                ext.put("isGuaranteed", isGuaranteed);
                ext.put("isGuaranteedFlow", isGuaranteedFlow);
            }
        }
    }

    /**
     * 兼容旧版本的setGuaranteeWeight方法（不带reqFeature参数）
     * 为了保持向后兼容性，保留原有方法签名
     */
    protected void setGuaranteeWeight(Map<String, GuaranteeView> map, String adVerId, Map<String, Object> ext, boolean isGuaranteedFlow) {
        setGuaranteeWeight(map, adVerId, ext, isGuaranteedFlow, new HashMap<>());
    }

    /**
     * 根据人群信息计算保量权重系数（新版本）
     *
     * @param guaranteeView 保量视图对象
     * @param reqFeature    请求特征信息，包含用户人群layer信息
     * @return 保量权重系数
     */
    protected double calculateGuaranteeWeightWithCrowd(GuaranteeView guaranteeView, Map<String, String> reqFeature) {
        try {
            // 空值检查：如果保量视图为空，返回默认权重
            if (guaranteeView == null) {
                return 1.0;
            }

            // 判断是否配置了保量人群代码
            String guaranteeCrowdCode = guaranteeView.getGuaranteeCrowdCode();
            if (StringUtils.isEmpty(guaranteeCrowdCode)) {
                // 保量人群代码无值，走原有逻辑
                return calculateGuaranteedWeight(guaranteeView);
            }
            // 判断是否勾选了全部人群
            if (isFullCrowd(guaranteeCrowdCode)) {
                // 勾选了全部人群，走原有逻辑
                return calculateGuaranteedWeight(guaranteeView);
            }

            // 保量人群代码有值，进行人群匹配判断
            String userLayer = reqFeature.getOrDefault("layer_l4", "");

            // 获取用户人群对应的CrowdLayerEnum code
            Integer userCrowdCode = getUserCrowdCode(userLayer);
            if (userCrowdCode == null) {
                log.error("RankStrategyBasic calculateGuaranteeWeightWithCrowd 无法获取用户人群代码，userLayer={}", userLayer);
                return 1.0;
            }

            // 判断用户人群是否在保量人群范围内
            boolean isUserInGuaranteeCrowd = isUserInGuaranteeCrowd(guaranteeCrowdCode, userCrowdCode);

            if (isUserInGuaranteeCrowd) {
                // 到这里，就是用户在保量人群中，且广告主勾选的不是全部人群
                // 计算保量权重
                double baseWeight = calculateGuaranteedWeight(guaranteeView);
                // 额外加权
                // 对勾选了保量人群，且不是勾选了全部的保量人群的广告进行额外加权
                double finalWeight = baseWeight * guaranteeCrowdWeightCoefficient;
                log.debug("RankStrategyBasic 保量人群加权: userLayer={}, guaranteeCrowdCode={}, baseWeight={}, coefficient={}, finalWeight={}",
                        userLayer, guaranteeCrowdCode, baseWeight, guaranteeCrowdWeightCoefficient, finalWeight);
                return finalWeight;
            } else {
                // 用户不在保量人群范围内，权重设为1
                log.debug("RankStrategyBasic 用户不在保量人群范围内: userLayer={}, guaranteeCrowdCode={}", userLayer, guaranteeCrowdCode);
                return 1.0;
            }
        } catch (Exception e) {
            log.error("RankStrategyBasic calculateGuaranteeWeightWithCrowd error", e);
            return calculateGuaranteedWeight(guaranteeView);
        }
    }

    /**
     * 计算保量权重系数（原有逻辑）
     * <p>
     * 保量逻辑说明：
     * 1. 根据广告主的保量配置（保量比例、保量上限）和实际曝光情况计算权重
     * 2. 权重用于调整广告排序分数，帮助未达到保量目标的广告主获得更多曝光
     * 3. 已达到保量上限的广告主权重为0，避免过度曝光
     *
     * @param guaranteeView 保量视图对象，包含广告主保量相关数据
     *                      - adrId: 广告主ID
     *                      - adrAlgoViewNum: 广告主当天算法曝光数
     *                      - allAlgoViewNum: 全平台当天算法曝光数
     *                      - guaranteeNum: 保量上限（广告主最大允许曝光数）
     *                      - guaranteeRate: 保量比例（广告主期望占全平台曝光的百分比）
     * @return 保量权重系数，范围[0.0, 2.0]，默认1.0
     */
    protected double calculateGuaranteedWeight(GuaranteeView guaranteeView) {
        // 1. 空值检查：如果保量视图为空，返回默认权重
        if (guaranteeView == null) {
            return 1.0;
        }

        double guaranteeWeight;

        // 2. 保量配置检查：如果保量上限或保量比例未配置，返回默认权重
        if (guaranteeView.getGuaranteeNum() == null || guaranteeView.getGuaranteeNum() == 0
                || guaranteeView.getGuaranteeRate() == null || guaranteeView.getGuaranteeRate() == 0.0) {
            guaranteeWeight = 1.0;
        } else {
            // 3. 保量达标检查：如果广告主曝光数已达到或超过保量上限，停止加权
            if (guaranteeView.getAdrAlgoViewNum() != null &&
                    guaranteeView.getGuaranteeNum() <= guaranteeView.getAdrAlgoViewNum()) {
                // 已达到保量目标，权重设为0，避免过度曝光
                guaranteeWeight = 0.0;
            } else {
                // 4. 计算保量权重
                // 防止除零错误：如果曝光数为空或0，设为1
                int allViewNum = guaranteeView.getAllAlgoViewNum() == null || guaranteeView.getAllAlgoViewNum() == 0 ?
                        1 : guaranteeView.getAllAlgoViewNum();
                int adrAlogViewNum = guaranteeView.getAdrAlgoViewNum() == null || guaranteeView.getAdrAlgoViewNum() == 0 ?
                        1 : guaranteeView.getAdrAlgoViewNum();

                // 核心计算公式：权重 = 保量比例 × 0.01 × 全平台曝光数 / 广告主曝光数
                // guaranteeView.getGuaranteeRate() 是百分之几，需要乘0.01转换为小数
                // 逻辑：如果广告主当前曝光占比低于目标保量比例，给予更高权重提升排序
                guaranteeWeight = guaranteeView.getGuaranteeRate() * 0.01 * allViewNum / adrAlogViewNum;

                // 5. 权重边界限制：避免权重过小或过大影响系统稳定性
                if (guaranteeWeight < 0.5) {
                    guaranteeWeight = 0.5;  // 最小权重限制
                }
                if (guaranteeWeight > 2) {
                    guaranteeWeight = 2;    // 最大权重限制
                }
            }
        }
        return guaranteeWeight;
    }

    protected boolean isGuaranteed(GuaranteeView guaranteeView) {
        if (guaranteeView == null) {
            return false;
        }
        return guaranteeView.getGuaranteeNum() != null && guaranteeView.getGuaranteeNum() != 0
                && guaranteeView.getGuaranteeRate() != null && guaranteeView.getGuaranteeRate() != 0.0;
    }

    protected double getGuaranteeScoreCoefficient(boolean isGuaranteedFlow, Map<String, Object> ext) {
        if (isGuaranteedFlow) {
            if (ext.get("guaranteeWeight") == null) {
                return 1.0;
            } else {
                return Math.pow((double) ext.get("guaranteeWeight"), alpha);
            }
        } else {
            return 1.0;
        }
    }

    protected Map<Long, CorrectCpaParam> getCorrectCpaParamMap(RankRecommendRequestParam request, ScoreParam scoreParam, Map<String, String> reqFeature) {
        String layer;
        String clazz;
        if (!checkoutLayer) {
            layer = reqFeature.get("layer");
            clazz = reqFeature.get("clazz");
        } else {
            layer = reqFeature.get("layer_l4");
            clazz = reqFeature.get("clazz_l4");
        }


        Map<Long, CorrectCpaParam> resultMap = new HashMap<>();
        try {
            if (CollectionUtils.isEmpty(request.getAdIdList())) {
                return resultMap;
            }
            String redisAdLayerHourKey = adLayerHourKey.replace("{layer}", layer).replace("{clazz}", clazz);
            String redisAdLayerDayKey = adLayerDayKey.replace("{layer}", layer).replace("{clazz}", clazz);
            Function<Long, String> hourKeyFunc = id -> redisAdLayerHourKey.replace("{adId}", String.valueOf(id));
            Function<Long, String> dayKeyFunc = id -> redisAdLayerDayKey.replace("{adId}", String.valueOf(id));
            List<Long> adIds = request.getAdIdList().stream()
                    .map(AdPlatformCreativeDTO::getAdId)
                    .distinct()
                    .collect(Collectors.toList());
            Map<Long, JSONObject> adLayerMap = getRedisData(adIds, hourKeyFunc, dayKeyFunc);
            adIds.forEach(adId -> {
                JSONObject jsonObject = adLayerMap.get(adId);
                CorrectCpaParam param = new CorrectCpaParam();

                if (jsonObject == null) {
                    param.setCorrectionFactor(DEFAULT_CORRECTION);
                } else {
                    // 设置基础参数
                    param.setViewsHour(jsonObject.getInteger("viewsHour"));
                    param.setViewsDay(jsonObject.getInteger("viewsDay"));
                    param.setRealCtcvrHour(jsonObject.getDouble("realCtcvrHour"));
                    param.setPCtcvrHour(jsonObject.getDouble("pCtcvrHour"));
                    param.setRealCtcvrDay(jsonObject.getDouble("realCtcvrDay"));
                    param.setPCtcvrDay(jsonObject.getDouble("pCtcvrDay"));
                    // 计算校正因子
                    double factor = calculateCorrection(jsonObject, scoreParam);
                    param.setCorrectionFactor(factor > 0 ? factor : DEFAULT_CORRECTION);
                }
                resultMap.put(adId, param);
            });
        } catch (Exception e) {
            log.error("getCorrectCpaParamMap error", e);
        }
        return resultMap;
    }

    private double calculateCorrection(JSONObject jsonObject, ScoreParam scoreParam) {
        // 安全获取值
        Integer viewsHour = jsonObject.getInteger("viewsHour");
        Integer viewsDay = jsonObject.getInteger("viewsDay");
        Double realCtcvrHour = safeDouble(jsonObject.getDouble("realCtcvrHour"), minCtcvr);
        Double pCtcvrHour = jsonObject.getDouble("pCtcvrHour");
        Double realCtcvrDay = safeDouble(jsonObject.getDouble("realCtcvrDay"), minCtcvr);
        Double pCtcvrDay = jsonObject.getDouble("pCtcvrDay");

        if (scoreParam.getExpCodeSet().contains(correctCpaExp2)) {
            double alpha = isValidViews(viewsHour, correctCpaViewHour) ? correctCpaAlpha2 : 0;
            double beta = isValidViews(viewsDay, correctCpaViewDay) ? correctCpaBeta2 : 0;
            return (1 - alpha - beta) +
                    safeDivision(realCtcvrHour, pCtcvrHour) * alpha +
                    safeDivision(realCtcvrDay, pCtcvrDay) * beta;
        } else {
            double alpha = isValidViews(viewsHour, correctCpaViewHour) ? correctCpaAlpha1 : 0;
            double beta = isValidViews(viewsDay, correctCpaViewDay) ? correctCpaBeta1 : 0;
            return Math.pow(safeDivision(realCtcvrHour, pCtcvrHour), alpha) *
                    Math.pow(safeDivision(realCtcvrDay, pCtcvrDay), beta);
        }
    }

    private Double safeDivision(Double realCtcvr, Double pCtcvr) {
        if (pCtcvr == null || pCtcvr == 0) {
            return 1.0;
        }
        if (realCtcvr == null || realCtcvr == 0) {
            return 0.0;
        }
        return realCtcvr / pCtcvr;
    }

    private <T> Map<T, JSONObject> getRedisData(
            Collection<T> ids,
            Function<T, String> hourKeyBuilder,
            Function<T, String> dayKeyBuilder) {

        Map<T, JSONObject> resultMap = new HashMap<>();
        if (CollectionUtils.isEmpty(ids)) {
            return resultMap;
        }

        // 构建Keys
        List<String> hourKeys = ids.stream()
                .map(hourKeyBuilder)
                .collect(Collectors.toList());
        List<String> dayKeys = ids.stream()
                .map(dayKeyBuilder)
                .collect(Collectors.toList());

        // 批量获取Redis值
        List<String> hourValues = adRedisHelper.mget(hourKeys);
        List<String> dayValues = adRedisHelper.mget(dayKeys);

        // 解析数据
        Iterator<T> idIter = ids.iterator();
        for (int i = 0; i < ids.size(); i++) {
            T id = idIter.next();
            JSONObject jsonObj = new JSONObject();

            // 解析小时数据
            parseRedisValue(hourValues.get(i), jsonObj, "Hour", "ctcvr", "pCtcvr", "views");
            // 解析天数据
            parseRedisValue(dayValues.get(i), jsonObj, "Day", "ctcvr", "pCtcvr", "views");

            resultMap.put(id, jsonObj);
        }
        return resultMap;
    }

    private void parseRedisValue(String value, JSONObject target,
                                 String suffix, String... keys) {
        if (StringUtils.isEmpty(value)) return;

        try {
            JSONObject source = JSONObject.parseObject(value);
            for (String key : keys) {
                Object val = source.get(key);
                if (val != null) {
                    String newKey = (key.equals("ctcvr") ? "realCtcvr" : key) + suffix;
                    target.put(newKey, val);
                }
            }
        } catch (Exception e) {
            log.warn("Parse redis value failed: {}", value, e);
        }
    }

    protected void calculateCtcvrScore(List<AdRankItem> items, RankRecommendRequestParam request, ScoreParam scoreParam, String modelName, Map<String, String> reqFeature) {
        //判断是否走校准试验
        if (scoreParam.getExpCodeSet().contains(calibrationCtcvrExp)) {
            try {
                calibrationDnnCtcvrScore(items, modelName, reqFeature);
            } catch (Exception e) {
                log.error("calibrationDnnCtcvrScore error", e);
            }
        }

        if (scoreParam.getExpCodeSet().contains(calibrationProfessionExp)) {
            try {
                calibrationProfessionCtcvrScore(items, modelName, reqFeature);
            } catch (Exception e) {
                log.error("calibrationProfessionCtcvrScore error", e);
            }
        }
    }

    private void calibrationProfessionCtcvrScore(List<AdRankItem> items, String modelName, Map<String, String> reqFeature) {
        if (StringUtils.isEmpty(modelName)) {
            return;
        }
        String layerKeyTemplate;
        if (!checkoutLayer) {
            // 构建Key模板
            layerKeyTemplate = userLayerDataKey
                    .replace("{model}", modelName)
                    .replace("{layer}", reqFeature.get("layer"))
                    .replace("{class}", reqFeature.get("clazz"))
                    .replace("{type}", "profession");
        } else {
            // 构建Key模板
            layerKeyTemplate = userLayerDataV2Key
                    .replace("{model}", modelName)
                    .replace("{layer}", reqFeature.get("layer_l4"))
                    .replace("{class}", reqFeature.get("clazz_l4"))
                    .replace("{type}", "profession");
        }
        List<String> professions = items.stream().map(AdRankItem::getProfession).filter(StringUtils::isNotEmpty).distinct().collect(Collectors.toList());
        List<String> professionRedisKeys = professions.stream().map(e -> layerKeyTemplate.replace("{value}", e)).collect(Collectors.toList());
        List<String> redisValues = adRedisHelper.mget(professionRedisKeys);
        Map<String, CalibrationModelCtcvrData> map = new HashMap<>();
        for (int i = 0; i < professions.size(); i++) {
            String value = redisValues.get(i);
            if (StringUtils.isEmpty(value)) {
                continue;
            }
            JSONObject json = JSONObject.parseObject(value);
            CalibrationModelCtcvrData calibrationModelCtcvrData = new CalibrationModelCtcvrData();
            calibrationModelCtcvrData.setRealCtcvr(json.getDouble("ctcvr"));
            calibrationModelCtcvrData.setView(json.getInteger("view"));
            calibrationModelCtcvrData.setPCtcvr(json.getDouble("pCtcvr"));
            map.put(professions.get(i), calibrationModelCtcvrData);
        }


        for (AdRankItem item : items) {
            CalibrationModelCtcvrData calibrationModelCtcvrData = map.get(item.getProfession());
            item.getExt().put("calibrationModelProfessionCtcvrData", JSONObject.toJSONString(calibrationModelCtcvrData));
            if (calibrationModelCtcvrData == null
                    || calibrationModelCtcvrData.getPCtcvr() == null
                    || calibrationModelCtcvrData.getPCtcvr() == 0.0
                    || calibrationModelCtcvrData.getRealCtcvr() == null
                    || calibrationModelCtcvrData.getRealCtcvr() == 0.0) {
                continue;
            }
            double diff = calibrationModelCtcvrData.getRealCtcvr() / calibrationModelCtcvrData.getPCtcvr();
            if (Math.abs(diff - 1) < 0.1) {
                continue;
            }
            double calibratedCtcvrScore = item.getLrScore() * diff;
            item.getScoreMap().put("layerModelProfessionCtcvrScore", calibratedCtcvrScore);
            item.getScoreMap().put("ctcvrScore", calibratedCtcvrScore);
            item.setLrScore(calibratedCtcvrScore);
        }
    }


    // 解析Redis返回值到Map
    private Map<Long, CorrectCtcvrScoreParam> parseRedisValues(List<Long> cidList, List<String> values) {
        Map<Long, CorrectCtcvrScoreParam> map = new HashMap<>();
        for (int i = 0; i < cidList.size(); i++) {
            String value = values.get(i);
            if (StringUtils.isEmpty(value)) continue;

            try {
                JSONObject json = JSONObject.parseObject(value);
                Integer view = json.getInteger("view");
                Double ctcvr = json.getDouble("ctcvr");
                if (view != null && ctcvr != null) {
                    CorrectCtcvrScoreParam param = new CorrectCtcvrScoreParam();
                    param.setView(view);
                    param.setRealCtcvr(ctcvr);
                    map.put(cidList.get(i), param);
                }
            } catch (Exception e) {
                log.error("Failed to parse calibration data for cid={}", cidList.get(i), e);
            }
        }
        return map;
    }


    protected AdRankItem creativeCovertRankItem(AdPlatformCreativeDTO dto, RankRecommendRequestParam request, Set<String> noApiAdVerIds) {
        AdRankItem adRankItem = new AdRankItem();
        adRankItem.setAdId(dto.getCreativeId());
        adRankItem.setCreativeCode(dto.getCreativeCode());
        adRankItem.setAdVerId(dto.getAdVerId());
        adRankItem.setVideoId(request.getVideoId());
        adRankItem.setCpa(dto.getCpa());
        adRankItem.setId(dto.getAdId());
        adRankItem.setCampaignId(dto.getCampaignId());
        adRankItem.setCpm(ObjUtil.nullOrDefault(dto.getCpm(), 90).doubleValue());
        adRankItem.setSkuId(dto.getSkuId());
        adRankItem.setProfession(dto.getProfession());
        adRankItem.getExt().put("recallsources", dto.getRecallSources());
        fillAdRankItemExt(adRankItem, dto);
        adRankItem.setRandom(new Random().nextInt(1000));
        if (CollectionUtils.isNotEmpty(noApiAdVerIds)) {
            if (noApiAdVerIds.contains(dto.getAdVerId())) {
                adRankItem.getExt().put("isApi", "0");
            } else {
                adRankItem.getExt().put("isApi", "1");
            }
        }

        return adRankItem;
    }

    protected void fillAdRankItemExt(AdRankItem adRankItem, AdPlatformCreativeDTO dto) {
        adRankItem.setAdProfessionName(dto.getAdProfessionName());
        adRankItem.setAdCategoryName(dto.getAdCategoryName());
        adRankItem.setAdCategoryId(dto.getAdCategoryId() != null ? String.valueOf(dto.getAdCategoryId()) : null);
        adRankItem.setAdSkuId(dto.getAdSkuId());
        adRankItem.setAdSkuCode(dto.getAdSkuCode());
        adRankItem.setAdSkuName(dto.getAdSkuName());
        adRankItem.getExt().put("categoryName", StringUtils.defaultString(dto.getCategoryName()));
        adRankItem.getExt().put("adProfessionId", dto.getAdProfessionId() != null ? String.valueOf(dto.getAdProfessionId()) : "");
        adRankItem.getExt().put("adProfessionName", StringUtils.defaultString(dto.getAdProfessionName()));
        adRankItem.getExt().put("adCategoryName", StringUtils.defaultString(dto.getAdCategoryName()));
        adRankItem.getExt().put("adCategoryId", dto.getAdCategoryId() != null ? String.valueOf(dto.getAdCategoryId()) : "");
        adRankItem.getExt().put("adSkuId", dto.getAdSkuId() != null ? String.valueOf(dto.getAdSkuId()) : "");
        adRankItem.getExt().put("adSkuCode", StringUtils.defaultString(dto.getAdSkuCode()));
        adRankItem.getExt().put("adSkuName", StringUtils.defaultString(dto.getAdSkuName()));
    }

    protected void putMetaFeature(AdRankItem adRankItem, Feature feature, Map<String, String> reqFeature,
                                  Map<String, String> sceneFeatureMap, RankRecommendRequestParam request) {
        if (feature == null) {
            log.warn("putMetaFeature: feature is null, skip processing. adVerId={}", adRankItem.getAdVerId());
            adRankItem.getMetaFeatureMap().put("reqFeature", reqFeature);
            adRankItem.getMetaFeatureMap().put("sceneFeature", sceneFeatureMap);
            return;
        }

        Map<String, Map<String, String>> userFeature = feature.getUserFeature();
        Map<String, Map<String, String>> videoFeature = feature.getVideoFeature();
        Map<String, Map<String, Map<String, String>>> allAdVerFeature = feature.getAdVerFeature();
        Map<String, Map<String, Map<String, String>>> allCidFeature = feature.getCidFeature();
        Map<String, Map<String, Map<String, String>>> allSkuFeature = feature.getSkuFeature();
        for (Map.Entry<String, Map<String, String>> entry : videoFeature.entrySet()) {
            if (MapUtils.isNotEmpty(entry.getValue())) {
                adRankItem.getMetaFeatureMap().put(entry.getKey(), entry.getValue());
            }
        }

        for (Map.Entry<String, Map<String, String>> entry : userFeature.entrySet()) {
            if (MapUtils.isNotEmpty(entry.getValue())) {
                adRankItem.getMetaFeatureMap().put(entry.getKey(), entry.getValue());
            }
        }

        Map<String, Map<String, String>> adVerFeature = allAdVerFeature.getOrDefault(adRankItem.getAdVerId(), new HashMap<>());
        for (Map.Entry<String, Map<String, String>> entry : adVerFeature.entrySet()) {
            if (MapUtils.isNotEmpty(entry.getValue())) {
                adRankItem.getMetaFeatureMap().put(entry.getKey(), entry.getValue());
            }
        }

        Map<String, Map<String, String>> cidFeature = allCidFeature.getOrDefault(String.valueOf(adRankItem.getAdId()), new HashMap<>());
        for (Map.Entry<String, Map<String, String>> entry : cidFeature.entrySet()) {
            if (MapUtils.isNotEmpty(entry.getValue())) {
                adRankItem.getMetaFeatureMap().put(entry.getKey(), entry.getValue());
            }
        }

        Map<String, Map<String, String>> skuFeature = allSkuFeature.getOrDefault(String.valueOf(adRankItem.getSkuId()), new HashMap<>());
        for (Map.Entry<String, Map<String, String>> entry : skuFeature.entrySet()) {
            if (MapUtils.isNotEmpty(entry.getValue())) {
                adRankItem.getMetaFeatureMap().put(entry.getKey(), entry.getValue());
            }
        }
        long cid = adRankItem.getAdId();
        List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
        AdPlatformCreativeDTO adPlatformCreativeDTO = adIdList.stream()
                .filter(dto -> cid == dto.getCreativeId())
                .findFirst().orElse(null);
        if (adPlatformCreativeDTO != null) {
            reqFeature.put("cid", String.valueOf(adPlatformCreativeDTO.getCreativeId()));
            reqFeature.put("adid", String.valueOf(adPlatformCreativeDTO.getAdId()));
            reqFeature.put("adverid", String.valueOf(adPlatformCreativeDTO.getAdVerId()));
            reqFeature.put("profession", adPlatformCreativeDTO.getProfession());
            reqFeature.put("landingPageType", String.valueOf(adPlatformCreativeDTO.getLandingPageType()));
            reqFeature.put("category_name", adPlatformCreativeDTO.getCategoryName());
            reqFeature.put("material_md5", adPlatformCreativeDTO.getMaterialMd5());
            reqFeature.put("agentId", adPlatformCreativeDTO.getAgentId() != null ? String.valueOf(adPlatformCreativeDTO.getAgentId()) : "");
            reqFeature.put("ad_profession_id", adPlatformCreativeDTO.getAdProfessionId() != null ? String.valueOf(adPlatformCreativeDTO.getAdProfessionId()) : "");
            reqFeature.put("ad_profession_name", adPlatformCreativeDTO.getAdProfessionName() != null ? adPlatformCreativeDTO.getAdProfessionName() : "");
            reqFeature.put("ad_category_name", adPlatformCreativeDTO.getAdCategoryName());
            reqFeature.put("ad_category_id", adPlatformCreativeDTO.getAdCategoryId() != null ? String.valueOf(adPlatformCreativeDTO.getAdCategoryId()) : "");
            reqFeature.put("ad_sku_id", adPlatformCreativeDTO.getAdSkuId() != null ? String.valueOf(adPlatformCreativeDTO.getAdSkuId()) : "");
            reqFeature.put("ad_sku_code", adPlatformCreativeDTO.getAdSkuCode() != null ? adPlatformCreativeDTO.getAdSkuCode() : "");
            reqFeature.put("ad_sku_name", adPlatformCreativeDTO.getAdSkuName() != null ? adPlatformCreativeDTO.getAdSkuName() : "");
        }
        adRankItem.getMetaFeatureMap().put("reqFeature", reqFeature);
        adRankItem.getMetaFeatureMap().put("sceneFeature", sceneFeatureMap);
    }

    protected Map<String, String> getUserLayer(String mid) {
        if (StringUtils.isEmpty(mid)) {
            return new HashMap<>();
        }
        String key = String.format("ad:engine:mid:layer:%s", mid);
        String value = algRedisHelper.get(key);
        if (StringUtils.isEmpty(value)) {
            return new HashMap<>();
        }
        try {
            Map<String, String> map = new HashMap<>();
            JSONObject jsonObject = JSONObject.parseObject(value);
            map.put("layer", jsonObject.getString("layer"));
            map.put("class", jsonObject.getString("class"));
            if (jsonObject.containsKey("basic_l4")) {
                map.put("layer_l4", jsonObject.getJSONObject("basic_l4").getString("level"));
                map.put("class_l4", jsonObject.getJSONObject("basic_l4").getString("class"));
            }
            if (jsonObject.containsKey("basic_l6")) {
                JSONObject basicL6 = JSONObject.parseObject(jsonObject.getString("basic_l6"));
                map.put("layer_l6", basicL6.getString("level"));
            }
            return map;
        } catch (Exception e) {
            log.error("getUserLayer error. mid: {}, \n", mid, e);
            return new HashMap<>();
        }
    }

    protected void calibrationDnnCtcvrScore(List<AdRankItem> adRankItems, String modelName, Map<String, String> reqFeature) {
        if (StringUtils.isEmpty(modelName)) {
            return;
        }
        String layerKeyTemplate;
        if (!checkoutLayer) {
            // 构建Key模板
            layerKeyTemplate = realLayerCtcvrKey
                    .replace("{model}", modelName)
                    .replace("{layer}", reqFeature.get("layer"))
                    .replace("{class}", reqFeature.get("clazz"));
        } else {
            // 构建Key模板
            layerKeyTemplate = realLayerCtcvrV2Key
                    .replace("{model}", modelName)
                    .replace("{layer}", reqFeature.get("layer_l4"))
                    .replace("{class}", reqFeature.get("clazz_l4"));
        }
        String value = adRedisHelper.get(layerKeyTemplate);
        JSONObject json = JSONObject.parseObject(value);
        Integer view = json.getInteger("view");
        Double realCtcvr = json.getDouble("ctcvr");
        Double pCtcvr = json.getDouble("pCtcvr");
        CalibrationModelCtcvrData calibrationModelCtcvrData = new CalibrationModelCtcvrData();
        calibrationModelCtcvrData.setRealCtcvr(realCtcvr);
        calibrationModelCtcvrData.setView(view);
        calibrationModelCtcvrData.setPCtcvr(pCtcvr);
        for (AdRankItem item : adRankItems) {
            item.getExt().put("calibrationModelCtcvrData", JSONObject.toJSONString(calibrationModelCtcvrData));
            if (pCtcvr == null || pCtcvr == 0.0 || realCtcvr == null || realCtcvr == 0.0) {
                continue;
            }
            double diff = realCtcvr / pCtcvr;
            if (Math.abs(diff - 1) < 0.1) {
                continue;
            }
            double correctCtcvrScore = item.getLrScore() * diff;
            item.getScoreMap().put("layerCorrectCtcvrScore", correctCtcvrScore);
            item.getScoreMap().put("ctcvrScore", correctCtcvrScore);
            item.setLrScore(correctCtcvrScore);
        }
    }

    // h5 权重
    protected double getH5SuppressWeight(AdRankItem item) {
        if (org.springframework.util.CollectionUtils.isEmpty(h5ScoreSuppress) || Objects.isNull(item)) {
            return 1.0;
        }

        Long customerId = item.getCustomerId();

        if (!CollectionUtils.isEmpty(customerIdWhiteSet) && Objects.nonNull(customerId) && customerIdWhiteSet.contains(customerId)) {
            log.info("getH5SuppressWeight 客户命中白名单， customerId:{}", customerId);
            return 1.0;
        }

        try {
            Integer landingPageType = item.getLandingPageType();
            String profession = item.getProfession();
            if (StringUtils.isBlank(profession) || Objects.isNull(landingPageType)) {
                return 1.0;
            }
            if (Objects.equals(2, landingPageType) && h5ScoreSuppress.containsKey(profession)) {
                return h5ScoreSuppress.get(profession);
            }
        } catch (Exception e) {
            log.error("获取h5权重失败", e);
        }
        return 1.0;
    }

    protected double getDefaultCxr(double cxr) {
        try {
            if (null != defaultCxr) {
                return Math.max(cxr, defaultCxr);
            }
        } catch (Exception e) {
            log.error("cxr", e);
        }
        return cxr;
    }

    protected boolean landingH5Page(AdRankItem item) {
        if (Objects.isNull(item)) {
            return false;
        }
        try {
            Integer landingPageType = item.getLandingPageType();
            if (Objects.isNull(landingPageType)) {
                return false;
            }
            return Objects.equals(2, landingPageType);
        } catch (Exception e) {
            log.error("h5判断失败", e);
        }
        return false;
    }

    // 安全的数值转换
    private double safeDouble(Double value, Double min) {
        return (value == null || value == 0) ? min : value;
    }

    // 视图数校验封装
    private boolean isValidViews(Integer views, int threshold) {
        return views != null && views >= threshold;
    }

    /**
     * 根据用户人群layer获取对应的CrowdLayerEnum code
     *
     * @param userLayer 用户人群layer，如"无曝光"、"有转化"、"有曝光无转化"
     * @return 对应的CrowdLayerEnum code，如果无法匹配则返回null
     */
    private Integer getUserCrowdCode(String userLayer) {
        if (StringUtils.isEmpty(userLayer)) {
            return null;
        }

        // 处理可能的"-炸"后缀
        String cleanLayer = userLayer.replace("-炸", "");

        // 使用CrowdLayerEnum提供的方法根据描述获取枚举对象
        CrowdLayerEnum crowdLayerEnum = CrowdLayerEnum.getBytDesc(cleanLayer);
        if (crowdLayerEnum != null) {
            return crowdLayerEnum.getCode();
        }

        log.warn("无法匹配用户人群layer到CrowdLayerEnum: {}", userLayer);
        return null;
    }

    /**
     * 判断用户人群是否在保量人群范围内
     *
     * @param guaranteeCrowdCode 保量人群代码字符串，格式如"1,2,3"
     * @param userCrowdCode      用户人群代码
     * @return true表示用户在保量人群范围内
     */
    private boolean isUserInGuaranteeCrowd(String guaranteeCrowdCode, Integer userCrowdCode) {
        if (StringUtils.isEmpty(guaranteeCrowdCode) || userCrowdCode == null) {
            return false;
        }

        try {
            // 将保量人群代码字符串按逗号分割
            String[] crowdCodes = guaranteeCrowdCode.split(",");
            for (String code : crowdCodes) {
                if (String.valueOf(userCrowdCode).equals(code.trim())) {
                    return true;
                }
            }
        } catch (Exception e) {
            log.error("解析保量人群代码失败: guaranteeCrowdCode={}", guaranteeCrowdCode, e);
        }

        return false;
    }

    /**
     * 判断是否全部保量人群
     *
     * @param guaranteeCrowdCode 保量人群代码字符串
     * @return true表示是全部保量人群
     */
    private boolean isFullCrowd(String guaranteeCrowdCode) {
        if (StringUtils.isEmpty(guaranteeCrowdCode)) {
            return false;
        }

        try {
            // 获取CrowdLayerEnum中的所有code值
            Set<String> allCrowdCodes = new HashSet<>();
            for (CrowdLayerEnum crowdLayer : CrowdLayerEnum.values()) {
                allCrowdCodes.add(String.valueOf(crowdLayer.getCode()));
            }

            // 解析保量人群代码
            Set<String> guaranteeCodes = new HashSet<>();
            String[] codes = guaranteeCrowdCode.split(",");
            for (String code : codes) {
                guaranteeCodes.add(code.trim());
            }

            // 判断保量人群代码是否为全量值（包含所有CrowdLayerEnum的code）
            boolean isFullCrowd = guaranteeCodes.containsAll(allCrowdCodes);
            return isFullCrowd;

        } catch (Exception e) {
            log.error("判断保量人群加权失败: guaranteeCrowdCode={}",
                    guaranteeCrowdCode, e);
            return false;
        }
    }

    // rerank
    protected void calRerankWeight(ScoreParam scoreParam, String layer, List<AdRankItem> items) {
        try {
            List<RootSessionIdExpConfig> expConfigs = RootSessionIdTailUtil.getRootSessionIdTailConfig(scoreParam, RootSessionIdTailUtil.reRankTailN, rerankRootSessionIdConfig);
            if (expConfigs.isEmpty()) {
                return;
            }
            Set<String> hit = new HashSet<>();
            for (RootSessionIdExpConfig expConfig : expConfigs) {
                if (null != expConfig && null != expConfig.getConfig()) {
                    String rerankStrategy = expConfig.getConfig().get("rerank");
                    if (null == rerankStrategy || hit.contains(rerankStrategy)) {
                        continue;
                    }
                    hit.add(rerankStrategy);
                    switch (rerankStrategy) {
                        case "flowCtlC":
                            calFlowControlWeight(rerankStrategy, layer, items, flowCtlCustomerParams);
                            break;
                        case "flowCtlA":
                            calFlowControlWeight(rerankStrategy, layer, items, flowCtlAgentParams);
                            break;
                        default:
                            break;
                    }
                }
            }

            calCreativeKFinalWeight("kFinal",items);
        } catch (Exception e) {
            log.error("cal rerank weight error", e);
        }
    }

    // 控制流量
    protected void calFlowControlWeight(String flowCtlType, String layer, List<AdRankItem> items, Map<String, Double> flowControlParams) {
        try {
            if (org.springframework.util.CollectionUtils.isEmpty(flowControlParams)) {
                return;
            }
            double cpmPow = flowControlParams.getOrDefault("cpmPow", 0.3);
            double lowerCpmWeight = flowControlParams.getOrDefault("lowerCpmWeight", 1.0);
            double upperCpmWeight = flowControlParams.getOrDefault("upperCpmWeight", 1.2);
            double flowPow = flowControlParams.getOrDefault("flowPow", 1.2);
            double lowerFlowWeight = flowControlParams.getOrDefault("lowerFlowWeight", 0.77);
            double upperFlowWeight = flowControlParams.getOrDefault("upperFlowWeight", 1.0);

            double hitViewRate = flowControlParams.getOrDefault("hitViewRate", 0.05);
            double plusViewRate = flowControlParams.getOrDefault("plusViewRate", 20.0);
            double viewRatePow = flowControlParams.getOrDefault("viewRatePow", 0.2);
            double viewRateScale = flowControlParams.getOrDefault("viewRateScale", 0.5491);
            if ("flowCtlC".equals(flowCtlType)) {
                for (AdRankItem item : items) {
                    String profession = item.getProfession();
                    String customerId = String.valueOf(item.getCustomerId());
                    Pair<Double, Double> pair = LayerCustomerFlowControlDataHelper.getWeight(layer, profession, customerId);
                    double flowWeight = getFlowControlWeight(pair,
                            cpmPow, lowerCpmWeight, upperCpmWeight,
                            flowPow, lowerFlowWeight, upperFlowWeight,
                            hitViewRate, plusViewRate, viewRatePow, viewRateScale);
                    item.getScoreMap().put(flowCtlType, flowWeight);
                }
            } else if ("flowCtlA".equals(flowCtlType)) {
                for (AdRankItem item : items) {
                    String profession = item.getProfession();
                    String agentId = String.valueOf(item.getAgentId());
                    Pair<Double, Double> pair = LayerAgentFlowControlDataHelper.getWeight(layer, profession, agentId);
                    double flowWeight = getFlowControlWeight(pair,
                            cpmPow, lowerCpmWeight, upperCpmWeight,
                            flowPow, lowerFlowWeight, upperFlowWeight,
                            hitViewRate, plusViewRate, viewRatePow, viewRateScale);
                    item.getScoreMap().put(flowCtlType, flowWeight);
                }
            }
        } catch (Exception e) {
            log.error("cal flow control weight error", e);
        }
    }


    // 加V打压系数
    protected void calCreativeKFinalWeight(String flowCtlType, List<AdRankItem> items) {
        try {
            for (AdRankItem item : items) {
                Double kFinal = CreativeKFinalDataHelper.getKFinal(item.getCreativeCode());
                if(kFinal != null){
                    item.getScoreMap().put(flowCtlType, kFinal);
                }
            }
        } catch (Exception e) {
            log.error("calCreativeKFinalWeight weight error", e);
        }
    }

    private double getFlowControlWeight(Pair<Double, Double> pair,
                                        double cpmPow, double lowerCpmWeight, double upperCpmWeight,
                                        double flowPow, double lowerFlowWeight, double upperFlowWeight,
                                        double hitViewRate, double plusViewRate, double viewRatePow, double viewRateScale) {
        try {
            if (null != pair) {
                double cpmWeight = 1;
                double flowWeight = 1;
                double cpmDiff = pair.getLeft();
                double viewRate = pair.getRight();
                if (cpmDiff > 0) {
                    cpmWeight = Math.pow(cpmDiff, cpmPow);
                    cpmWeight = Math.min(Math.max(lowerCpmWeight, cpmWeight), upperCpmWeight);
                }
                if (viewRate > hitViewRate) {
                    flowWeight = 1 / Math.pow(viewRate * 100 + plusViewRate, viewRatePow) / viewRateScale;
                    flowWeight = Math.pow(flowWeight, flowPow);
                    flowWeight = Math.min(Math.max(lowerFlowWeight, flowWeight), upperFlowWeight);
                }
                return cpmWeight * flowWeight;
            }
        } catch (Exception e) {
            log.error("flowWeight error", e);
        }
        return 1;
    }
}
