package com.tzld.piaoquan.recommend.server.service;

import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.reflect.TypeToken;
import com.tzld.piaoquan.recommend.server.common.RedisKeyConstants;
import com.tzld.piaoquan.recommend.server.common.base.Constant;
import com.tzld.piaoquan.recommend.server.gen.common.Result;
import com.tzld.piaoquan.recommend.server.gen.recommend.*;
import com.tzld.piaoquan.recommend.server.model.MachineInfo;
import com.tzld.piaoquan.recommend.server.model.RecommendParam;
import com.tzld.piaoquan.recommend.server.model.Video;
import com.tzld.piaoquan.recommend.server.remote.ABTestRemoteService;
import com.tzld.piaoquan.recommend.server.service.flowpool.FlowPoolConfigService;
import com.tzld.piaoquan.recommend.server.service.flowpool.FlowPoolConstants;
import com.tzld.piaoquan.recommend.server.service.flowpool.FlowPoolService;
import com.tzld.piaoquan.recommend.server.service.rank.RankParam;
import com.tzld.piaoquan.recommend.server.service.rank.RankResult;
import com.tzld.piaoquan.recommend.server.service.rank.RankRouter;
import com.tzld.piaoquan.recommend.server.service.recall.RecallParam;
import com.tzld.piaoquan.recommend.server.service.recall.RecallResult;
import com.tzld.piaoquan.recommend.server.service.recall.RecallService;
import com.tzld.piaoquan.recommend.server.service.recall.strategy.*;
import com.tzld.piaoquan.recommend.server.util.CommonCollectionUtils;
import com.tzld.piaoquan.recommend.server.util.JSONUtils;
import com.tzld.piaoquan.recommend.server.util.ParserUtils;
import com.tzld.piaoquan.recommend.server.util.TraceUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author dyp
 */
@Service
@Slf4j
public class RecommendService {

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate<String, String> redisTemplate;


    private final Map<Integer, String> ab_initial_config_map = new HashMap<>();

    @ApolloJsonValue("${ab_exp_code:{}}")
    private Map<String, Map<String, String>> abExpCodeMap;

    @ApolloJsonValue("${app_ab_code:{}}")
    private Map<String, Map<String, Map<String, String>>> appAbCodeMap;

    @ApolloJsonValue("${flow.pool.ids:[7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}")
    private List<Integer> flowPoolIds;
    @ApolloJsonValue("${city_code:[]}")
    private Set<String> cityCodes;

    @ApolloJsonValue("${new.exp.list:[6]}")
    private Set<Integer> newExpList;

    @ApolloJsonValue("${new.exp,config.v2:[]}")
    private List<ExpConfig> expConfigs;

    @Value("${new.exp.abtest.service.switch:true}")
    private boolean newExpABTestServiceSwitch;

    @Autowired
    private FlowPoolConfigService flowPoolConfigService;
    @Autowired
    private RecallService recallService;
    @Autowired
    private RankRouter rankRouter;
    @Autowired
    private PreViewedService preViewedService;
    @Autowired
    private FlowPoolService flowPoolService;
    @Autowired
    private StatisticsLogService statisticsLogService;
    @Autowired
    private TimerLogService timerLogService;

    @Autowired
    private ABTestRemoteService abTestRemoteService;

    private ThreadLocal<Map<String, Object>> timerLogMapTL = ThreadLocal.withInitial(HashMap::new);

    @Value("${risk.video.filter.switch:true}")
    private boolean riskVideoFilterSwitch;
    @ApolloJsonValue("${risk.scene:[]}")
    private Set<Long> riskScenes;

    private LoadingCache<String, Set<String>> riskUserCache = CacheBuilder.newBuilder()
            .maximumSize(5)
            .refreshAfterWrite(300, TimeUnit.SECONDS)
            .expireAfterWrite(300, TimeUnit.SECONDS)
            .expireAfterAccess(300, TimeUnit.SECONDS)
            .build(new CacheLoader<String, Set<String>>() {
                @Override
                public Set<String> load(String key) {
                    Set<String> result = redisTemplate.opsForSet().members(key);
                    if (CollectionUtils.isEmpty(result)) {
                        return Collections.emptySet();
                    }
                    return result;
                }
            });

