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

import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.tzld.piaoquan.recommend.server.model.Video;
import com.tzld.piaoquan.recommend.server.util.CommonCollectionUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author dyp
 */
@Service
@Slf4j
public class PreViewedService {
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    @ApolloJsonValue("${preview.exp.config:{}}")
    private Map<String, Integer> preViewExpConfig;

    public static final String KEY_PRE_VIEWED_FORMAT = "previewed:videos:%s:%s";

//    private LoadingCache<String, Set<Long>> preViewedCache = CacheBuilder.newBuilder()
//            .maximumSize(100000) // TODO 评估容量
//            .refreshAfterWrite(10, TimeUnit.SECONDS)
//            .expireAfterWrite(10, TimeUnit.SECONDS)
//            .expireAfterAccess(10, TimeUnit.SECONDS)
//            .build(new CacheLoader<String, Set<Long>>() {
//                @Override
//                public Set<Long> load(String key) {
//                    return getVideoIdsFromCache(key);
//                }
//            });

    public void updateCache(int appType, String mid, List<Video> videos, Set<String> abExpCodes) {
        if (StringUtils.isBlank(mid)
                || CollectionUtils.isEmpty(videos)) {
            return;
        }
        // 1 更新预曝光数据 过期时间 30min
        String[] videoIds = new String[videos.size()];
        for (int i = 0; i < videoIds.length; i++) {
            videoIds[i] = String.valueOf(videos.get(i).getVideoId());
        }
        String key = String.format(KEY_PRE_VIEWED_FORMAT, appType, mid);

        int expire = 30;
        if (MapUtils.isNotEmpty(preViewExpConfig)) {
            for (Map.Entry<String, Integer> entry : preViewExpConfig.entrySet()) {
                if (CommonCollectionUtils.contains(abExpCodes, entry.getKey())) {
                    expire = entry.getValue() != null
                            ? entry.getValue()
                            : 30;
                    break;
                }
            }
        }

        redisTemplate.opsForSet().add(key, videoIds);
        redisTemplate.expire(key, expire, TimeUnit.MINUTES);
    }

    public Set<Long> getVideoIds(int appType, String mid) {
        if (StringUtils.isBlank(mid)) {
            return Collections.emptySet();
        }
        String key = String.format(KEY_PRE_VIEWED_FORMAT, appType, mid);
        try {

            //return preViewedCache.get(key);
            // 预曝光要求实时性，不适合本地缓存
            return getVideoIdsFromCache(key);
        } catch (Exception e) {
            log.error("get pre viewed cache error! key={}", key, e);
        }
        return Collections.emptySet();
    }

    private Set<Long> getVideoIdsFromCache(String key) {
        Set<Long> result = new HashSet<>();
        Cursor<String> cursor = redisTemplate.opsForSet().scan(key, ScanOptions.NONE);
        try {
            while (cursor != null && cursor.hasNext()) {
                Long videoId = NumberUtils.toLong(cursor.next(), 0);
                result.add(videoId);
            }
        } catch (Exception e) {
            log.error("sScan", e);
        } finally {
            try {
                if (cursor != null && !cursor.isClosed()) {
                    cursor.close();
                }
            } catch (IOException e) {
                log.error("sScan", e);
            }
        }
        return result;
    }

}
