package com.tzld.piaoquan.ad.engine.service.predict.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.tzld.piaoquan.ad.engine.commons.enums.AppTypeEnum;
import com.tzld.piaoquan.ad.engine.commons.enums.RedisPrefixEnum;
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.util.DateUtils;
import com.tzld.piaoquan.ad.engine.service.predict.PredictModelService;
import com.tzld.piaoquan.ad.engine.service.predict.config.AbConfig;
import com.tzld.piaoquan.ad.engine.service.predict.config.NewExpUserGroupConfig;
import com.tzld.piaoquan.ad.engine.service.predict.config.RoiModelConfig;
import com.tzld.piaoquan.ad.engine.service.predict.constant.RuleRedisKeyConst;
import com.tzld.piaoquan.ad.engine.service.predict.container.AbTestConfigContainer;
import com.tzld.piaoquan.ad.engine.service.predict.container.ThresholdModelContainer;
import com.tzld.piaoquan.ad.engine.service.predict.container.TopOneVideoContainer;
import com.tzld.piaoquan.ad.engine.service.predict.helper.NewExpInfoHelper;
import com.tzld.piaoquan.ad.engine.service.predict.param.RoiThresholdPredictModelParam;
import com.tzld.piaoquan.ad.engine.service.predict.param.ThresholdPredictModelParam;
import com.tzld.piaoquan.ad.engine.service.predict.param.request.RoiPredictModelRequestParam;
import com.tzld.piaoquan.ad.engine.service.predict.param.request.ThresholdPredictModelRequestParam;
import com.tzld.piaoquan.ad.engine.service.predict.v2.*;
import com.tzld.piaoquan.ad.engine.service.user.UserService;
import com.tzld.piaoquan.ad.engine.commons.feign.manager.service.HolidayService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

//
@Service
public class PredictModelServiceImpl implements PredictModelService {
    private final static Logger log = LoggerFactory.getLogger(PredictModelServiceImpl.class);

    @Autowired
    private AbTestConfigContainer abTestConfigContainer;

    @Autowired
    private TopOneVideoContainer topOneVideoContainer;

    @Autowired
    AlgorithmRedisHelper redisHelper;

    @Autowired
    AbConfig abConfig;

    @Autowired
    RoiModelConfig roiModelConfig;

    // @Autowired
    // private PredictServiceV2 predictServiceV2;
    // @Autowired
    // private RootSessionIdPredict rootSessionIdPredict;
    @Autowired
    private UserLayerRootSessionIdPredict userLayerRootSessionIdPredict;
    @Autowired
    private PredictStrategyBy673 predictStrategyBy673;
    @Autowired
    private PredictStrategyBy667 predictStrategyBy667;
    @Autowired
    private PredictStrategyBy599 predictStrategyBy599;
    @Autowired
    private PredictStrategyBy817 predictStrategyBy817;
    @Autowired
    private PredictStrategyBy819 predictStrategyBy819;
    @Autowired
    private PredictStrategyBy820 predictStrategyBy820;
    @Autowired
    private PredictStrategyByMorning56 predictStrategyByMorning56;
    @Autowired
    private PredictStrategyByRor predictStrategyByRor;
    @Autowired
    private PredictStrategyByRorMorning predictStrategyByRorMorning;
    @Autowired
    private PredictStrategyByFissionRate predictStrategyByFissionRate;
    @Autowired
    private PredictStrategyByRorCopy predictStrategyByRorCopy;
    @Autowired
    private PredictStrategyByFissionRateCopy predictStrategyByFissionRateCopy;
    @Autowired
    private PredictStrategyByAppTypeTail predictStrategyByAppTypeTail;


    @Autowired
    private UserService userService;

    // @Value("${ad.own.experiment.tier:ab_test002}")
    // private String adOwnExperimentTier;
    // @Autowired
    // private LogHubService logHubService;
    // @Value("${ad.predict.share0.exp.code:000}")
    // private String adPredictNoShareUserExpCode;

    // @Value("${ad.predict.immersion.exp.code:607}")
    // private String adPredictImmersionExpCode;

    @Value("${ad.predict.param.testIds:0}")
    private String testIds;
    @Value("${ad.predict.without.ad.video_ids:0}")
    private String withoutAdVideoIds;
    @Value("${ad.predict.old.exp.appId:0,3,4,5,6,17,18,19,21,22}")
    private String oldExpGroupAppId = "";

    @Value("${user.source.layer.ad.rate.exp.ids:673,676}")
    private String userSourceLayerAdRateExpIds;

