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

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.tzld.piaoquan.recommend.server.common.enums.AppTypeEnum;
import com.tzld.piaoquan.recommend.server.service.filter.FilterParam;
import com.tzld.piaoquan.recommend.server.service.filter.FilterStrategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
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 javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;

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

    private LoadingCache<Integer, Set<String>> allowVideoCache = CacheBuilder.newBuilder()
            .maximumSize(1000000) // TODO 评估容量
            .refreshAfterWrite(600, TimeUnit.SECONDS)
            .expireAfterWrite(600, TimeUnit.SECONDS)
            .expireAfterAccess(600, TimeUnit.SECONDS)
            .build(new CacheLoader<Integer, Set<String>>() {
                @Override
                public Set<String> load(Integer idx) {
                    String key = VIDEO_ALLOW_LIST_BITMAP_KEY_SET_PREFIX + idx;
                    Set<String> result = redisTemplate.opsForSet().members(key);
                    if (CollectionUtils.isEmpty(result)) {
                        return Collections.emptySet();
                    }
                    return result;
                }
            });

    private static final String VIDEO_ALLOW_LIST_BITMAP_KEY_SET_PREFIX = "movie:videoid:allowSet:";
    private static final String RELIGION_VIDEO_ALLOW_LIST_BITMAP_KEY = "mp:religion:allowlist:videoid:bitmap";


    @PostConstruct
    public void init() {
        // 预加载本地缓存
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            allowVideoCache.getUnchecked(finalI);
        }
    }

    @Override
    public List<Long> filter(FilterParam param) {
        if (param == null
                || CollectionUtils.isEmpty(param.getVideoIds())) {
            return Collections.emptyList();
        }
        //不是新小程序 在白名单则不显示
        Set<Long> retainVideoIds = new LinkedHashSet<>();
        for (Long videoId : param.getVideoIds()) {
            //如果不在新小程序白名单 则保留
            if (!isMemberOfVideoAllowList(videoId)) {
                retainVideoIds.add(videoId);
            }
        }
        if (CollectionUtils.isEmpty(retainVideoIds)) {
            return Collections.emptyList();
        }

        return Lists.newArrayList(retainVideoIds);
    }

    private boolean isMemberOfVideoAllowList(Long videoId) {
        // TODO 优化
        // 1. 每个idx 2000条数据 总共20w 约1.6M 可放入本地内存
        // 2. 部分视频已下推荐，可清理
        if (Objects.isNull(videoId)) {
            return false;
        }
        try {
            int idx = Math.abs(Hashing.murmur3_32().hashLong(videoId).asInt()) % 100;
            Set<String> allowList = allowVideoCache.get(idx);
            if (CollectionUtils.isEmpty(allowList)) {
                return false;
            }
            return allowList.contains(String.valueOf(videoId));
        } catch (Exception e) {
            log.error("isMemberOfVideoAllowList error {}", videoId, e);
        }
        return false;
    }

    public boolean isMemberOfReligionVideoAllowList(Long videoId) {
        if (Objects.isNull(videoId)) {
            return false;
        }
        try {
            return redisTemplate.opsForValue().getBit(RELIGION_VIDEO_ALLOW_LIST_BITMAP_KEY, videoId);
        } catch (Exception e) {
            log.error("isMemberOfReligionVideoAllowList error {}", e, videoId);
        }
        return false;
    }
}
