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

import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.tzld.piaoquan.recommend.server.common.ThreadPoolFactory;
import com.tzld.piaoquan.recommend.server.repository.WxVideoStatus;
import com.tzld.piaoquan.recommend.server.repository.WxVideoStatusRepository;
import com.tzld.piaoquan.recommend.server.repository.WxVideoTagRel;
import com.tzld.piaoquan.recommend.server.repository.WxVideoTagRelRepository;
import com.tzld.piaoquan.recommend.server.service.filter.FilterParam;
import com.tzld.piaoquan.recommend.server.service.filter.FilterStrategy;
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.CollectionUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author dyp
 */
@Component
@Slf4j
public class VideoSourceTypeStrategy implements FilterStrategy {
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate<String, String> redisTemplate;


    @Autowired
    private WxVideoStatusRepository wxVideoStatusRepository;


    private final String keyFormat = "video:uid:%s";

    public static final String NOT_USER_UPLOAD_USER_KEY = "not:user:upload:user";
    private LoadingCache<String, Set<String>> notUserUploadUserCache = CacheBuilder.newBuilder()
            .maximumSize(10000)
            .refreshAfterWrite(600, TimeUnit.SECONDS)
            .expireAfterWrite(600, TimeUnit.SECONDS)
            .expireAfterAccess(600, TimeUnit.SECONDS)
            .build(new CacheLoader<String, Set<String>>() {
                @Override
                public Set<String> load(String key) {
                    Set<String> result = redisTemplate.opsForSet().members(key);
                    if (CollectionUtils.isEmpty(result)) {
                        return Collections.emptySet();
                    }
                    return result;
                }
            });

    @Autowired
    private WxVideoTagRelRepository repository;

    @ApolloJsonValue("${user.upload.exclude.tagids:[]}")
    private List<Long> userUploadExcludeTagIds;

    private String userUploadExcludeVideoCacheKey = "user.upload.exclude.video";
    // 内存持久保存不淘汰
    private LoadingCache<String, Set<Long>> userUploadExcludeVideoCache = CacheBuilder.newBuilder()
            .maximumSize(10)
            .refreshAfterWrite(60, TimeUnit.SECONDS)
            .expireAfterWrite(60, TimeUnit.SECONDS)
            .expireAfterAccess(60, TimeUnit.SECONDS)
            .build(new CacheLoader<String, Set<Long>>() {
                @Override
                public Set<Long> load(String key) {
                    List<WxVideoTagRel> rels = repository.findAllByTagIdIn(userUploadExcludeTagIds);
                    return CommonCollectionUtils.toSet(rels, WxVideoTagRel::getVideoId);
                }
            });

    @PostConstruct
    public void init() {
        Set<Long> data = userUploadExcludeVideoCache.getUnchecked(userUploadExcludeVideoCacheKey);
        log.info("userUploadExcludeVideoCache size {}", data.size());
    }

    @Override
    public List<Long> filter(FilterParam param) {
        if (param == null) {
            return Collections.emptyList();
        }
        if (StringUtils.isBlank(param.getMid())
                || CollectionUtils.isEmpty(param.getVideoIds())) {
            return param.getVideoIds();
        }

        // vid -> uid
        List<String> keys = param.getVideoIds().stream()
                .map(id -> String.format(keyFormat, id))
                .collect(Collectors.toList());

        List<String> uids = redisTemplate.opsForValue().multiGet(keys);
        List<Long> cacheMissVideoIds = new ArrayList<>();
        Map<Long, String> vid2UidMap = new HashMap<>();
        for (int i = 0; i < param.getVideoIds().size(); i++) {
            String value = uids.get(i);
            if (StringUtils.isBlank(value)) {
                cacheMissVideoIds.add(param.getVideoIds().get(i));
            } else {
                vid2UidMap.put(param.getVideoIds().get(i), value);
            }
        }

        if (CollectionUtils.isNotEmpty(cacheMissVideoIds)) {
            List<WxVideoStatus> status = wxVideoStatusRepository.findAllByVideoIdIn(cacheMissVideoIds);
            if (CollectionUtils.isNotEmpty(status)) {
                for (WxVideoStatus v : status) {
                    vid2UidMap.put(v.getVideoId(), String.valueOf(v.getVideoUid()));

                    // TODO 异步更新缓存
                    ThreadPoolFactory.defaultPool().execute(() -> {
                        redisTemplate.executePipelined(new SessionCallback<String>() {
                            @Override
                            public <A, B> String execute(RedisOperations<A, B> redisOperations) throws DataAccessException {
                                ValueOperations<String, String> operations =
                                        (ValueOperations<String, String>) redisOperations.opsForValue();
                                status.forEach(v -> {
                                    operations.set(String.format(keyFormat, v.getVideoId()),
                                            String.valueOf(v.getVideoUid()), RandomUtils.nextInt(30, 60),
                                            TimeUnit.SECONDS);
                                });

                                return null;
                            }
                        });
                    });
                }
            }
        }

        Set<String> notUserUploadUserIds = notUserUploadUserCache.getUnchecked(NOT_USER_UPLOAD_USER_KEY);
        Set<Long> data = userUploadExcludeVideoCache.getUnchecked(userUploadExcludeVideoCacheKey);

        List<Long> videoIds = param.getVideoIds().stream()
                .filter(l -> data.contains(l)
                        || (vid2UidMap.containsKey(l) && notUserUploadUserIds.contains(vid2UidMap.get(l))))
                .collect(Collectors.toList());

//        log.info("VideoSourceTypeStrategy \t param={} \t before={} \t " +
//                        "after={}",
//                JSONUtils.toJson(param),
//                JSONUtils.toJson(param.getVideoIds()),
//                JSONUtils.toJson(videoIds));

        return videoIds;
    }
}
