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

import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.tzld.piaoquan.recommend.server.common.ThreadPoolFactory;
import com.tzld.piaoquan.recommend.server.common.base.Constant;
import com.tzld.piaoquan.recommend.server.common.enums.AppTypeEnum;
import com.tzld.piaoquan.recommend.server.model.Video;
import com.tzld.piaoquan.recommend.server.service.filter.strategy.BlacklistContainer;
import com.tzld.piaoquan.recommend.server.service.flowpool.FlowPoolConstants;
import com.tzld.piaoquan.recommend.server.service.recall.strategy.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @author dyp
 */
@Service
@Slf4j
public class RecallService implements ApplicationContextAware {

    private final Map<String, RecallStrategy> strategyMap = new HashMap<>();
    private ApplicationContext applicationContext;
    private final ExecutorService pool = ThreadPoolFactory.recallPool();
    @ApolloJsonValue("${last.digit.abcode:{}}")
    protected Map<Integer, String> lastDigitAbcode;
    @ApolloJsonValue("${alg.recall.special.app&vid:{}}")
    protected Map<String, List<Long>> specialAppVid;
    @Resource
    private BlacklistContainer blacklistContainer;

    /**
     * 在流量池场景下，哪些appType用判断黑名单
     */
    @ApolloJsonValue("${content.security.recommendflowpool.blacklist.apptype.config:[]}")
    private Set<Integer> blacklistAppTypeSet;

    @PostConstruct
    public void init() {
        Map<String, RecallStrategy> type = applicationContext.getBeansOfType(RecallStrategy.class);
        for (Map.Entry<String, RecallStrategy> entry : type.entrySet()) {
            RecallStrategy value = entry.getValue();
            strategyMap.put(value.getClass().getSimpleName(), value);
        }
    }