    @PostConstruct
    public void init() {
        Set<String> data1 = riskUserCache.getUnchecked(RedisKeyConstants.Recommend.riskUserUid);
        Set<String> data2 = riskUserCache.getUnchecked(RedisKeyConstants.Recommend.riskUserMid);
        log.info("riskUserCache uid size {}  mid size {}", data1.size(), data2.size());
    }

    public RecommendResponse homepageRecommend(RecommendRequest request) {
        return recommend(request, 0);
    }

    public RecommendResponse relevantRecommend(RecommendRequest request) {
        return recommend(request, 1);
    }

    public RecommendResponse recommend(RecommendRequest request, int recommendType) {
        try {
            timerLogMapTL.get().put("traceId", TraceUtils.currentTraceId());

            if (request == null) {
                return RecommendResponse.newBuilder()
                        .setResult(Result.newBuilder().setCode(1).setMessage("success"))
                        .build();
            }

            timerLogMapTL.get().put("appType", request.getAppType());
            timerLogMapTL.get().put("recommendType", recommendType);

            if (request.getVersionAuditStatus() == 1) {
                timerLogMapTL.get().put("special", true);
                return specialMidRecommend(request);
            }
            Stopwatch stopwatch = Stopwatch.createStarted();
            if (StringUtils.isNotBlank(request.getMid())
                    && redisTemplate.opsForSet().isMember("special:mid", request.getMid())) {
                return specialMidRecommend(request);
            }
            stopwatch.reset().start();
            RecommendParam param = genRecommendParam(request, recommendType);
            long genRecommendParamTime = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
            timerLogMapTL.get().put("genRecommendParamTime", genRecommendParamTime);
            log.info("genRecommendParam={},genRecommendParam cost={}", JSONUtils.toJson(param),
                    genRecommendParamTime);
            List<Video> videos = videoRecommend(param);
            stopwatch.reset().start();
            updateCache(request, param, videos);
            long updateCacheTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
            timerLogMapTL.get().put("updateCacheTime", updateCacheTime);
            timerLogMapTL.get().put("abCodeFake", param.getAbCode());
            timerLogService.log(timerLogMapTL.get());
            logStatisticsInfo(request, param, videos);
            // 更新position
            List<VideoProto> vps = new ArrayList<>();
            for (int i = 0; i < videos.size(); i++) {
                Map<String, List<String>> pushFromIndex = videos.get(i).getPushFromIndex();
                List<PushFromIndex> pushFromIndexList = new ArrayList<>();
                if (MapUtils.isNotEmpty(pushFromIndex)) {
                    pushFromIndex.forEach((k, v) -> {
                        PushFromIndex.Builder builder = PushFromIndex.newBuilder().setPushFrom(k);
                        v.forEach(builder::addIndex);
                        pushFromIndexList.add(builder.build());
                    });
                }
                vps.add(VideoProto.newBuilder()
                        .setPosition(i + 1)
                        .setPushFrom(Strings.nullToEmpty(videos.get(i).getPushFrom()))
                        .setAbCode(Strings.nullToEmpty(videos.get(i).getAbCode()))
                        .setVideoId(videos.get(i).getVideoId())
                        .setRovScore(videos.get(i).getRovScore())
                        .setSortScore(videos.get(i).getSortScore())
                        .setFlowPool(Strings.nullToEmpty(videos.get(i).getFlowPool()))
                        .setIsInFlowPool(videos.get(i).isInFlowPool() ? 1 : 0)
                        .setRand(videos.get(i).getRand())
                        .addAllPushFromIndex(pushFromIndexList)
                        .build());
            }

            return RecommendResponse.newBuilder()
                    .setResult(Result.newBuilder().setCode(1).setMessage("success"))
                    .addAllVideo(vps)
                    .build();
        } finally {
            timerLogMapTL.remove();
        }

    }

