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

import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.tzld.piaoquan.recommend.server.model.Video;
import com.tzld.piaoquan.recommend.server.service.filter.FilterParam;
import com.tzld.piaoquan.recommend.server.service.filter.FilterResult;
import com.tzld.piaoquan.recommend.server.service.filter.FilterService;
import com.tzld.piaoquan.recommend.server.service.recall.FilterParamFactory;
import com.tzld.piaoquan.recommend.server.service.recall.RecallParam;
import com.tzld.piaoquan.recommend.server.service.recall.RecallStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
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.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 头部视频，类似品类召回
 */
@Slf4j
@Component
public class HeadCate2RovRecallStrategy implements RecallStrategy {

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

    @ApolloJsonValue("${head.cate2.rov.recall.param:{}}")
    private Map<String, String> paramJson;

    public static final String PUSH_FROM = "recall_strategy_head_cate2_rov";

    private static final String SIM_MERGE_CATE2_KEY_FORMAT = "alg_recsys_good_cate_pair_list:%s";
    private static final String MERGE_CATE2_VIDEO_LIST_KEY_FORMAT = "alg_recsys_recall_good_cate_pair_rovn:%s";

    @Override
    public List<Video> recall(RecallParam param) {
        if (Objects.isNull(param.getVideoId())) {
            return Collections.emptyList();
        }

        // 获取头部视频基础信息
        Map<String, String> headVideoInfo = param.getHeadInfo();
        if (MapUtils.isEmpty(headVideoInfo)) {
            return Collections.emptyList();
        }

        // 不存在品类或无效品类
        String mergeCate2 = headVideoInfo.get("merge_second_level_cate");
        if (StringUtils.isBlank(mergeCate2) || "unknown".equals(mergeCate2)) {
            return Collections.emptyList();
        }

        // 获取相似品类
        String simCate2Key = String.format(SIM_MERGE_CATE2_KEY_FORMAT, mergeCate2);
        String simCate2List = redisTemplate.opsForValue().get(simCate2Key);
        if (Objects.isNull(simCate2List)) {
            return Collections.emptyList();
        }

        List<Video> videoResult = new ArrayList<>();

        int simCateLength = Integer.parseInt(paramJson.getOrDefault("sim_cate_length", "10000"));
        Map<String, Double> mergeCate2Pair = this.parsePair(simCate2List, simCateLength);

        Map<String, Map<String, Double>> recallVideoMap = this.cate2Recall(new ArrayList<>(mergeCate2Pair.keySet()));

        // 过滤
        List<Long> allVid = recallVideoMap.values().stream()
                .map(Map::keySet)
                .flatMap(Collection::stream)
                .map(Long::parseLong)
                .collect(Collectors.toList());

        FilterParam filterParam = FilterParamFactory.create(param, allVid);
        FilterResult filterResult = filterService.filter(filterParam);
        Set<Long> filterVids = new HashSet<>(filterResult.getVideoIds());
        filterVids.remove(param.getVideoId());

        Set<Long> hit = new HashSet<>();
        for (Map.Entry<String, Double> entry : mergeCate2Pair.entrySet()) {
            String cate = entry.getKey();
            Double cateScore = entry.getValue();

            Map<String, Double> videoMap = recallVideoMap.getOrDefault(cate, new HashMap<>());
            for (Map.Entry<String, Double> videoEntry : videoMap.entrySet()) {
                long vid = Long.parseLong(videoEntry.getKey());

                // 过滤之后不存在的视频，过滤掉
                if (!filterVids.contains(vid)) {
                    continue;
                }
                // 去重
                if (hit.contains(vid)) {
                    continue;
                }
                hit.add(vid);

                Double videoScore = videoEntry.getValue();

                Video video = new Video();
                video.setVideoId(vid);
                video.setRovScore(cateScore * videoScore);
                video.setPushFrom(PUSH_FROM);
                videoResult.add(video);
            }

        }

        videoResult.sort(Comparator.comparingDouble(o -> -o.getRovScore()));

        return videoResult;
    }

    private Map<String, Double> parsePair(String value, int length) {
        if (StringUtils.isBlank(value)) {
            return new HashMap<>();
        }

        String[] split = value.split("\t");
        if (split.length != 2) {
            return new HashMap<>();
        }

        String[] valueList = split[0].trim().split(",");
        String[] scoreList = split[1].trim().split(",");
        if (valueList.length != scoreList.length) {
            return new HashMap<>();
        }

        int minLength = Math.min(length, valueList.length);
        Map<String, Double> resultMap = new HashMap<>();
        for (int i = 0; i < minLength; i++) {
            resultMap.put(valueList[i].trim(), Double.parseDouble(scoreList[i].trim()));
        }

        return resultMap;
    }

    private Map<String, Map<String, Double>> cate2Recall(List<String> mergeCate2List) {


        List<String> redisKeys = mergeCate2List.stream().map(i -> String.format(MERGE_CATE2_VIDEO_LIST_KEY_FORMAT, i)).collect(Collectors.toList());
        List<String> values = redisTemplate.opsForValue().multiGet(redisKeys);
        if (CollectionUtils.isEmpty(values)) {
            return new HashMap<>();
        }

        Map<String, Map<String, Double>> resultMap = new HashMap<>();

        int recallVidLength = Integer.parseInt(paramJson.getOrDefault("recall_vid_length", "10000"));
        for (int i = 0; i < mergeCate2List.size(); i++) {
            String mergeCate2 = mergeCate2List.get(i);

            String value = values.get(i);
            if (StringUtils.isBlank(value)) {
                continue;
            }

            Map<String, Double> recallVideoMap = this.parsePair(value, recallVidLength);
            if (MapUtils.isEmpty(recallVideoMap)) {
                continue;
            }

            resultMap.put(mergeCate2, recallVideoMap);
        }

        return resultMap;
    }

    @Override
    public String pushFrom() {
        return PUSH_FROM;
    }
}