    public RecallResult recall(RecallParam param) {
        List<RecallStrategy> strategies = getRecallStrategy(param);
        CountDownLatch cdl = new CountDownLatch(strategies.size());
        List<Future<RecallResult.RecallData>> recallResultFutures = new ArrayList<>();
        for (final RecallStrategy strategy : strategies) {
            Future<RecallResult.RecallData> future = pool.submit(() -> {
                List<Video> result = Collections.emptyList();
                try {
                    result = strategy.recall(param);
                } catch (Throwable e) {
                    log.error("recall error {}", strategy.getClass().getSimpleName(), e);
                }
                cdl.countDown();
                return new RecallResult.RecallData(strategy.pushFrom(), result);
            });
            recallResultFutures.add(future);
        }
        try {
            cdl.await(3000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.error("recall error", e);
            return null;
        }

        List<RecallResult.RecallData> results = new ArrayList<>();
        for (Future<RecallResult.RecallData> f : recallResultFutures) {
            try {
                RecallResult.RecallData data = f.get();
                results.add(data);
            } catch (Exception e) {
                log.error("future get error ", e);
            }
        }

        return new RecallResult(results);
    }

    private List<RecallStrategy> getRecallStrategy(RecallParam param) {
        List<RecallStrategy> strategies = new ArrayList<>();
        if (param.isSpecialRecommend()) {
            strategies.add(strategyMap.get(SpecialRecallStrategy.class.getSimpleName()));
            return strategies;
        }

        String matchUserBlacklistTypeEnum = blacklistContainer.matchUserBlacklistTypeEnum(param.getUid(), param.getHotSceneType(), param.getCityCode(),
                param.getClientIp(), param.getMid(), "recommend-flow-pool", param.getAppType());
        boolean hitUserBlacklist = StringUtils.isNotBlank(matchUserBlacklistTypeEnum);
        boolean isInBlacklist = CollectionUtils.isNotEmpty(blacklistAppTypeSet) && blacklistAppTypeSet.contains(param.getAppType());

        String abCode = param.getAbCode();
        //1：通过“产品”控制“召回子策略”. 票圈美好祝福与内部tab只走祝福召回。APP只走固定列表。特殊配置的app只有固定召回列表。
        if (this.matchSpecialApp(param.getAppType())) {
            strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV5Hand.class.getSimpleName()));
            return strategies;
        }
        if (param.getAppType() == AppTypeEnum.PIAO_QUAN_MEIHAO_ZHUFU.getCode() && Objects.equals(param.getCategoryId(), 100)) {
            strategies.add(strategyMap.get(BlessRecallStrategy.class.getSimpleName()));
            return strategies;
        }
        if (param.getAppType() == AppTypeEnum.APP_SPEED.getCode()) {
            strategies.add(strategyMap.get(AppFallbackRecallStrategy.class.getSimpleName()));
            return strategies;
        }
        switch (abCode) {
            case "60122": // 537
            case "60124": // 546
            case "60125": // 547
            case "60126": // 548
            case "60116": // 566
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV2.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV3.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV4.class.getSimpleName()));
                strategies.addAll(getRegionRecallStrategy(param));
                break;
            case "60120": // 576
            case "60123": // 541
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV2.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV3.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV4.class.getSimpleName()));
                break;

            case "60656": // 656
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.addAll(getRegionRecallStrategy(param));
                break;
            case "60118": // 568
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV4.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.addAll(getRegionRecallStrategy(param));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                break;
            case "60107": // 553
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.addAll(getRegionRecallStrategy(param));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV7VovLongTermV1.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV7VovLongTermV2.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV7VovLongTermV3.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV7VovLongTermV4.class.getSimpleName()));
                break;
            case "60121": // 536
            case "60105": // 551
            case "60106": // 552
            case "60112": // 562
            case "60117": // 567
            case "60114": // 564
            case "60115": // 565
            case "60119": // 569
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.addAll(getRegionRecallStrategy(param));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                break;
            case "60113": // 563
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.addAll(getRegionRecallStrategy(param));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(SceneCFRovnRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(SceneCFRosnRecallStrategy.class.getSimpleName()));
                break;
            default:
                strategies.addAll(getRegionRecallStrategy(param));
        }
        // 命中用户黑名单不走流量池
        if (!hitUserBlacklist || !isInBlacklist || !param.isRiskUser()) {
            Set<String> abExpCodes = param.getAbExpCodes();
            if (Objects.nonNull(abExpCodes) && abExpCodes.contains(Constant.SUPPLY_AB_CODE_ID)) {
//            if (Objects.equals(Constant.SUPPLY_AB_CODE, abCode)) {
                // 供给流量池策略 648 实验 random
                strategies.add(strategyMap.get(FlowPoolWithLevelSupplyRecallStrategy.class.getSimpleName()));
                //2:通过“流量池标记”控制“流量池召回子策略” 其中有9组会走EXPERIMENTAL_FLOW_SET_LEVEL 有1组会走EXPERIMENTAL_FLOW_SET_LEVEL_SCORE
            } else if ("60116".equals(abCode)) {
                int lastDigit = param.getLastDigit();
                String lastDigitAB = lastDigitAbcode != null ? lastDigitAbcode.getOrDefault(lastDigit, "default") : "default";
                switch (lastDigitAB) {
                    case "random":
                        strategies.add(strategyMap.get(FlowPoolWithLevelRecallStrategyFilterDigit.class.getSimpleName()));
                        break;
                    case "tomson":
                        strategies.add(strategyMap.get(FlowPoolWithLevelRecallStrategyTomsonFilterDigitV2.class.getSimpleName()));
                        break;
                    case "score":
                        strategies.add(strategyMap.get(FlowPoolWithLevelScoreRecallStrategy.class.getSimpleName()));
                        break;
                    default:
                        strategies.add(strategyMap.get(FlowPoolWithLevelRecallStrategyFilterDigit.class.getSimpleName()));
                        break;
                }
            } else {
                if (param.getFlowPoolAbtestGroup().equals(FlowPoolConstants.EXPERIMENTAL_FLOW_SET_LEVEL)) {
                    strategies.add(strategyMap.get(QuickFlowPoolWithLevelRecallStrategy.class.getSimpleName()));
                    if ("60126".equals(abCode) || "60125".equals(abCode) || "60124".equals(abCode)
                            || "60105".equals(abCode) || "60106".equals(abCode) || "60107".equals(abCode)
                            || "60112".equals(abCode) || "60113".equals(abCode) || "60114".equals(abCode)
                            || "60115".equals(abCode) || "60117".equals(abCode) || "60118".equals(abCode)
                            || "60119".equals(abCode) || "60150".equals(abCode) || "60151".equals(abCode)
                            || "60654".equals(abCode) || "60655".equals(abCode) || "60656".equals(abCode)
                            || "60121".equals(abCode)
                    ) {
                        strategies.add(strategyMap.get(FlowPoolWithLevelRecallStrategyTomson.class.getSimpleName()));
                    } else {
                        strategies.add(strategyMap.get(FlowPoolWithLevelRecallStrategy.class.getSimpleName()));
                    }
                } else if (param.getFlowPoolAbtestGroup().equals(FlowPoolConstants.EXPERIMENTAL_FLOW_SET_LEVEL_SCORE)) {
                    strategies.add(strategyMap.get(QuickFlowPoolWithLevelScoreRecallStrategy.class.getSimpleName()));
                    // 在执行中
                    strategies.add(strategyMap.get(FlowPoolWithLevelScoreRecallStrategy.class.getSimpleName()));
                } else {
                    strategies.add(strategyMap.get(QuickFlowPoolWithScoreRecallStrategy.class.getSimpleName()));
                    strategies.add(strategyMap.get(FlowPoolWithScoreRecallStrategy.class.getSimpleName()));
                }
            }
        }

        //3:通过“abcode”控制“召回子策略”
        switch (abCode) {
            case "60096":
                strategies.add(strategyMap.get(RegionHWithoutDupRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                break;
            case "60111": // 561
            case "60068":
            case "60092":
            case "60094":
            case "60097": // 523
            case "60098": // 533
            case "60101": // 排序str实验
            case "60102": // 通过更改param中的配置实现使用不同数据源 data66 rule68 + 无排序模块
            case "60103": // 增加地域1小时扩量，通过配置实现
            case "60120": // 576
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                break;
            case "60122": // 537
            case "60124": // 546
            case "60125": // 547
            case "60123": // 541
            case "60126": // 548
            case "60116": // 566
            case "60656": // 656
            case "60104": // 去掉sim的对比实验
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                break;
            case "60121": // 536
            case "60105": // 551
            case "60106": // 552
            case "60107": // 553
            case "60112": // 562
            case "60113": // 563
            case "60114": // 564
            case "60115": // 565
            case "60117": // 567
            case "60118": // 568
            case "60119": // 569
                break;
            case "60110": // 新内容的召回（流量池的Top内容）
                strategies.add(strategyMap.get(TopGoodPerformanceVideoRecallStrategy.class.getSimpleName()));
                if (!hitUserBlacklist || !isInBlacklist) {
                    strategies.add(strategyMap.get(FlowPoolLastDayTopRecallStrategy.class.getSimpleName()));
                }
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                break;
            case "60130":
            case "60131":
            case "60132":
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(FestivalRecallStrategyV1.class.getSimpleName()));
                break;
            case "60150": // 645
                strategies.add(strategyMap.get(ShareWidthRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ShareDeepRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(FestivalRecallStrategyV1.class.getSimpleName()));
                break;
            case "60151": // 650
                strategies.add(strategyMap.get(ContentBaseRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(FestivalRecallStrategyV1.class.getSimpleName()));
                break;
            case "60654":
                strategies.add(strategyMap.get(CFRovnRecallStrategyV1.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(FestivalRecallStrategyV1.class.getSimpleName()));
            case "60655":
                strategies.add(strategyMap.get(CFRovnRecallStrategyV1.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV1.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV2_sort.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV3.class.getSimpleName()));
                strategies.add(strategyMap.get(RegionRealtimeRecallStrategyV4.class.getSimpleName()));
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(FestivalRecallStrategyV1.class.getSimpleName()));
            default:
                strategies.add(strategyMap.get(SimHotVideoRecallStrategy.class.getSimpleName()));
                strategies.add(strategyMap.get(ReturnVideoRecallStrategy.class.getSimpleName()));
                break;
        }

        return strategies;
    }

    private List<RecallStrategy> getRegionRecallStrategy(RecallParam param) {

        List<RecallStrategy> strategies = new ArrayList<>();
        if (StringUtils.isNotBlank(param.getRegionCode())) {
            strategies.add(strategyMap.get(RegionHRecallStrategy.class.getSimpleName()));
            strategies.add(strategyMap.get(Region24HRecallStrategy.class.getSimpleName()));
        }
        strategies.add(strategyMap.get(RegionHDupRecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(RegionRelative24HRecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(RegionRelative24HDupRecallStrategy.class.getSimpleName()));

        return strategies;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private boolean matchSpecialApp(int appId) {
        Set<Integer> notSpecialApp = new HashSet<>(Arrays.asList(0, 4, 5));
        if (notSpecialApp.contains(appId)) {
            // vlog 票圈视频 内容精选 不允许走特殊列表，即使配置了也无效。
            return false;
        }
        if (specialAppVid != null && specialAppVid.getOrDefault("app", new ArrayList<>()).contains((long) appId)) {
            log.info("This request hits a special logic in matchSpecialApp with appId={}", appId);
            return true;
        }
        return false;
    }
}