    private void logStatisticsInfo(RecommendRequest request, RecommendParam param, List<Video> videos) {

        if (CollectionUtils.isEmpty(videos)) {
            return;
        }

        try {
            List<Map<String, String>> data = videos.stream().map(v -> {
                Map<String, String> map = new HashMap<>();

                map.put("traceId", String.valueOf(TraceUtils.currentTraceId()));
                map.put("recommendTraceId", Strings.nullToEmpty(request.getRecommendTraceId()));

                // TODO user
                map.put("sessionId", request.getSessionId());
                map.put("subsessionid", request.getSubSessionId());
                map.put("mid", param.getMid());

                // ab
                map.put("newexpgroup", request.getNewExpGroup());

                // scene
                map.put("apptype", String.valueOf(request.getAppType()));
                map.put("pagesource", request.getPageSource());

                // video
                map.put("videoId", String.valueOf(v.getVideoId()));
                map.put("relevantVideoId", String.valueOf(request.getVideoId()));
                map.put("rovScore", String.valueOf(v.getRovScore()));
                map.put("sortScore", String.valueOf(v.getSortScore()));
                map.put("pushFrom", String.valueOf(v.getPushFrom()));

                map.put("flowPool", String.valueOf(v.getFlowPool()));
                map.put("level", String.valueOf(v.getLevel()));
                map.put("flowPoolAbtestGroup", String.valueOf(v.getFlowPoolAbtestGroup()));
                map.put("inFlowPool", String.valueOf(v.isInFlowPool()));

                map.put("scoreRos", String.valueOf(v.getScoreRos()));
                map.put("scoreStr", String.valueOf(v.getScoreStr()));
                map.put("score", String.valueOf(v.getScore()));
                map.put("scoresMap", JSONUtils.toJson(v.getScoresMap()));
                map.put("allFeatureMap", JSONUtils.toJson(v.getAllFeatureMap()));
                map.put("metaFeatureMap", JSONUtils.toJson(v.getMetaFeatureMap()));

                map.put("pushFromRank", JSONUtils.toJson(v.getPushFromRank()));
                map.put("abExpCode", JSONUtils.toJson(param.getAbExpCodes()));

                return map;

            }).collect(Collectors.toList());

            statisticsLogService.log(data);
        } catch (Exception e) {
            log.error("logStatisticsInfo error", e);
        }
    }

    public RecommendResponse specialMidRecommend(RecommendRequest request) {
        log.info("hit special mid recommend request={}", JSONUtils.toJson(request));
        if (request == null) {
            return RecommendResponse.newBuilder()
                    .setResult(Result.newBuilder().setCode(1).setMessage("success"))
                    .build();
        }

        RecallParam recallParam = new RecallParam();
        recallParam.setAppType(request.getAppType());
        recallParam.setMid(request.getMid());
        recallParam.setSpecialRecommend(true);
        recallParam.setSize(request.getSize());
        recallParam.setProvince(request.getProvince());
        recallParam.setCityCode(request.getCityCode());

        RecallResult recallResult = recallService.recall(recallParam);

        RankParam rankParam = new RankParam();
        rankParam.setRecallResult(recallResult);
        rankParam.setSize(request.getSize());
        rankParam.setSpecialRecommend(true);

        RankResult rankResult = rankRouter.rank(rankParam);

        if (rankResult == null || CollectionUtils.isEmpty(rankResult.getVideos())) {
            return RecommendResponse.newBuilder()
                    .setResult(Result.newBuilder().setCode(1).setMessage("success"))
                    .build();
        }

        // 只返回size条数据
        List<Video> videos = rankResult.getVideos();
        if (recallParam.getSize() < rankResult.getVideos().size()) {
            videos = rankResult.getVideos().subList(0, recallParam.getSize());
        }

        if (CollectionUtils.isNotEmpty(videos)) {
            Video lastVideo = videos.get(videos.size() - 1);
            redisTemplate.opsForValue().set(lastVideo.getLastVideoKey(), String.valueOf(lastVideo.getVideoId()),
                    1, TimeUnit.DAYS);
        }

        return RecommendResponse.newBuilder()
                .setResult(Result.newBuilder().setCode(1).setMessage("success"))
                .addAllVideo(CommonCollectionUtils.toList(videos, v -> VideoProto.newBuilder()
                        .setPushFrom(Strings.nullToEmpty(v.getPushFrom()))
                        .setAbCode(Strings.nullToEmpty(v.getAbCode()))
                        .setVideoId(v.getVideoId())
                        .setRovScore(v.getRovScore())
                        .build()))
                .build();
    }