    @Value("${advance.show.ad.global.flag:false}")
    private boolean advanceShowAdGlobalFlag;

    @Value("${advance.show.ad.switch:true}")
    private boolean advanceShowAdSwitch;

    @Value("#{'${show.ad.whitelist.mid:}'.split(',')}")
    private Set<String> showAdWhitelistMidSet;

    @Value("${experiment.817.ad.hour:5,8}")
    private String experiment817WithAdHour;

    @Value("${experiment.morning56.ad.hour:5,6}")
    private String experimentMorning56WithAdHour;

    @Value("${experiment.morning56.switch:false}")
    private String experimentMorning56Switch;

    @Autowired
    private AdRedisHelper adRedisHelper;

    // List<Integer> appIdArr = Arrays.asList(new Integer[]{0, 3, 4, 5, 6, 17, 18, 19, 21, 22});
    @Autowired
    private HolidayService holidayService;

    List<Integer> appIdArr = Arrays.asList(new Integer[]{0, 3, 4, 5, 6, 17, 18, 19, 21, 22});


    public Map<String, Object> adPredictWithDataFill(ThresholdPredictModelRequestParam requestParam) {

        Map<String, Object> result = adPredict(requestParam);

        if (MapUtils.isNotEmpty(result)) {
            PredictContext predictContext = ConvertUtil.predictParam2Context(requestParam);
            Map<String, Object> ecpmExperiment = predictStrategyByAppTypeTail.predict(predictContext);
            // 填充 ecpm实验 参数
            if (MapUtils.isNotEmpty(ecpmExperiment)) {
                result.putAll(ecpmExperiment);
            }
        }
        return result;
    }

