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

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.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;

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

@Slf4j
public abstract class SocialI2IBasicRecallStrategy implements RecallStrategy {

    private final String CLASS_NAME = this.getClass().getSimpleName();

//    private static final String REDIS_KEY_FORMAT = "user_social_i2i_recall:";
//    private static final String REDIS_KEY_FORMAT = "user_shares_i2i_recall:";
//    private static final String DEFAULT_REDIS_KEY_FORMAT = "user_social_i2i_recall:";
    private static final String DEFAULT_REDIS_KEY_FORMAT = "clicks_user_shares_i2i_recall:";

    @Autowired
    private FilterService filterService;

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

    protected abstract List<Long> genLeftItemList(RecallParam param);

    protected String getRedisKeyFormat() {
        return DEFAULT_REDIS_KEY_FORMAT;
    }

    @Override
    public List<Video> recall(RecallParam param) {
        List<Video> videos = new ArrayList<>();

        try {

            List<Long> leftItems = this.genLeftItemList(param);
            if (CollectionUtils.isEmpty(leftItems)) {
                return videos;
            }

//            Map<Long, List<Pair<Long, Double>>> recallResult = this.multiGetRecallResult(REDIS_KEY_FORMAT, leftItems);
            Map<Long, List<Pair<Long, Double>>> recallResult = this.multiGetRecallResult(this.getRedisKeyFormat(), leftItems);

            // 打平多个视频的召回，并按照分数从大到小排序
            List<Pair<Long, Double>> recallPair = this.flatAndSortRecallResult(recallResult);
            recallPair.sort(Comparator.comparingDouble(p -> -p.getValue()));

            List<Long> videoIds = recallPair.stream().map(Pair::getKey).collect(Collectors.toList());


            // 视频过滤
            FilterParam filterParam = FilterParamFactory.create(param, videoIds);
            FilterResult filterResult = filterService.filter(filterParam);
            if (Objects.isNull(filterResult) || CollectionUtils.isEmpty(filterResult.getVideoIds())) {
                return videos;
            }

            // 返回结果
            Map<Long, Double> videoScoreMap = recallPair.stream()
                    .collect(Collectors.toMap(Pair::getKey, Pair::getValue, (o1, o2) -> o1));
            for (Long videoId : filterResult.getVideoIds()) {
                Video video = new Video();
                video.setVideoId(videoId);
                video.setRovScore(videoScoreMap.getOrDefault(videoId, 0D));
                video.setPushFrom(pushFrom());
                videos.add(video);
            }
        } catch (Exception e) {
            log.error("recall error strategy is {}, pushFrom is {} \n", CLASS_NAME, pushFrom(), e);
        }
        return videos;

    }

    /**
     * 获取多个视频的I2I召回结果
     */
    private Map<Long, List<Pair<Long, Double>>> multiGetRecallResult(String keyPrefix, List<Long> keySuffix) {
        if (StringUtils.isBlank(keyPrefix) || CollectionUtils.isEmpty(keySuffix)) {
            return new HashMap<>();
        }
        List<String> keys = keySuffix.stream()
                .map(i -> keyPrefix + i)
                .collect(Collectors.toList());

        List<String> values = redisTemplate.opsForValue().multiGet(keys);
        if (CollectionUtils.isEmpty(values)) {
            return new HashMap<>();
        }

        Map<Long, List<Pair<Long, Double>>> resultMap = new HashMap<>();
        for (int i = 0; i < keySuffix.size(); i++) {
            String value = values.get(i);
            List<Pair<Long, Double>> recallVideos = this.parseRedisValue(value);
            if (CollectionUtils.isEmpty(recallVideos)) {
                continue;
            }
            resultMap.put(keySuffix.get(i), recallVideos);
        }
        return resultMap;
    }


    private List<Pair<Long, Double>> parseRedisValue(String value) {
        if (StringUtils.isBlank(value)) {
            return new ArrayList<>();
        }
        String[] split = value.split("\t");
        if (split.length != 2) {
            return new ArrayList<>();
        }

        List<Long> videoIds = Arrays.stream(split[0].split(","))
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .map(Long::parseLong)
                .collect(Collectors.toList());

        List<Double> scores = Arrays.stream(split[1].split(","))
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .map(Double::parseDouble)
                .collect(Collectors.toList());

        if (videoIds.size() != scores.size()) {
            return new ArrayList<>();
        }

        List<Pair<Long, Double>> recallVideos = new ArrayList<>();
        for (int i = 0; i < videoIds.size(); i++) {
            recallVideos.add(Pair.of(videoIds.get(i), scores.get(i)));
        }
        return recallVideos;
    }

    /**
     * 打平多个视频的召回结果，同一个被召回的视频对其分数求和
     */
    private List<Pair<Long, Double>> flatAndSortRecallResult(Map<Long, List<Pair<Long, Double>>> resultMap) {
        return resultMap.values().stream()
                .flatMap(Collection::stream)
                .collect(Collectors.groupingBy(Pair::getKey, Collectors.summingDouble(Pair::getValue)))
                .entrySet()
                .stream()
                .map(i -> Pair.of(i.getKey(), i.getValue()))
                .collect(Collectors.toList());
    }
}