    public RecommendParam genRecommendParam(RecommendRequest request, int recommendType) {
        RecommendParam param = new RecommendParam();
        param.setTopK(3);
        param.setFlowPoolP(0.3);
        param.setMid(request.getMid());
        param.setUid(request.getUid());
        param.setSize(request.getSize());
        param.setVideoId(request.getVideoId());
        param.setCityCode(request.getCityCode());
        param.setProvinceCode(request.getProvinceCode());
        param.setAppType(request.getAppType());
        param.setProvince(request.getProvince());
        param.setCity(request.getCity());


        String abInitialConfig = ab_initial_config_map.getOrDefault(request.getAppType(), "095-1");
        Map<String, String> abExpCode = abExpCodeMap.getOrDefault(abInitialConfig, Collections.emptyMap());
        param.setAbCode(abExpCode.get("ab_code"));
        param.setRuleKey(abExpCode.get("rule_key"));
        param.setDataKey(abExpCode.get("data_key"));
        param.setRankKeyPrefix("rank:score1:");

        // 实验配置
        if (request.getAbExpCodeCount() > 0) {
            Set<String> abExpCodes = request.getAbExpCodeList().stream().collect(Collectors.toSet());
            param.setAbExpCodes(abExpCodes);
            // 流量池视频分发概率
            if (abExpCodes.contains("211")) {
                param.setFlowPoolP(0.9);
            } else if (abExpCodes.contains("221")) {
                param.setFlowPoolP(0.7);
            } else if (abExpCodes.contains("299")) {
                param.setFlowPoolP(0.5);
            } else if (abExpCodes.contains("300")) {
                param.setFlowPoolP(0.4);
            } else if (abExpCodes.contains("301")) {
                param.setFlowPoolP(0.6);
            } else if (abExpCodes.contains("339")) {
                param.setFlowPoolP(0);
            }

            for (Map.Entry<String, Map<String, String>> entry : abExpCodeMap.entrySet()) {
                if (abExpCodes.contains(entry.getKey())) {
                    param.setAbCode(entry.getValue().get("ab_code"));
                    param.setRuleKey(entry.getValue().get("rule_key"));
                    param.setDataKey(entry.getValue().get("data_key"));

                    param.setHDataKey(entry.getValue().get("h_data_key"));
                    param.setHRuleKey(entry.getValue().get("h_rule_key"));

                    param.setRankKeyPrefix(StringUtils.isNotBlank(entry.getValue().get("rank_key_prefix"))
                            ? entry.getValue().get("rank_key_prefix")
                            : "rank:score1:");
                    break;
                }
            }
        }

        // app实验组
        if (request.getEventIdCount() > 0) {
            if (recommendType == 0) {
                String eventId = request.getEventIdMap().get("10003");
                Map<String, Map<String, String>> appAbCode = appAbCodeMap.get("10003");
                if (StringUtils.isNotBlank(eventId)
                        && appAbCode != null
                        && appAbCode.containsKey(eventId)) {
                    param.setAbCode(appAbCode.get(eventId).get("ab_code"));
                    param.setRuleKey(appAbCode.get(eventId).get("rule_key"));
                    param.setDataKey(appAbCode.get(eventId).get("data_key"));
                }
            }
        }

        // 流量池分发实验组划分
        int flowPoolIdChoice = flowPoolIds.get(RandomUtils.nextInt(0, flowPoolIds.size()));
        Map<String, List<Integer>> flowPoolConfig = flowPoolConfigService.getFlowPoolConfig();
        Set<String> abExpCodes = param.getAbExpCodes();
        if (Objects.nonNull(abExpCodes) && abExpCodes.contains(Constant.SUPPLY_AB_CODE_ID)) {
//        if (Objects.equals(Constant.SUPPLY_AB_CODE, param.getAbCode())) {
            List<Integer> supplyFlowPoolIdList = flowPoolConfig.get(FlowPoolConstants.SUPPLY_FLOW_SET_LEVEL);
            if (Objects.nonNull(supplyFlowPoolIdList) && !supplyFlowPoolIdList.isEmpty()) {
                flowPoolIdChoice = supplyFlowPoolIdList.get(0);
                param.setFlowPoolId(flowPoolIdChoice);
                param.setFlowPoolAbtestGroup(FlowPoolConstants.SUPPLY_FLOW_SET_LEVEL);
            }
        } else {
            param.setFlowPoolId(flowPoolIdChoice);
            param.setFlowPoolAbtestGroup("control_group");
            for (Map.Entry<String, List<Integer>> entry : flowPoolConfig.entrySet()) {
                if (entry.getValue().contains(flowPoolIdChoice)) {
                    param.setFlowPoolAbtestGroup(entry.getKey());
                }
            }
        }
        // @desc 新的流量池分发实验组划分，每个尾号不同策略分组。 @time 20240318 @author 张博
        int lastDigit = RandomUtils.nextInt(0, 10);
        param.setLastDigit(lastDigit);

        // 风险过滤
        if (riskVideoFilterSwitch) {
            boolean riskUser = riskScenes.contains(request.getHotSceneType())
                    || riskUserCache.getUnchecked(RedisKeyConstants.Recommend.riskUserUid).contains(param.getUid())
                    || riskUserCache.getUnchecked(RedisKeyConstants.Recommend.riskUserMid).contains(param.getMid());
            param.setRiskUser(riskUser);
        }


        if (request.hasMachineInfo()) {
            MachineInfoProto machineInfoProto = request.getMachineInfo();
            MachineInfo machineInfo = new MachineInfo();
            machineInfo.setBrand(machineInfoProto.getBrand());
            machineInfo.setModel(machineInfoProto.getModel());
            machineInfo.setPlatform(machineInfoProto.getPlatform());
            machineInfo.setSdkVersion(machineInfoProto.getSdkVersion());
            machineInfo.setSystem(machineInfoProto.getSystem());
            machineInfo.setWechatVersion(machineInfoProto.getWechatVersion());
            param.setMachineInfo(machineInfo);
        }


        // TODO：hard code 为了快速做AB验证，应该由AB系统支持
        if (newExpList.contains(param.getAppType())) {
            if (newExpABTestServiceSwitch) {
                param.setExpIdMap(abTestRemoteService.getExp(param.getMid(), param.getUid(), param.getAppType(),
                        request.getNewExpGroup()));
            } else {
                Map<String, List<Layer>> layerMap = CommonCollectionUtils.toMap(expConfigs, c -> c.getAppType(),
                        c -> c.getLayers());
                if (MapUtils.isNotEmpty(layerMap)) {
                    Map<String, Integer> bucketMap = JSONUtils.fromJson(request.getNewExpGroup(),
                            new TypeToken<Map<String, Integer>>() {
                            }, Collections.emptyMap());
                    if (MapUtils.isNotEmpty(bucketMap)) {
                        List<Layer> layers = layerMap.get(param.getAppType() + "");

                        if (CollectionUtils.isNotEmpty(layers)) {
                            Map<String, String> expIdMap = new HashMap<>();
                            for (Layer layer : layers) {
                                for (Exp exp : layer.getExps()) {
                                    if (bucketMap.containsKey(layer.getLayerId())
                                            && exp.getRange().length == 2
                                            && exp.getRange()[0] <= bucketMap.get(layer.getLayerId())
                                            && exp.getRange()[1] >= bucketMap.get(layer.getLayerId())) {
                                        expIdMap.put(layer.getLayerId(), exp.getExpId());
                                    }
                                }
                            }
                            param.setExpIdMap(expIdMap);
                        }
                    }
                }
            }
        }
        if (StringUtils.isNotBlank(request.getCategoryId()) && StringUtils.isNumeric(request.getCategoryId())) {
            param.setCategoryId(Integer.parseInt(request.getCategoryId()));
        }

        param.setHotSceneType(request.getHotSceneType());
        param.setClientIp(request.getClientIp());

        return param;
    }