    public Map<String, Object> adPredict(ThresholdPredictModelRequestParam requestParam) {
        Map<String, Object> result = new HashMap<>();
        result.put("pqtId", requestParam.getPqtId());

        // 白名单用户固定出广告
        if (CollectionUtils.isNotEmpty(showAdWhitelistMidSet) && showAdWhitelistMidSet.contains(requestParam.getMid())) {
            result.put("ad_predict", 2);
            result.put("ad_strategy", "show_ad_whitelist");
            return result;
        }

        try {
            String[] withoutAdVideoIdsArr = withoutAdVideoIds.split(",");
            for (String videoId : withoutAdVideoIdsArr) {
                if (videoId.equals(requestParam.getVideoId() + "")) {
                    if (requestParam.getAppType().equals(0)
                            || requestParam.getAppType().equals(4)
                            || requestParam.getAppType().equals(5)
                            || requestParam.getAppType().equals(21)
                    ) {
                        result = new HashMap<>();
                        result.put("ad_predict", 1);
                        result.put("no_ad_strategy", "no_ad_with_video_in_white_list");
                        return result;
                    }
                }
            }


            List<Map<String, Object>> mapList = (List) requestParam.getAbExpInfo().get("ab_test002");

            // 该用户所有实验合集
            Set<String> expCodes = new HashSet<>();
            for (Map<String, Object> map : mapList) {
                String expCode = map.getOrDefault("abExpCode", "").toString();
                expCodes.add(expCode);
            }
            boolean execute817 = false;
            boolean in817time = false;
            boolean inMorning56time = false;
            // 不出广告时间判定
            int hourOfDay = DateUtils.getCurrentHour();
            if ( 0 <= hourOfDay && hourOfDay < 8 && !isAdvanceShowAd() ) {
                // 0点到8点 && 不是节日

                boolean isInMorning56Time = false;
                if (StringUtils.isNotBlank(experimentMorning56Switch) && Objects.equals("true", experimentMorning56Switch)) {
                    // 判断是否在morning56时间范围（5-6点）
                    try {
                        String[] split = experimentMorning56WithAdHour.split(",");
                        if (Integer.parseInt(split[0]) <= hourOfDay && hourOfDay < Integer.parseInt(split[1])) {
                            isInMorning56Time = true;
                        }
                    } catch (Exception e) {
                        log.error("experimentMorning56WithAdHour配置异常", e);
                    }
                }

                // 判断是否在817时间范围
                boolean isIn817Time = false;
                try{
                    String[] split = experiment817WithAdHour.split(",");
                    if ( Integer.parseInt(split[0]) <= hourOfDay && hourOfDay < Integer.parseInt(split[1]) ) {
                        isIn817Time = true;
                    }
                }catch (Exception e){
                    log.error("experiment817WithAdHour配置异常", e);
                }

                // 优先判断morning56（5-6点）
                if (isInMorning56Time) {
                    inMorning56time = true;
                } else if (isIn817Time) {
                    // 在 817实验时间范围 && 817实验开启
                    in817time = true;
                    execute817 = expCodes.contains("817");
                } else {
                    result.put("ad_predict", 1);
                    result.put("no_ad_strategy", "no_ad_time_with_fixed_time");
                    return result;
                }
            }

            // 新老实验和填充requestParam参数（老实验可能直接返回结果）
            Map<String, Object> result1 = getParamAndFill(requestParam, result);
            if (result1 != null){
                result1.put("ad_predict", 1);
                return result1;
            }


            PredictContext predictContext = ConvertUtil.predictParam2Context(requestParam);

            // morning56策略（5-6点，所有用户执行）
            if(inMorning56time){
                return predictStrategyByMorning56.predict(predictContext);
            }

            if(in817time){

                // 早间ror熔断
                Map<String, Object> userRorPredictMorning = predictStrategyByRorMorning.predict(predictContext);
                if (MapUtils.isNotEmpty(userRorPredictMorning)) {
                    return userRorPredictMorning;
                }
            }


            // 817熔断
            if (execute817) {
                return predictStrategyBy817.predict(predictContext);
            }

            // 819只做参数填充
            Map<String, Object> predictExtInfo = null;
            // if (expCodes.contains("819")) {
            //     predictExtInfo = predictStrategyBy819.predict(predictContext);
            // }

            Map<String, Object> userLayerPredict = userLayerRootSessionIdPredict.predict(predictContext);
            if (MapUtils.isNotEmpty(userLayerPredict)) {
                // 填充 819 参数
                if (MapUtils.isNotEmpty(predictExtInfo)) {
                    userLayerPredict.putAll(predictExtInfo);
                }
                return userLayerPredict;
            }


            // if (expCodes.contains("820")) {
            //     Map<String, Object> userLayerPredict820 = predictStrategyBy820.predict(predictContext);
            //     if (MapUtils.isNotEmpty(userLayerPredict820)) {
            //         // 填充 819 参数
            //         if (MapUtils.isNotEmpty(predictExtInfo)) {
            //             userLayerPredict820.putAll(predictExtInfo);
            //         }
            //         return userLayerPredict820;
            //     }
            // }



            if (expCodes.contains("673")) {
                Map<String, Object> predictResult = predictStrategyBy673.predict(predictContext);
                if (Objects.nonNull(predictResult)) {
                    // 填充 819 参数
                    if (MapUtils.isNotEmpty(predictExtInfo)) {
                        predictResult.putAll(predictExtInfo);
                    }
                    return predictResult;
                }
            }

            // ror行为策略
            Map<String, Object> userRorPredict = predictStrategyByRor.predict(predictContext);
            if (MapUtils.isNotEmpty(userRorPredict)) {
                // 填充 819 参数
                if (MapUtils.isNotEmpty(predictExtInfo)) {
                    userRorPredict.putAll(predictExtInfo);
                }
                return userRorPredict;
            }

            // ror行为策略
            Map<String, Object> userRorPredictCopy = predictStrategyByRorCopy.predict(predictContext);
            if (MapUtils.isNotEmpty(userRorPredictCopy)) {
                // 填充 819 参数
                if (MapUtils.isNotEmpty(predictExtInfo)) {
                    userRorPredictCopy.putAll(predictExtInfo);
                }
                return userRorPredictCopy;
            }


            // fission_rate行为策略
            Map<String, Object> userFissionRatePredict = predictStrategyByFissionRate.predict(predictContext);
            if (MapUtils.isNotEmpty(userFissionRatePredict)) {
                // 填充 819 参数
                if (MapUtils.isNotEmpty(predictExtInfo)) {
                    userFissionRatePredict.putAll(predictExtInfo);
                }
                return userFissionRatePredict;
            }

            // fission_rate_copy行为策略
            Map<String, Object> userFissionRatePredictCopy = predictStrategyByFissionRateCopy.predict(predictContext);
            if (MapUtils.isNotEmpty(userFissionRatePredictCopy)) {
                // 填充 819 参数
                if (MapUtils.isNotEmpty(predictExtInfo)) {
                    userFissionRatePredictCopy.putAll(predictExtInfo);
                }
                return userFissionRatePredictCopy;
            }


            Map<String, Object> predictResult;
            if (expCodes.contains("599")){
                predictResult = predictStrategyBy599.predict(predictContext);
            }else if (expCodes.contains("667")){
                predictResult = predictStrategyBy667.predict(predictContext);
            }else {
                predictResult = predictStrategyBy599.predict(predictContext);
            }
            // 填充 819 参数
            if (MapUtils.isNotEmpty(predictResult) && MapUtils.isNotEmpty(predictExtInfo)) {
                predictResult.putAll(predictExtInfo);
            }
            return predictResult;
            
        } catch (Exception e) {
            log.error("svc=adPredict appType={} group={} newGroup={} pqtId={}"
                    , requestParam.getAppType(), requestParam.getAbTestCode(), requestParam.getNewExpGroup(), requestParam.getPqtId(), e);
            result.put("ad_predict", 1);
            result.put("no_ad_strategy", "error");
            return result;
        }

    }

