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.rank.bo.UserShareReturnProfile;
import com.tzld.piaoquan.recommend.server.service.rank.bo.VideoAttrSRBO;
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.math3.util.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.stream.Collectors;

@Slf4j
@Component
public class UserDeconstructionKeywordsRecallStrategy implements RecallStrategy {
    private final String CLASS_NAME = this.getClass().getSimpleName();

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

    public static final int topN = 5;
    public static final String PUSH_FORM = "recall_strategy_deconstruction_keywords";
    public static final String redisKeyPrefix = "deconstruction_keywords_recall";

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

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

        List<Video> videos = new ArrayList<>();
        try {
            UserShareReturnProfile userProfile = param.getUserProfile();
            if (Objects.isNull(userProfile) || MapUtils.isEmpty(userProfile.getD_k_s())) {
                return videos;
            }
            List<String> keys = this.getRedisKey(userProfile.getD_k_s());
            if (CollectionUtils.isEmpty(keys)) {
                return videos;
            }
            List<String> values = redisTemplate.opsForValue().multiGet(keys);
            List<Long> vids = this.recall(param.getVideoId(), values);
            FilterParam filterParam = FilterParamFactory.create(param, vids);

            FilterResult filterResult = filterService.filter(filterParam);
            if (Objects.isNull(filterResult) || CollectionUtils.isEmpty(filterResult.getVideoIds())) {
                return videos;
            }
            List<Long> filterIds = filterResult.getVideoIds();

            int n = filterIds.size();
            for (int i = 0; i < n; i++) {
                Video video = new Video();
                video.setVideoId(filterIds.get(i));
                video.setRovScore(n - i);
                video.setPushFrom(pushFrom());
                videos.add(video);
            }
        } catch (Exception e) {
            log.error("recall is wrong in {}, error={}", CLASS_NAME, e);
        }
        return videos;
    }

    private List<String> getRedisKey(Map<String, VideoAttrSRBO> map) {
        List<String> keys = new ArrayList<>();
        if (MapUtils.isEmpty(map)) {
            return keys;
        }

        List<Pair<String, Double>> keywordsList = getOrderedList(map);
        for (Pair<String, Double> pair : keywordsList) {
            keys.add(String.format("%s:%s", redisKeyPrefix, pair.getFirst()));
            if (keys.size() >= topN) {
                break;
            }
        }
        return keys;
    }

    private List<Pair<String, Double>> getOrderedList(Map<String, VideoAttrSRBO> map) {
        List<Pair<String, Double>> pairList = new ArrayList<>();
        if (MapUtils.isEmpty(map)) {
            return pairList;
        }

        for (Map.Entry<String, VideoAttrSRBO> entry : map.entrySet()) {
            String name = entry.getKey();
            VideoAttrSRBO videoAttrSRBO = entry.getValue();
            if (Objects.nonNull(videoAttrSRBO) && videoAttrSRBO.getRu() > 0) {
                long sharePv = videoAttrSRBO.getSp();
                long returnUv = videoAttrSRBO.getRu();
                double score = FeatureUtils.plusSmooth(returnUv, sharePv, 5);
                Pair<String, Double> pair = Pair.create(name, score);
                pairList.add(pair);
            }
        }
        if (pairList.size() > 1) {
            pairList.sort(Comparator.comparingDouble(o -> -o.getSecond()));
        }
        return pairList;
    }

    private List<Long> recall(Long headVid, List<String> values) {
        List<Long> vids = new ArrayList<>();
        if (CollectionUtils.isEmpty(values)) {
            return vids;
        }

        Set<Long> hits = new HashSet<>();
        hits.add(headVid);
        List<Pair<Long, Double>> list = new ArrayList<>();
        for (String value : values) {
            if (StringUtils.isBlank(value)) {
                continue;
            }
            String[] cells = value.split("\t");
            if (cells.length != 2) {
                continue;
            }
            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 (CollectionUtils.isEmpty(ids) || CollectionUtils.isEmpty(scores)) {
                continue;
            }
            if (ids.size() != scores.size()) {
                continue;
            }
            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(Pair.create(id, score));
            }
        }
        if (CollectionUtils.isEmpty(list)) {
            return vids;
        }
        list.sort(Comparator.comparingDouble(o -> -o.getSecond()));
        for (Pair<Long, Double> pair : list) {
            vids.add(pair.getFirst());
        }
        return vids;
    }
}
