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 com.tzld.piaoquan.recommend.server.util.FeatureUtils;
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.apache.commons.lang3.math.NumberUtils;
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 org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Component
public class YearShareCate2RecallStrategy implements RecallStrategy {

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

    @Autowired
    private FilterService filterService;

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

    public static final String PUSH_FROM = "recall_user_year_share_cate2";
    public static final String redisKeyPrefix = "merge_cate_recall:cate2";

    @Override
    public List<Video> recall(RecallParam param) {

        List<Video> videosResult = new ArrayList<>();
        try {

            if (MapUtils.isEmpty(param.getUserNetworkSeqVideoInfoMap())) {
                return videosResult;
            }

            List<Pair<Long, String>> userNetworkVideoCate2 = this.parseUserActionVideoAndCate2(param.getUserNetworkSeqFeature(), param.getUserNetworkSeqVideoInfoMap());
            if (CollectionUtils.isEmpty(userNetworkVideoCate2)) {
                return videosResult;
            }
            int limit = Math.min(userNetworkVideoCate2.size(), 3);
            List<String> lastTopNCate = userNetworkVideoCate2.subList(0, limit).stream()
                    .map(Pair::getValue)
                    .distinct()
                    .collect(Collectors.toList());

            List<String> freqTopNCate = userNetworkVideoCate2.stream()
                    .map(Pair::getValue)
                    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).entrySet()
                    .stream()
                    .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                    .limit(limit)
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toList());


            List<String> allCate2 = Stream.of(lastTopNCate, freqTopNCate)
                    .flatMap(Collection::stream)
                    .distinct()
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());

            List<String> keys = this.getRedisKey(allCate2);
            List<String> values = redisTemplate.opsForValue().multiGet(keys);
            List<Long> ids = recall(param.getVideoId(), values);

            Map<Long, Double> scoresMap = FilterParamFactory.positionScores(ids);
            FilterParam filterParam = FilterParamFactory.create(param, ids, pushFrom(), scoresMap);
            FilterResult filterResult = filterService.filter(filterParam);
            if (filterResult != null && CollectionUtils.isNotEmpty(filterResult.getVideoIds())) {
                for (Long vid : filterResult.getVideoIds()) {
                    Video video = new Video();
                    video.setVideoId(vid);
                    video.setRovScore(scoresMap.getOrDefault(vid, 0.0));
                    video.setPushFrom(pushFrom());
                    videosResult.add(video);
                }
            }
        } catch (Exception e) {
            log.error("recall is wrong in {}, error={}", CLASS_NAME, e);
        }

        return videosResult;
    }

    private List<Pair<Long, String>> parseUserActionVideoAndCate2(Map<String, String> userNetworkSeqFeature, Map<Long, Map<String, String>> userNetworkSeqVideoInfoMap) {
        List<Pair<Long, String>> result = new ArrayList<>();
        List<String> actVidSeq = FeatureUtils.extractVidsFromUserNetworkSeqFeature(userNetworkSeqFeature, "a_v_s");
        List<String> actTypeSeq = FeatureUtils.extractVidsFromUserNetworkSeqFeature(userNetworkSeqFeature, "a_t_s");
        if (actVidSeq.size() != actTypeSeq.size()) {
            return new ArrayList<>();
        }

        for (int i = 0; i < actVidSeq.size(); i++) {
            long videoIdL = NumberUtils.toLong(actVidSeq.get(i), -1);
            if (videoIdL <= 0){
                continue;
            }
            String type = actTypeSeq.get(i);
            if (!"share".equals(type)) {
                continue;
            }

            Map<String, String> videoBaseInfo = userNetworkSeqVideoInfoMap.getOrDefault(videoIdL, new HashMap<>());
            String mergeCate2 = videoBaseInfo.get("merge_second_level_cate");
            if (StringUtils.isBlank(mergeCate2)) {
                continue;
            }
            result.add(Pair.of(videoIdL, mergeCate2));
        }
        return result;
    }

    private List<String> getRedisKey(List<String> cate2List) {
        List<String> keys = new ArrayList<>();
        for (String cate2 : cate2List) {
            keys.add(String.format("%s:%s", redisKeyPrefix, cate2));
        }
        return keys;
    }

    private List<Long> recall(Long headVid, List<String> values) {
        List<Long> vidList = new ArrayList<>();
        if (null != values && !values.isEmpty()) {
            Set<Long> hits = new HashSet<>();
            hits.add(headVid);
            List<org.apache.commons.math3.util.Pair<Long, Double>> list = new ArrayList<>();
            for (String value : values) {
                if (null != value && !value.isEmpty()) {
                    String[] cells = value.split("\t");
                    if (2 == cells.length) {
                        List<Long> ids = Arrays.stream(cells[0].split(",")).map(Long::valueOf).collect(Collectors.toList());
                        List<Double> scores = Arrays.stream(cells[1].split(",")).map(Double::valueOf).collect(Collectors.toList());
                        if (!ids.isEmpty() && ids.size() == scores.size()) {
                            for (int i = 0; i < ids.size(); ++i) {
                                long id = ids.get(i);
                                double score = scores.get(i);
                                if (hits.contains(id)) {
                                    continue;
                                }
                                hits.add(id);
                                list.add(org.apache.commons.math3.util.Pair.create(id, score));
                            }
                        }
                    }
                }
            }
            if (!list.isEmpty()) {
                list.sort(Comparator.comparingDouble(o -> -o.getSecond()));
                for (org.apache.commons.math3.util.Pair<Long, Double> pair : list) {
                    vidList.add(pair.getFirst());
                }
            }
        }
        return vidList;
    }

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