    private @Nullable Map<String, Object> getParamAndFill(ThresholdPredictModelRequestParam requestParam, Map<String, Object> result) {
        String abtestId = null;
        String abTestConfigTag = null;
        Map<String, Object> abtestParam = null;
        String midGroup = null;
        String shareType = null;

        String[] appIdArr = oldExpGroupAppId.split(",");
        // 新老实验系统
        List<String> appIdList = Arrays.asList(appIdArr);
        if (appIdList.contains(requestParam.getAppType().toString())) {
            String[] abParamArr = abConfig.getAbParams(requestParam.getAbTestCode(), requestParam.getAbExpInfo());
            if (abParamArr == null) {
                abParamArr = NewExpUserGroupConfig.newExpUserGroupMap.get(requestParam.getAppType().toString());
                if (abParamArr == null) {
                    result.put("msg", "abConfig_error");
                    return result;
                }
            }
            abtestId = abParamArr[0];
            abTestConfigTag = abParamArr[1];
            HashMap<String, Map<String, Object>> abConfigMap = abConfig.getAbConfigMap();
            abtestParam = abConfigMap.getOrDefault(abtestId + "-" + abTestConfigTag, null);
            if (abtestParam == null) {
                result.put("msg", "abConfig_error");
                return result;
            }
            // Determine the group to which mid belongs
            String groupClassKey = (String) abtestParam.get("group_class_key");
            String midGroupKeyName = RuleRedisKeyConst.KEY_NAME_PREFIX_MID_GROUP + groupClassKey + ":" + requestParam.getMid();
            midGroup = redisHelper.getString(midGroupKeyName);

            // 没有时为新用户或者无分享用户
            shareType = midGroup;
            if (midGroup == null) {
                midGroup = "mean_group";
                shareType = "noShare";
            }
            String[] noAdMidGroupList = new String[0];
            noAdMidGroupList = ((JSONArray) abtestParam.get("no_ad_mid_group_list")).toArray(noAdMidGroupList);

            boolean inNoAdGroup = false;
            for (String group : noAdMidGroupList) {
                if (group.equals(midGroup)) {
                    inNoAdGroup = true;
                    break;
                }
            }
            // 不出广告组
            if (inNoAdGroup) {
                // User is in the no-ad group, no ad should be shown
                result.put("mid_group", midGroup);
                result.put("ad_predict", 1);
                result.put("no_ad_strategy", "no_ad_mid_group_with_video");
                return result;
            }
            // top1广告不出视频
            Map<String, List<String>> noAdGroupWithVideoMapping = (Map) abtestParam.getOrDefault("no_ad_group_with_video_mapping", new HashMap<>());
            if (noAdGroupWithVideoMapping.keySet().contains(midGroup)
                    &&
                    topOneVideoContainer.inNoAdTopVideo(requestParam.getAppType().longValue(), requestParam.getVideoId())
            ) {
                result.put("mid_group", midGroup);
                result.put("ad_predict", 1);
                result.put("no_ad_strategy", "no_ad_mid_group_with_video");
                return result;
            }
        } else {
            String midGroupKeyName = RuleRedisKeyConst.KEY_NAME_PREFIX_MID_GROUP + "class1:" + requestParam.getMid();
            midGroup = redisHelper.getString(midGroupKeyName);
            shareType = midGroup;
            if (midGroup == null) {
                midGroup = "mean_group";
                shareType = "noShare";
            }
        }

        // 市-中文
        requestParam.setRegion(requestParam.getRegion().replace("省", ""));
        requestParam.setCity(requestParam.getCity().replace("市", ""));
        requestParam.setShareType(shareType);
        String userLayer = userService.getUserLayer4Level(requestParam.getMid());
        requestParam.setUserLayer(userLayer);
        return null;
    }


