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

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.tzld.piaoquan.recommend.server.common.ThreadPoolFactory;
import com.tzld.piaoquan.recommend.server.framework.common.User;
import com.tzld.piaoquan.recommend.server.gen.common.Result;
import com.tzld.piaoquan.recommend.server.gen.recommend.PushFromIndex;
import com.tzld.piaoquan.recommend.server.gen.recommend.RecommendRequest;
import com.tzld.piaoquan.recommend.server.gen.recommend.RecommendResponse;
import com.tzld.piaoquan.recommend.server.gen.recommend.VideoProto;
import com.tzld.piaoquan.recommend.server.implement.FlowPoolRecommendPipeline;
import com.tzld.piaoquan.recommend.server.implement.TopRecommendPipeline;
import com.tzld.piaoquan.recommend.server.model.RecommendParam;
import com.tzld.piaoquan.recommend.server.model.Video;
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.strategy.RankStrategy4RegionMergeModelV547;
import com.tzld.piaoquan.recommend.server.util.CommonCollectionUtils;
import com.tzld.piaoquan.recommend.server.util.JSONUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
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.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author sunxy
 */
@Service
@Slf4j
public class VideoRecommendService {

    @Value("${recommend.new.log.print:true}")
    private Boolean newLogPrint;
    @Resource
    private RecommendService recommendService;
    @Resource
    private TopRecommendPipeline topRecommendPipeline;
    @Resource
    private FlowPoolRecommendPipeline flowPoolRecommendPipeline;
    @Resource
    private RankStrategy4RegionMergeModelV547 rankStrategy4RegionMergeModelV547;
    @Resource
    private TimerLogService timerLogService;
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    @Value("${recall.framework.exp:code:610}")
    private String recallFrameworkExpCode;


    public RecommendResponse homepageRecommend(RecommendRequest request) {
        boolean hit = CommonCollectionUtils.contains(request.getAbExpCodeList(), recallFrameworkExpCode);
        if (hit) {
            RecommendParam recommendParam = recommendService.genRecommendParam(request, 0);
            return recommendNew(request, recommendParam, 0);
        }
        return recommend(request, 0);
    }


    public RecommendResponse relevantRecommend(RecommendRequest request) {
        boolean hit = CommonCollectionUtils.contains(request.getAbExpCodeList(), recallFrameworkExpCode);
        if (hit) {
            RecommendParam recommendParam = recommendService.genRecommendParam(request, 1);
            return recommendNew(request, recommendParam, 1);
        }
        return recommend(request, 1);
    }

    private RecommendResponse recommendNew(RecommendRequest request, RecommendParam param, int recommendType) {
        if (request == null) {
            return RecommendResponse.newBuilder()
                    .setResult(Result.newBuilder().setCode(1).setMessage("success"))
                    .build();
        }
        if (request.getVersionAuditStatus() == 1) {
            return recommendService.specialMidRecommend(request);
        }
        Stopwatch stopwatch = Stopwatch.createStarted();
        if (StringUtils.isNotBlank(request.getMid())
                && redisTemplate.opsForSet().isMember("special:mid", request.getMid())) {
            return recommendService.specialMidRecommend(request);
        }
        stopwatch.reset().start();

        log.info("genRecommendParam={},genRecommendParam cost={}", JSONUtils.toJson(param),
                stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
        stopwatch.reset().start();
        List<Video> videos = videoRecommend(request, param, recommendType);
        recommendService.updateCache(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();
    }

    private List<Video> videoRecommend(RecommendRequest request, RecommendParam param, int recommendType) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        User user = getUser(request);
        List<Video> topRecommendVideoList = new ArrayList<>();
        List<Video> flowPoolRecommendVideoList = new ArrayList<>();
        Map<String, String> timeLogMap = new HashMap<>(32);
        try {
            topRecommendVideoList = topRecommendPipeline.feeds(request, 0, user, newLogPrint, timeLogMap);
            if (newLogPrint) {
                log.info("traceId = {}, cost = {}, topRecommendVideoList [{}]", request.getRequestId(),
                        stopwatch.elapsed().toMillis(), JSONObject.toJSONString(topRecommendVideoList));
            }

        } catch (Exception e) {
            log.error("traceId = {}, topRecommendVideoList error", request.getRequestId(), e);
        } finally {
        }
        try {
            stopwatch.reset().start();
            flowPoolRecommendVideoList = flowPoolRecommendPipeline.feedByRec(request, recommendType, newLogPrint);
            if (newLogPrint) {
                log.info("traceId = {}, cost = {}, flowPoolRecommendVideoList [{}]", request.getRequestId(),
                        stopwatch.elapsed().toMillis(), JSONObject.toJSONString(flowPoolRecommendVideoList));
            }
        } catch (Exception e) {
            log.error("traceId = {}, flowPoolRecommendVideoList error", request.getRequestId(), e);
        }

        stopwatch.reset().start();
        RankParam rankParam = recommendService.convertToRankParam(param, null);
        RankResult rankResult = rankStrategy4RegionMergeModelV547.mergeAndSort(rankParam, topRecommendVideoList,
                flowPoolRecommendVideoList);
        if (newLogPrint) {
            log.info("traceId = {}, cost = {}, final rankResult [{}]", request.getRequestId(),
                    stopwatch.elapsed().toMillis(), JSONObject.toJSONString(rankResult));
        }

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

        // 更新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 videos;
    }


    private User getUser(RecommendRequest request) {
        User user = new User();
        user.setMid(request.getMid());
        user.setUid(request.getUid());
        user.setRegion(request.getProvince());
        return user;
    }

    private RecommendResponse recommend(RecommendRequest request, int recommendType) {
        return recommendService.recommend(request, recommendType);
    }

}