    private List<Video> videoRecommend(RecommendParam param) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        RecallResult recallResult = recallService.recall(convertToRecallParam(param));

        long recallTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
        timerLogMapTL.get().put("recallTime", recallTime);
        log.info("recallResult={}, videoRecommend recallResult cost={}", recallResult, recallTime);
        stopwatch.reset().start();

        RankResult rankResult = rankRouter.rank(convertToRankParam(param, recallResult));

        long rankTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
        timerLogMapTL.get().put("rankTime", rankTime);
        log.info("rankResult={}, videoRecommend rank cost={}", rankResult, rankTime);


        if (rankResult == null || CollectionUtils.isEmpty(rankResult.getVideos())) {
            return Collections.emptyList();
        }

        // 只返回size条数据
        List<Video> videos = rankResult.getVideos();
        if (param.getSize() < rankResult.getVideos().size()) {
            videos = rankResult.getVideos().subList(0, param.getSize());
        }


        // mark video in flow pool
        // TODO 业务上有什么用处
        // 需要被标记的视频满足什么条件
        // 逻辑已经废弃
        // markFlowPoolVideo(param, videos);

        return videos;
    }

    public RecallParam convertToRecallParam(RecommendParam param) {
        RecallParam recallParam = new RecallParam();
        recallParam.setAppType(param.getAppType());
        // hard code 算法实验配置化之前，复用abcode做AB验证
        // note 避免非实验产品被覆盖
        recallParam.setAbCode(param.getAbCode());
        recallParam.setRuleKey(param.getRuleKey());
        recallParam.setDataKey(param.getDataKey());
        recallParam.setHRuleKey(param.getHRuleKey());
        recallParam.setHDataKey(param.getHDataKey());
        if (newExpList.contains(param.getAppType())) {
            recallParam.setAbCode("");
            if (MapUtils.isNotEmpty(param.getExpIdMap()) && param.getExpIdMap().containsKey("recall")) {
                String expId = param.getExpIdMap().get("recall");
                if (abExpCodeMap.containsKey(expId)) {
                    recallParam.setAbCode(abExpCodeMap.get(expId).get("ab_code"));
                    recallParam.setRuleKey(abExpCodeMap.get(expId).get("rule_key"));
                    recallParam.setDataKey(abExpCodeMap.get(expId).get("data_key"));
                    recallParam.setHDataKey(abExpCodeMap.get(expId).get("h_data_key"));
                    recallParam.setHRuleKey(abExpCodeMap.get(expId).get("h_rule_key"));
                }
            }
        }

        recallParam.setVideoId(param.getVideoId());
        recallParam.setFlowPoolAbtestGroup(param.getFlowPoolAbtestGroup());
        recallParam.setLastDigit(param.getLastDigit());

        String provinceCode = StringUtils.isNotBlank(param.getProvinceCode())
                ? param.getProvinceCode()
                : "";
        String cityCode = StringUtils.isNotBlank(param.getCityCode())
                ? param.getCityCode()
                : "";
        recallParam.setRegionCode(cityCodes.contains(cityCode) ? cityCode : provinceCode);
        recallParam.setMid(param.getMid());
        recallParam.setSize(param.getSize());
        recallParam.setUid(param.getUid());
        //风险过滤
        recallParam.setRiskUser(param.isRiskUser());
        recallParam.setAbExpCodes(param.getAbExpCodes());

        recallParam.setProvince(param.getProvince());
        recallParam.setExpIdMap(param.getExpIdMap());
        recallParam.setCategoryId(param.getCategoryId());

        recallParam.setCityCode(param.getCityCode());
        // recallParam.setAbCode(param.getAbCode());

        recallParam.setHotSceneType(param.getHotSceneType());
        recallParam.setClientIp(param.getClientIp());

        return recallParam;
    }


    public RankParam convertToRankParam(RecommendParam param, RecallResult recallResult) {
        RankParam rankParam = new RankParam();
        rankParam.setRecallResult(recallResult);
        // hard code 算法实验配置化之前，复用abcode做AB验证
        // note 避免非实验产品被覆盖
        rankParam.setAbCode(param.getAbCode());
        rankParam.setRankKeyPrefix(param.getRankKeyPrefix());
        if (newExpList.contains(param.getAppType())) {
            rankParam.setAbCode("");
//            if (MapUtils.isNotEmpty(param.getExpIdMap())
//                    && param.getExpIdMap().containsKey("rank")) {
//                String expId = param.getExpIdMap().get("rank");
            // TODO hard code  MVP版本 为了快速验证，对rank强制染色，不做实验编排
            if (MapUtils.isNotEmpty(param.getExpIdMap())
                    && param.getExpIdMap().containsKey("recall")) {
                String expId = param.getExpIdMap().get("recall");
                if (abExpCodeMap.containsKey(expId)) {
                    rankParam.setAbCode(abExpCodeMap.get(expId).get("ab_code"));
                    rankParam.setRankKeyPrefix(StringUtils.isNotBlank(abExpCodeMap.get(expId).get("rank_key_prefix"))
                            ? abExpCodeMap.get(expId).get("rank_key_prefix")
                            : "rank:score1:");
                }
            }
        }

        rankParam.setSize(param.getSize());
        rankParam.setFlowPoolP(param.getFlowPoolP());
        rankParam.setTopK(param.getTopK());
        rankParam.setAppType(param.getAppType());
        rankParam.setMid(param.getMid());
        rankParam.setProvince(param.getProvince());
        rankParam.setCity(param.getCity());
        rankParam.setMachineInfo(param.getMachineInfo());
        rankParam.setAbExpCodes(param.getAbExpCodes());
        rankParam.setExpIdMap(param.getExpIdMap());
        rankParam.setCategoryId(param.getCategoryId());
        rankParam.setHeadVid(param.getVideoId());
        rankParam.setHotSceneType(param.getHotSceneType());
        return rankParam;
    }

    public void updateCache(RecommendRequest request, RecommendParam param, List<Video> videos) {
        if (StringUtils.isBlank(request.getMid())
                || CollectionUtils.isEmpty(videos)) {
            return;
        }

        preViewedService.updateCache(request.getAppType(), request.getMid(), videos, param.getAbExpCodes());
        updateLastVideoCache(videos);

        updateFlowPoolCache(request, param, videos);

    }

    private void updateFlowPoolCache(RecommendRequest request, RecommendParam param,
                                     List<Video> videos) {
        if (request.getAppType() == 18
                || request.getAppType() == 19
                || CollectionUtils.isEmpty(videos)) {
            return;
        }

        List<Video> flowPoolVideos = videos.stream()
                .filter(v -> v.getPushFrom().equals(FlowPoolConstants.PUSH_FORM))
                .collect(Collectors.toList());


        if (CollectionUtils.isEmpty(flowPoolVideos)) {
            return;
        }

        switch (param.getFlowPoolAbtestGroup()) {
            case FlowPoolConstants.SUPPLY_FLOW_SET_LEVEL:
                flowPoolService.updateSupplyDistributeCountWithLevel(flowPoolVideos);
                break;
            case FlowPoolConstants.EXPERIMENTAL_FLOW_SET_LEVEL:
                flowPoolService.updateDistributeCountWithLevel(flowPoolVideos);
                break;
            case FlowPoolConstants.EXPERIMENTAL_FLOW_SET_LEVEL_SCORE:
                flowPoolService.updateDistributeCountWithLevelScore(flowPoolVideos);
                break;
            default:
                flowPoolService.updateDistributeCountWithScore(flowPoolVideos);
                break;
        }
    }

    private void updateLastVideoCache(List<Video> videos) {

        if (CollectionUtils.isEmpty(videos)) {
            return;
        }

        Consumer<String> consumer = (p) -> {
            for (int i = videos.size() - 1; i >= 0; i--) {
                if (videos.get(i).getPushFrom().equals(p)) {
                    redisTemplate.opsForValue().set(videos.get(i).getLastVideoKey(), String.valueOf(videos.get(i).getVideoId()),
                            24, TimeUnit.HOURS);
                    break;
                }
            }
        };

        consumer.accept(RegionHRecallStrategy.PUSH_FORM);
        consumer.accept(RegionHDupRecallStrategy.PUSH_FORM);
        consumer.accept(Region24HRecallStrategy.PUSH_FORM);
        consumer.accept(RegionRelative24HRecallStrategy.PUSH_FORM);
        consumer.accept(RegionRelative24HDupRecallStrategy.PUSH_FORM);
        consumer.accept(RegionHWithoutDupRecallStrategy.PUSH_FORM);
    }

    public static class ExpConfig {
        private String appType;
        private List<Layer> layers;

        public String getAppType() {
            return appType;
        }

        public void setAppType(String appType) {
            this.appType = appType;
        }

        public List<Layer> getLayers() {
            return layers;
        }

        public void setLayers(List<Layer> layers) {
            this.layers = layers;
        }
    }

    public static class Layer {
        private String layerId;
        private int bucketNum;
        private List<Exp> exps;
        private Map<String, String> groupRule;

        public String getLayerId() {
            return layerId;
        }

        public void setLayerId(String layerId) {
            this.layerId = layerId;
        }

        public int getBucketNum() {
            return bucketNum;
        }

        public void setBucketNum(int bucketNum) {
            this.bucketNum = bucketNum;
        }

        public List<Exp> getExps() {
            return exps;
        }

        public void setExps(List<Exp> exps) {
            this.exps = exps;
        }

        public Map<String, String> getGroupRule() {
            return groupRule;
        }

        public void setGroupRule(Map<String, String> groupRule) {
            this.groupRule = groupRule;
        }
    }

    public static class Exp {
        private String expId;
        private int[] range;

        public String getExpId() {
            return expId;
        }

        public void setExpId(String expId) {
            this.expId = expId;
        }

        public int[] getRange() {
            return range;
        }

        public void setRange(int[] range) {
            this.range = range;
        }
    }

}