    public Map<String, Object> adPredictOld(ThresholdPredictModelRequestParam requestParam) {
        Map<String, Object> result = new HashMap<>();
        result.put("pqtId", requestParam.getPqtId());

        // 白名单用户固定出广告
        if (CollectionUtils.isNotEmpty(showAdWhitelistMidSet) && showAdWhitelistMidSet.contains(requestParam.getMid())) {
            result.put("ad_predict", 2);
            result.put("ad_strategy", "show_ad_whitelist");
            return result;
        }

        try {
            String[] withoutAdVideoIdsArr = withoutAdVideoIds.split(",");
            for (String videoId : withoutAdVideoIdsArr) {
                if (videoId.equals(requestParam.getVideoId() + "")) {
                    if (requestParam.getAppType().equals(0)
                            || requestParam.getAppType().equals(4)
                            || requestParam.getAppType().equals(5)
                            || requestParam.getAppType().equals(21)
                    ) {
                        result = new HashMap<>();
                        result.put("ad_predict", 1);
                        result.put("no_ad_strategy", "no_ad_with_video_in_white_list");
                        return result;
                    }
                }
            }


            List<Map<String, Object>> mapList = (List) requestParam.getAbExpInfo().get("ab_test002");

            Map<String, List<JSONObject>> configMap = new HashMap<>();
            // 该用户所有实验合集
            Set<String> expCodes = new HashSet<>();
            for (Map<String, Object> map : mapList) {
                String expCode = map.getOrDefault("abExpCode", "").toString();
                expCodes.add(expCode);
                if ("555".equals(expCode)) {
                    configMap = JSONObject.parseObject(map.get("configValue").toString(), Map.class);
                }
            }
            // 先判断是否开启实验 和是否不出广告时间 而后判断默认0-8
            // 不出广告时间判定
            int hourOfDay = DateUtils.getCurrentHour();
            Boolean condition1 = abTestConfigContainer.inAdTimeTest(requestParam.getAbExpInfo());
            Boolean condition2 = abTestConfigContainer.containsCode(configMap, requestParam.getAbTestCode());
            if (condition1
                    &&
                    condition2
                    &&
                    abTestConfigContainer.inWithoutAdTime(configMap, requestParam.getAbTestCode(), hourOfDay)) {
                // 开启555 & 555的配置包含abcode & 命中555为这个code配置的不出广告时间
                result.put("ad_predict", 1);
                result.put("no_ad_strategy", "no_ad_time_with_time_plan");
                return result;
            } else if (
                    (!condition1 || !condition2) && 0 <= hourOfDay && hourOfDay < 8 && !isAdvanceShowAd()) {
                // 0点到8点 && （未开启555 || 555配置不包含abcode）
                result.put("ad_predict", 1);
                result.put("no_ad_strategy", "no_ad_time_with_fixed_time");
                return result;
            }

            // if (AbUtil.isInAbExp(expCodes, requestParam.getAppType(), requestParam.getNewExpGroup(), "713")) {
            //     PredictContext context = ConvertUtil.predictParam2Context(requestParam);
            //     Map<String, Object> resultMap = predictServiceV2.predict(context);
            //     logHubService.crowdChooseLogUpload(context);
            //     resultMap.put("pqtId", requestParam.getPqtId());
            //     return resultMap;
            // }

            String abtestId = null;
            String abTestConfigTag = null;
            Map<String, Object> abtestParam = null;
            String midGroup = null;
            String shareType = null;

            String[] appIdArr = oldExpGroupAppId.split(",");
            // 新老实验系统
            List<String> appIdList = Arrays.asList(appIdArr);
            if (appIdList.contains(requestParam.getAppType().toString())) {
//            if(appIdArr.contains(requestParam.getAppType())){
                String[] abParamArr = abConfig.getAbParams(requestParam.getAbTestCode(), requestParam.getAbExpInfo());
                if (abParamArr == null) {
                    abParamArr = NewExpUserGroupConfig.newExpUserGroupMap.get(requestParam.getAppType().toString());
                    if (abParamArr == null) {
                        result.put("msg", "abConfig_error");
                        return result;
                    }
                }
                abtestId = abParamArr[0];
                abTestConfigTag = abParamArr[1];
                HashMap<String, Map<String, Object>> abConfigMap = abConfig.getAbConfigMap();
                abtestParam = abConfigMap.getOrDefault(abtestId + "-" + abTestConfigTag, null);
                if (abtestParam == null) {
                    result.put("msg", "abConfig_error");
                    return result;
                }
                // Determine the group to which mid belongs
                String groupClassKey = (String) abtestParam.get("group_class_key");
                String midGroupKeyName = RuleRedisKeyConst.KEY_NAME_PREFIX_MID_GROUP + groupClassKey + ":" + requestParam.getMid();
                midGroup = redisHelper.getString(midGroupKeyName);
//        if(expCodes.contains(adPredict90DNoShareUserExpCode)){
//            midGroup = redisHelper.getString(RuleRedisKeyConst.KEY_NAME_PREFIX_90_SHARE_TYPE+requestParam.getMid());
//        }else {
//            midGroup = redisHelper.getString(midGroupKeyName);
//        }
                // 没有时为新用户或者无分享用户
                shareType = midGroup;
                if (midGroup == null) {
                    midGroup = "mean_group";
                    shareType = "noShare";
                }
                String[] noAdMidGroupList = new String[0];
                noAdMidGroupList = ((JSONArray) abtestParam.get("no_ad_mid_group_list")).toArray(noAdMidGroupList);

                boolean inNoAdGroup = false;
                for (String group : noAdMidGroupList) {
                    if (group.equals(midGroup)) {
                        inNoAdGroup = true;
                        break;
                    }
                }
                // 不出广告组
                if (inNoAdGroup) {
                    // User is in the no-ad group, no ad should be shown
                    result.put("mid_group", midGroup);
                    result.put("ad_predict", 1);
                    result.put("no_ad_strategy", "no_ad_mid_group_with_video");
                    return result;
                }
                // top1广告不出视频
                Map<String, List<String>> noAdGroupWithVideoMapping = (Map) abtestParam.getOrDefault("no_ad_group_with_video_mapping", new HashMap<>());
                if (noAdGroupWithVideoMapping.keySet().contains(midGroup)
                        &&
                        topOneVideoContainer.inNoAdTopVideo(requestParam.getAppType().longValue(), requestParam.getVideoId())
                ) {
                    result.put("mid_group", midGroup);
                    result.put("ad_predict", 1);
                    result.put("no_ad_strategy", "no_ad_mid_group_with_video");
                    return result;
                }
            } else {
                String midGroupKeyName = RuleRedisKeyConst.KEY_NAME_PREFIX_MID_GROUP + "class1:" + requestParam.getMid();
                midGroup = redisHelper.getString(midGroupKeyName);
                shareType = midGroup;
                if (midGroup == null) {
                    midGroup = "mean_group";
                    shareType = "noShare";
                }
            }

            // 市-中文
            requestParam.setRegion(requestParam.getRegion().replace("省", ""));
            requestParam.setCity(requestParam.getCity().replace("市", ""));
            requestParam.setShareType(shareType);
            String userLayer = userService.getUserLayer4Level(requestParam.getMid());
            requestParam.setUserLayer(userLayer);

            // 先走rootSessionId 实验
            // Map<String, Object> predict = rootSessionIdPredict.predict(ConvertUtil.predictParam2Context(requestParam));
            // if (MapUtils.isNotEmpty(predict)) {
            //     return predict;
            // }

            Map<String, Object> userLayerPredict = userLayerRootSessionIdPredict.predict(ConvertUtil.predictParam2Context(requestParam));
            if (MapUtils.isNotEmpty(userLayerPredict)) {
                return userLayerPredict;
            }

            if (expCodes.contains("673")) {
                Map<String, Object> predictResult = predictStrategyBy673.predict(ConvertUtil.predictParam2Context(requestParam));
                if (Objects.nonNull(predictResult)) {
                    return predictResult;
                }
            }

            if (expCodes.contains("599")){
                return predictStrategyBy599.predict(ConvertUtil.predictParam2Context(requestParam));
            }else if (expCodes.contains("667")){
                return predictStrategyBy667.predict(ConvertUtil.predictParam2Context(requestParam));
            }else {
                return predictStrategyBy599.predict(ConvertUtil.predictParam2Context(requestParam));
            }

            // 设置信息
            // ThresholdPredictModelParam modelParam = ThresholdPredictModelParam.builder()
            //         .build();
            // BeanUtils.copyProperties(requestParam, modelParam);
            // modelParam.setDate(new Date());
            // modelParam.setAbtestId(abtestId);
            // modelParam.setAbTestConfigTag(abTestConfigTag);
            // modelParam.setAbtestParam(abtestParam);
            // modelParam.setMidGroup(midGroup);
            // modelParam.setExtraParam(new HashMap<>());
            // modelParam.addUserExtraFuture("shareType", shareType);
            // setExtraParam(modelParam);

            // String appTypeStr = requestParam.getAppType().toString();
            //
            // List<String> userSourceAndLayerAdRateExpIds = Arrays.asList(userSourceLayerAdRateExpIds.split(","));
            // boolean inAdRateExp = this.checkInAnyExpIdsAndNewAb(expCodes, userSourceAndLayerAdRateExpIds, appTypeStr, requestParam.getNewExpGroup(), modelParam);
            // if (inAdRateExp) {
            //     result = ThresholdModelContainer.getThresholdPredictModel("random673").predict(modelParam);
            //     // 如果676实验返回结果，表示未命中规则即对应的用户来源和所属层存在配置，使用676实验的结果，否则继续走599实验
            //     if (Objects.nonNull(result)) {
            //         return result;
            //     }
            // }
            // // 新老实验系统兼容
            // if (expCodes.contains("599") ||
            //         (expCodes.contains(NewExpInfoHelper.flagId) && NewExpInfoHelper.checkInNewExpGroupAndSetParamIfIn(
            //                 requestParam.getAppType().toString(), requestParam.getNewExpGroup(), "599", modelParam))) {
            //     // NewExpInfoHelper.flagId   647
            //     result = ThresholdModelContainer.
            //             getThresholdPredictModel("random")
            //             .predict(modelParam);
            // } else if (expCodes.contains("667") ||
            //         (expCodes.contains(NewExpInfoHelper.flagId) && NewExpInfoHelper.checkInNewExpGroupAndSetParamIfIn(
            //                 requestParam.getAppType().toString(), requestParam.getNewExpGroup(), "667", modelParam))) {
            //     // NewExpInfoHelper.flagId   647
            //     result = ThresholdModelContainer.
            //             getThresholdPredictModel("random667")
            //             .predict(modelParam);
            // } else if (inExpList(expCodes, adPredictImmersionExpCode)
            //         ||
            //         (expCodes.contains(NewExpInfoHelper.flagId) && NewExpInfoHelper.checkInNewExpGroupAndSetParamIfIn(
            //                 requestParam.getAppType().toString(), requestParam.getNewExpGroup(), "607", modelParam))) {
            //     // adPredictImmersionExpCode 607 631
            //     result = ThresholdModelContainer.
            //             getThresholdPredictModel("immersion")
            //             .predict(modelParam);
            // } else {
            //     Object thresholdMixFunc = abtestParam.getOrDefault("threshold_mix_func", "basic");
            //     result = ThresholdModelContainer.
            //             getThresholdPredictModel(thresholdMixFunc.toString())
            //             .predict(modelParam);
            // }
            // return result;
        } catch (Exception e) {
            log.error("svc=adPredict appType={} group={} newGroup={} pqtId={}"
                    , requestParam.getAppType(), requestParam.getAbTestCode(), requestParam.getNewExpGroup(), requestParam.getPqtId(), e);
            result.put("ad_predict", 1);
            result.put("no_ad_strategy", "error");
            return result;
        }

    }

