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.enums.AppTypeEnum;
import com.tzld.piaoquan.recommend.server.model.Video;
import com.tzld.piaoquan.recommend.server.service.ExperimentService;
import com.tzld.piaoquan.recommend.server.service.recall.strategy.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.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 {

    @Resource
    private ExperimentService experimentService;

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

    @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);
//        log.info("recall strategies {}", JSONUtils.toJson(CommonCollectionUtils.toList(strategies,
//                o -> o.getClass().getSimpleName())));
        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);
                    //log.info("recall result {} {}", strategy.getClass().getSimpleName(), JSONUtils.toJson(result));
                } 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 (2 == param.getRecommendType()) {
            strategies.add(strategyMap.get(HotReturnUvRecallStrategy.class.getSimpleName()));
            return strategies;
        }
        if (param.isSpecialRecommend()) {
            strategies.add(strategyMap.get(SpecialRecallStrategy.class.getSimpleName()));
            return strategies;
        }

        //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;
        }

        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()));
        strategies.add(strategyMap.get(UserCate1RecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(UserCate2RecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(HeadProvinceCate1RecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(HeadProvinceCate2RecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(HeadCate2RovRecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(CityRovnRecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(PrioriProvinceRovnRecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(PrioriProvinceStrRecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(PrioriProvinceRosRecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(Return1Cate2RosRecallStrategy.class.getSimpleName()));
        strategies.add(strategyMap.get(Return1Cate2StrRecallStrategy.class.getSimpleName()));
        Set<String> abExpCodes = param.getAbExpCodes();
        if (CollectionUtils.isNotEmpty(abExpCodes)) {
            // if (abExpCodes.contains("568")) {
            //     strategies.add(strategyMap.get(PrioriPremiumRovnRecallStrategy.class.getSimpleName()));
            // }
            // if (abExpCodes.contains("566")){
            //     strategies.add(strategyMap.get(SocialI2IDirectRecallStrategy.class.getSimpleName()));
            //     strategies.add(strategyMap.get(SocialI2IHistoryShareRecallStrategy.class.getSimpleName()));
            //     strategies.add(strategyMap.get(SocialI2IHistoryClickRecallStrategy.class.getSimpleName()));
            // }
        }

        boolean isHit842Exp = experimentService.judgeHitExp(param.getAppType(), param.getRootSessionId(), abExpCodes, "842");
        if (isHit842Exp) {
            strategies.add(strategyMap.get(UserDeconstructionKeywordsRecallStrategy.class.getSimpleName()));
        }

        // 命中用户黑名单不走流量池
        // 命中安全测试风险地域不走流量池
        if (!param.isRiskUser() && !param.isTestingRiskRegion()) {
            strategies.add(strategyMap.get(QuickFlowPoolWithLevelRecallStrategy.class.getSimpleName()));
            strategies.add(strategyMap.get(FlowPoolWithLevelRecallStrategyTomson.class.getSimpleName()));
            strategies.add(strategyMap.get(DouHotFlowPoolRecallStrategy.class.getSimpleName()));
        }
        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;
    }
}
