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

import com.tzld.piaoquan.recommend.server.model.TripleConsumer;
import com.tzld.piaoquan.recommend.server.model.Video;
import com.tzld.piaoquan.recommend.server.common.ThreadPoolFactory;
import com.tzld.piaoquan.recommend.server.util.JSONUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
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.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

import static com.tzld.piaoquan.recommend.server.common.enums.AppTypeEnum.*;
import static com.tzld.piaoquan.recommend.server.service.flowpool.FlowPoolConstants.*;

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

    private final String localDistributeCountFormat = "flow:pool:local:distribute:count:%s:%s";
    /**
     * 供给池 本地缓存
     * flow:pool:supply:local:distribute:count:{videoId}:{flowPool标记}
     */
    private final String supplyLocalDistributeCountFormat = "flow:pool:supply:local:distribute:count:%s:%s";

    public final String valueFormat = "%s-%s";

    private ExecutorService pool = ThreadPoolFactory.defaultPool();

    private int[] appTypes = {VLOG.getCode(), LOVE_MOVIE.getCode(), LOVE_LIVE.getCode(), LONG_VIDEO.getCode(),
            SHORT_VIDEO.getCode(), H5.getCode(), APP_SPEED.getCode(), WAN_NENG_VIDEO.getCode(),
            LAO_HAO_KAN_VIDEO.getCode(), ZUI_JING_QI.getCode(), PIAO_QUAN_VIDEO_PLUS.getCode(), JOURNEY.getCode()};


    public Map<Long, Integer> getDistributeCountWithLevel(Map<Long, String> videoFlowPoolMap) {
        if (MapUtils.isEmpty(videoFlowPoolMap)) {
            return Collections.emptyMap();
        }

        Map<Long, Integer> result = getDistributeCount(videoFlowPoolMap);


        // 处理脏数据：分发数<0
        Map<Long, String> dirties = videoFlowPoolMap.entrySet().stream()
                .filter(e -> result.get(e.getKey()) <= 0)
                .collect(Collectors.toMap(
                        e -> e.getKey(),
                        e -> e.getValue()
                ));
        asyncDelDistributeCountWithLevel(dirties);

        return result;
    }

    public void updateDistributeCountWithLevel(List<Video> videos) {
        if (CollectionUtils.isEmpty(videos)) {
            return;
        }
        Map<Long, String> removeMap = updateDistributeCount(videos);

        asyncDelDistributeCountWithLevel(removeMap);

    }

    private void asyncDelDistributeCountWithLevel(Map<Long, String> videoFlowPoolMap) {
        asyncDelDistributeCount(videoFlowPoolMap, (appType, level, values) -> {
            String key = String.format(KEY_WITH_LEVEL_FORMAT, appType, level);
            Long count = redisTemplate.opsForSet().remove(key, values);
            redisTemplate.opsForSet().remove(String.format(KEY_QUICK_WITH_LEVEL_FORMAT, appType), values);
        });
    }

    public Map<Long, Integer> getDistributeCountWithLevelScore(Map<Long, String> videoFlowPoolMap) {
        if (MapUtils.isEmpty(videoFlowPoolMap)) {
            return Collections.emptyMap();
        }

        Map<Long, Integer> result = getDistributeCount(videoFlowPoolMap);
        // 处理脏数据：分发数<0
        Map<Long, String> dirties = videoFlowPoolMap.entrySet().stream()
                .filter(e -> result.get(e.getKey()) <= 0)
                .collect(Collectors.toMap(
                        e -> e.getKey(),
                        e -> e.getValue()
                ));
        asyncLocalDistributeCountWithLevelScore(dirties);

        return result;
    }

    public void updateDistributeCountWithLevelScore(List<Video> videos) {
        if (CollectionUtils.isEmpty(videos)) {
            return;
        }
        Map<Long, String> removeMap = updateDistributeCount(videos);

        asyncLocalDistributeCountWithLevelScore(removeMap);
    }

    private void asyncLocalDistributeCountWithLevelScore(Map<Long, String> videoFlowPoolMap) {
        asyncDelDistributeCount(videoFlowPoolMap, (appType, level, values) -> {
            String key = String.format(KEY_WITH_LEVEL_SCORE_FORMAT, appType, level);
            redisTemplate.opsForZSet().remove(key, values);
            redisTemplate.opsForZSet().remove(String.format(KEY_QUICK_WITH_LEVEL_SCORE_FORMAT, appType), values);
        });
    }

    public Map<Long, Integer> getDistributeCountWithScore(Map<Long, String> videoFlowPoolMap) {
        if (MapUtils.isEmpty(videoFlowPoolMap)) {
            return Collections.emptyMap();
        }
        Map<Long, Integer> result = getDistributeCount(videoFlowPoolMap);
        // 处理脏数据：分发数<0
        Map<Long, String> dirties = videoFlowPoolMap.entrySet().stream()
                .filter(e -> result.get(e.getKey()) <= 0)
                .collect(Collectors.toMap(
                        e -> e.getKey(),
                        e -> e.getValue()
                ));
        asyncDelDistributeCountWithScore(dirties);
        return result;
    }

    public void updateDistributeCountWithScore(List<Video> videos) {
        if (CollectionUtils.isEmpty(videos)) {
            return;
        }
        Map<Long, String> removeMap = updateDistributeCount(videos);
        asyncDelDistributeCountWithScore(removeMap);
    }

    private void asyncDelDistributeCountWithScore(Map<Long, String> videoFlowPoolMap) {
        asyncDelDistributeCount(videoFlowPoolMap, (appType, level, values) -> {
            String key = String.format(KEY_WITH_SCORE_FORMAT, appType, level);
            Long count = redisTemplate.opsForZSet().remove(key, values);

            redisTemplate.opsForZSet().remove(String.format(KEY_QUICK_WITH_SCORE_FORMAT, appType), values);
        });
    }

    private Map<Long, Integer> getDistributeCount(Map<Long, String> videoFlowPoolMap) {
        // 为了保证有序
        List<Map.Entry<Long, String>> entries = videoFlowPoolMap.entrySet().stream()
                .sorted(Comparator.comparingLong(e -> e.getKey()))
                .collect(Collectors.toList());

        List<String> keys = entries.stream()
                .map(v -> String.format(localDistributeCountFormat, v.getKey(), v.getValue()))
                .collect(Collectors.toList());
        List<String> counts = redisTemplate.opsForValue().multiGet(keys);
        Map<Long, Integer> result = new HashMap<>();
        for (int i = 0; i < entries.size(); i++) {
            result.put(entries.get(i).getKey(), NumberUtils.toInt(counts.get(i), 0));
        }
        return result;
    }

    private Map<Long, String> updateDistributeCount(List<Video> videos) {
        // TODO 异步更新
        Map<Long, String> removeMap = new HashMap<>();
        videos.stream().forEach(v -> {
            String key = String.format(localDistributeCountFormat, v.getVideoId(), v.getFlowPool());
            Long count = redisTemplate.opsForValue().decrement(key);
            if (count <= 0) {
                removeMap.put(v.getVideoId(), v.getFlowPool());
            }
        });
        return removeMap;
    }

    private void asyncDelDistributeCount(Map<Long, String> videoFlowPoolMap,
                                         TripleConsumer<Integer, String, String[]> flowPoolRemoveConsumer) {
        if (MapUtils.isEmpty(videoFlowPoolMap)) {
            return;
        }
        pool.execute(() -> {
            List<String> keys = videoFlowPoolMap.entrySet().stream()
                    .map(v -> String.format(localDistributeCountFormat, v.getKey(), v.getValue()))
                    .collect(Collectors.toList());
            redisTemplate.delete(keys);

            Map<String, Double> levelWeight = flowPoolConfigService.getLevelWeight();
            String[] values = new String[videoFlowPoolMap.size()];
            int i = 0;
            for (Map.Entry v : videoFlowPoolMap.entrySet()) {
                values[i++] = String.format(valueFormat, v.getKey(), v.getValue());
            }
            // remove 每小程序每层的数据
            // TODO 现在视频只会出现在一个层级，所以可以做个优化
            for (String level : levelWeight.keySet()) {
                for (int appType : appTypes) {
                    flowPoolRemoveConsumer.accept(appType, level, values);
                }
            }
        });
    }

    public Map<Long, Integer> getSupplyDistributeCountWithLevel(Map<Long, String> videoFlowPoolMap) {
        if (MapUtils.isEmpty(videoFlowPoolMap)) {
            return Collections.emptyMap();
        }

        Map<Long, Integer> result = getSupplyDistributeCount(videoFlowPoolMap);


        // 处理脏数据：分发数<0
        Map<Long, String> dirties = videoFlowPoolMap.entrySet().stream()
                .filter(e -> result.get(e.getKey()) <= 0)
                .collect(Collectors.toMap(
                        e -> e.getKey(),
                        e -> e.getValue()
                ));
        asyncDelSupplyDistributeCountWithLevel(dirties);

        return result;
    }

    private Map<Long, Integer> getSupplyDistributeCount(Map<Long, String> videoFlowPoolMap) {
        // 为了保证有序
        List<Map.Entry<Long, String>> entries = videoFlowPoolMap.entrySet().stream()
                .sorted(Comparator.comparingLong(e -> e.getKey()))
                .collect(Collectors.toList());

        List<String> keys = entries.stream()
                .map(v -> String.format(supplyLocalDistributeCountFormat, v.getKey(), v.getValue()))
                .collect(Collectors.toList());
        List<String> counts = redisTemplate.opsForValue().multiGet(keys);
        Map<Long, Integer> result = new HashMap<>();
        for (int i = 0; i < entries.size(); i++) {
            result.put(entries.get(i).getKey(), NumberUtils.toInt(counts.get(i), 0));
        }
        return result;
    }

    private Map<Long, String> updateSupplyDistributeCount(List<Video> videos) {
        // TODO 异步更新
        Map<Long, String> removeMap = new HashMap<>();
        videos.stream().forEach(v -> {
            String key = String.format(supplyLocalDistributeCountFormat, v.getVideoId(), v.getFlowPool());
            Long count = redisTemplate.opsForValue().decrement(key);
            if (count <= 0) {
                removeMap.put(v.getVideoId(), v.getFlowPool());
            }
        });
        return removeMap;
    }

    public void updateSupplyDistributeCountWithLevel(List<Video> videos) {
        if (CollectionUtils.isEmpty(videos)) {
            return;
        }
        Map<Long, String> removeMap = updateSupplyDistributeCount(videos);

        asyncDelSupplyDistributeCountWithLevel(removeMap);

    }

    private void asyncDelSupplyDistributeCountWithLevel(Map<Long, String> videoFlowPoolMap) {
        asyncDelDistributeCount(videoFlowPoolMap, (appType, level, values) -> {
            String key = String.format(KEY_WITH_LEVEL_SUPPLY_FORMAT, appType, level);
            Long count = redisTemplate.opsForSet().remove(key, values);
        });
    }
}