    public Map<String, Object> adRecommendPredictByRoiModel(RoiPredictModelRequestParam requestParam) {

        Map<String, Object> result = new HashMap<>();
        int hourOfDay = DateUtils.getCurrentHour();
        if (hourOfDay < 8 && hourOfDay >= 0) {
            result.put("ad_predict", 1);
            result.put("no_ad_strategy", "no_ad_time");
            return result;
        }
        RoiThresholdPredictModelParam modelParam = new RoiThresholdPredictModelParam();
        BeanUtils.copyProperties(requestParam, modelParam);
        modelParam.setEcpm(requestParam.getAds().get(0).getEcpm());
        modelParam.setAdId(requestParam.getAds().get(0).getAdId());
        modelParam.setAdType(requestParam.getAds().get(0).getAdType());

        String groupClassKey = (String) ((Map<String, Object>) roiModelConfig.get(AppTypeEnum.valueOf(modelParam.getAppType()).name())).get("group_class_key");
        String midGroupKeyName = RuleRedisKeyConst.KEY_NAME_PREFIX_MID_GROUP + groupClassKey + ":" + modelParam.getMid();
        String midGroup = redisHelper.get(midGroupKeyName);
        if (midGroup == null) {
            midGroup = "mean_group";
        }
        modelParam.setMidGroup(midGroup);

        return ThresholdModelContainer.
                getThresholdPredictModel("roi")
                .predict(modelParam);
    }

    public void setExtraParam(ThresholdPredictModelParam modelParam) {
        List<String> idList = Arrays.asList(testIds.split(","));
        List<Map<String, Object>> mapList = (List) modelParam.getAbExpInfo().get("ab_test002");
        mapList.sort(new Comparator<Map<String, Object>>() {
            @Override
            public int compare(Map<String, Object> map1, Map<String, Object> map2) {
                int abExpCode1 = Integer.parseInt(map1.get("abExpCode").toString());
                int abExpCode2 = Integer.parseInt(map2.get("abExpCode").toString());
                return Integer.compare(abExpCode1, abExpCode2);
            }
        });
        Map<String, Object> configMap;
        for (Map<String, Object> map : mapList) {
            if (idList.contains(map.getOrDefault("abExpCode", ""))) {
                configMap = JSONObject.parseObject(map.get("configValue").toString(), new TypeReference<Map<String, Object>>() {
                });
                for (Map.Entry<String, Object> entry : configMap.entrySet()) {
                    modelParam.getExtraParam().put(entry.getKey(), entry.getValue());
                }
            }
        }


    }


    private boolean inExpList(Set<String> set, String expCodes) {
        String[] expArr = expCodes.split(",");
        for (String str : expArr) {
            if (set.contains(str)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断用户是否处于某些实验中
     *
     * @param userExpIdSet 用户所在的实验
     * @param expIds       需要判断的实验
     */
    private boolean checkInAnyExpIdsAndNewAb(Collection<String> userExpIdSet, Collection<String> expIds, String appId, int groupNumber, ThresholdPredictModelParam modelParam) {
        for (String expId : expIds) {
            // 如果包含，返回true
            if (userExpIdSet.contains(expId)) {
                return true;
            }
            // 在新实验系统中有配置
            if (NewExpInfoHelper.checkInNewExpGroupAndSetParamIfIn(userExpIdSet, appId, groupNumber, expId, modelParam)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否提前出广告
     *
     * @return true-提前出，false-不提前出
     */
    private boolean isAdvanceShowAd() {

        // 节日期间，6点开始出广告，不是节日，走下面的逻辑
        if (isEarlyMorningHoliday()) {
            return true;
        }

        // 提前出广告是否生效，不生效表示不用提前出广告，返回false。默认生效
        if (!advanceShowAdSwitch) {
            return false;
        }

        // 提前出广告全局开关，如果开启不走定时任务的判断
        if (advanceShowAdGlobalFlag) {
            return true;
        }

        // 提前出广告标识，由ad后端的定时任务写入，为1表示提前出广告
        String day = DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDateTime.now());
        String redisKey = String.format(RedisPrefixEnum.ADVANCE_SHOW_AD_FLAG.getPrefix(), day);
        String flag = adRedisHelper.get(redisKey);
        log.info("提前出广告标识: {} ---> {}", redisKey, flag);
        return StringUtils.equals("1", flag);
    }

    /**
     * 判断当前时间是否在6点之后且为节日
     *
     * @return true-当前时间在6点之后且为节日，false-不满足条件
     */
	    public boolean isEarlyMorningHoliday() {
	        try {
	            // 获取当前小时，6点之前直接返回 false，无需查询节日服务或缓存
	            int currentHour = DateUtils.getCurrentHour();
	            if (currentHour < 6) {
	                return false;
	            }

	            String day = DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDateTime.now());
	            String redisKey = String.format(RedisPrefixEnum.ADVANCE_HOLIDAY_FLAG.getPrefix(), day);
	            String flag = adRedisHelper.get(redisKey);

	            // 先从缓存读取当日节日标记
	            if (StringUtils.isNotBlank(flag)) {
	                boolean isHoliday = StringUtils.equals("1", flag);
	                return isHoliday;
	            }

	            // 调用节日服务判断今日是否为节日
	            boolean isHoliday = holidayService.isTodayHoliday();

	            // 查询结果回种缓存，保存 10 小时
	            String cacheVal = isHoliday ? "1" : "0";
	            boolean cacheResult = adRedisHelper.set(redisKey, cacheVal, 10 * 60 * 60);
	            log.info("写入早晨节日标记缓存: {} -> {}, ttl=10h, success={}", redisKey, cacheVal, cacheResult);

	            if (isHoliday) {
	                return true;
	            } else {
	                return false;
	            }

	        } catch (Exception e) {
	            log.error("判断早晨节日时间异常", e);
	            // 异常情况下返回false，确保系统稳定性
	            return false;
	        }
	    }
}
