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

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.tzld.piaoquan.recommend.server.common.RedisKeyConstants;
import com.tzld.piaoquan.recommend.server.common.ThreadPoolFactory;
import com.tzld.piaoquan.recommend.server.feign.FlowPoolFeign;
import com.tzld.piaoquan.recommend.server.feign.model.CommonResponse;
import com.tzld.piaoquan.recommend.server.feign.model.FlowPoolVideoInfo;
import com.tzld.piaoquan.recommend.server.model.TripleConsumer;
import com.tzld.piaoquan.recommend.server.model.Video;
import com.tzld.piaoquan.recommend.server.repository.DouHotVideoMapping;
import com.tzld.piaoquan.recommend.server.repository.DouHotVideoMappingRepository;
import com.tzld.piaoquan.recommend.server.repository.DouHotVideoPortraitDataRepository;
import com.tzld.piaoquan.recommend.server.repository.DouHotVideoProvince;
import com.tzld.piaoquan.recommend.server.util.RecallUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
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.KEY_WITH_LEVEL_FORMAT;
import static com.tzld.piaoquan.recommend.server.service.flowpool.FlowPoolConstants.KEY_WITH_LEVEL_FORMAT_V2;

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

    @Autowired
    private DouHotVideoMappingRepository douHotVideoMappingRepository;
    @Autowired
    private DouHotVideoPortraitDataRepository douHotVideoPortraitDataRepository;

    @Value("${dou.hot.flow.pool.id:18}")
    private Integer douHotFlowPoolId;

    @Autowired
    private FlowPoolFeign flowPoolFeign;

    private final String localDistributeCountFormat = "flow:pool: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 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) {
        asyncDelDistributeCountV2(videoFlowPoolMap, (appType, level, values) -> {
            redisTemplate.opsForSet().remove(String.format(KEY_WITH_LEVEL_FORMAT_V2, appType, level), values);
        });
        asyncDelDistributeCount(videoFlowPoolMap, (appType, level, values) -> {
            String key = String.format(KEY_WITH_LEVEL_FORMAT, appType, level);
            Long count = redisTemplate.opsForSet().remove(key, values);
        });
    }

    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.getLevelWeight4FlowPoolWithLevel();
            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);
                }
            }
        });
    }


    private void asyncDelDistributeCountV2(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);

            String[] values = new String[videoFlowPoolMap.size()];
            int i = 0;
            for (Map.Entry v : videoFlowPoolMap.entrySet()) {
                values[i++] = String.format(valueFormat, v.getKey(), v.getValue());
            }

            Map<String, Double> levelWeight = flowPoolConfigService.getLevelWeight4FlowPoolWithLevel();
            for (String level : levelWeight.keySet()) {
                flowPoolRemoveConsumer.accept(0, level, values);
            }
        });
    }

    public void asyncHandleDouHotCache(List<Video> videos, String province) {
        if (StringUtils.isBlank(province) || CollectionUtils.isEmpty(videos)) {
            return;
        }
        pool.execute(() -> {
            List<String> needRemoveVideoIds = new ArrayList<>();
            for (Video video : videos) {
                String distributeKey = String.format(RedisKeyConstants.DouHot.LOCAL_DISTRIBUTE_KEY_FORMAT, video.getVideoId(), video.getFlowPool());
                Long count = redisTemplate.opsForValue().decrement(distributeKey);
                if (Objects.isNull(count) || count <= 0) {
                    redisTemplate.delete(distributeKey);
                    needRemoveVideoIds.add(String.format("%d-%s", video.getVideoId(), video.getFlowPool()));
                }
            }
            if (CollectionUtils.isEmpty(needRemoveVideoIds)) {
                return;
            }

            Map<String, Double> douHotLevelWeight = flowPoolConfigService.getDouHotFLowPoolWithLevel();
            for (Map.Entry<String, Double> entry : douHotLevelWeight.entrySet()) {
                // 从流量池缓存中移除达到分发次数限制的视频ID
                String itemKey = String.format(RedisKeyConstants.DouHot.ITEM_REDIS_KEY_FORMAT, RecallUtils.douHotProvinceConvert(province), entry.getKey());
                // log.info("[DouHot view distribute count remove cache] key: {}, item: {}", itemKey, needRemoveVideoIds);
                redisTemplate.opsForZSet().remove(itemKey, needRemoveVideoIds.toArray());
            }
        });
    }

    public void syncDouHotFlowPoolVideo() {

        // 获取流量池中 全部热点宝视频
        List<FlowPoolVideoInfo> allDouHotVideo = this.findAllDouHotVideoFromFlowPool();
        // log.info("[DouHot video size]: {}", allDouHotVideo.size());

        // 获取省份与视频列表的映射，省份 -> [video]
        Map<String, Map<String, List<FlowPoolVideoInfo>>> levelAndProvinceAndVideoMap = this.findAllVideoAndProvinceMapByAttribute(allDouHotVideo);
        /*for (Map.Entry<String, Map<String, List<FlowPoolVideoInfo>>> levelEntry : levelAndProvinceAndVideoMap.entrySet()) {
            levelAndProvinceAndVideoMap.forEach((key, value) -> log.info("[DouHot province video size]. level: {}, province: {}, video size: {}", levelEntry.getKey(), key, value.size()));
        }*/

        // 获取某个省份下所有视频的分数, 省份 -> video -> score
        Map<String, Map<Long, Double>> videoInProvinceScore = this.findVideoInProvinceScore(allDouHotVideo);

        // 获取视频的可分发数，videoId-flowPool -> viewCnt
        Map<String, Integer> videoDistributeCount = this.findDouHotVideoDistributeCount(allDouHotVideo);
        // log.info("[DouHot view distribute count size]: {}", videoDistributeCount.size());

        this.douHotVideoWriteRedis(levelAndProvinceAndVideoMap, videoInProvinceScore, videoDistributeCount);
    }


    // public void syncDouHotFlowPoolVideoV1() {
    //     // 获取流量池中 全部热点宝视频
    //     List<FlowPoolVideoInfo> allDouHotVideo = this.findAllDouHotVideoFromFlowPool();
    //     log.info("[DouHot video size]: {}", allDouHotVideo.size());
    //
    //     List<Long> allVideoId = allDouHotVideo.stream()
    //             .map(FlowPoolVideoInfo::getVideoId)
    //             .collect(Collectors.toList());
    //
    //     // 获取视频对应的省份信息, video -> {省份: tgi}
    //     Map<Long, Map<String, Double>> allVideoAndProvinceMap = this.findAllVideoAndProvinceMap(allVideoId);
    //     log.info("[DouHot video and province mapping size]: {}", allVideoAndProvinceMap.size());
    //
    //     Map<String, List<FlowPoolVideoInfo>> provinceAndVideoListMap = new HashMap<>(allVideoAndProvinceMap.size());
    //
    //     // 获取省份与对应视频的映射, 省份 -> [视频信息列表]
    //     Map<Long, List<FlowPoolVideoInfo>> videoIdList = allDouHotVideo.stream()
    //             .collect(Collectors.groupingBy(FlowPoolVideoInfo::getVideoId));
    //
    //     for (Map.Entry<Long, Map<String, Double>> entry : allVideoAndProvinceMap.entrySet()) {
    //         Long videoId = entry.getKey();
    //         Set<String> provinceList = entry.getValue().keySet();
    //         for (String province : provinceList) {
    //             if (videoIdList.containsKey(videoId)) {
    //                 List<FlowPoolVideoInfo> flowPoolVideoInfos = provinceAndVideoListMap.computeIfAbsent(province, s -> new ArrayList<>());
    //                 flowPoolVideoInfos.addAll(videoIdList.get(videoId));
    //             }
    //         }
    //     }
    //     provinceAndVideoListMap.forEach((key, value) -> log.info("[DouHot province video size]: province: {}, video size: {}", key, value.size()));
    //
    //     // 将每个省份的数据写入Redis，并同步写入每个视频在对应流量池中的可分发数量
    //     // 获取视频对应的可分发数量, videoId-flowpool -> viewCnt
    //
    //     Map<String, Integer> videoAndDistributeCountMap = this.findDouHotVideoDistributeCount(allDouHotVideo);
    //     log.info("[DouHot view distribute count size]: {}", videoAndDistributeCountMap.size());
    //
    //     for (Map.Entry<String, List<FlowPoolVideoInfo>> entry : provinceAndVideoListMap.entrySet()) {
    //         String province = entry.getKey();
    //         List<FlowPoolVideoInfo> flowPoolVideoInfos = entry.getValue();
    //
    //         String redisKey = String.format(RedisKeyConstants.DouHot.ITEM_REDIS_KEY_FORMAT, province, "1");
    //         log.info("[DouHot item redis key]: redisKey: {}, video size: {}", redisKey, flowPoolVideoInfos.size());
    //         redisTemplate.delete(redisKey);
    //
    //         // 将视频添加到Redis缓存中，并设置可分发数量
    //         for (FlowPoolVideoInfo flowPoolVideoInfo : flowPoolVideoInfos) {
    //             String item = String.format("%d-%s", flowPoolVideoInfo.getVideoId(), flowPoolVideoInfo.getFlowPool());
    //
    //             // 如果剩余的可分发数量不存在或者小于为 则不添加到Redis中
    //             if (!videoAndDistributeCountMap.containsKey(item) || videoAndDistributeCountMap.get(item) <= 0) {
    //                 continue;
    //             }
    //
    //             // 获取视频在某个省份的tgi，作为视频的分数
    //             double score = 0;
    //             if (allVideoAndProvinceMap.containsKey(flowPoolVideoInfo.getVideoId())) {
    //                 score = allVideoAndProvinceMap.get(flowPoolVideoInfo.getVideoId()).getOrDefault(province, 0d);
    //             }
    //
    //             redisTemplate.opsForZSet().add(redisKey, item, score);
    //
    //             String distributeKey = String.format(RedisKeyConstants.DouHot.LOCAL_DISTRIBUTE_KEY_FORMAT, flowPoolVideoInfo.getVideoId(), flowPoolVideoInfo.getFlowPool());
    //             redisTemplate.opsForValue().set(distributeKey, String.valueOf(videoAndDistributeCountMap.get(item)));
    //             redisTemplate.expire(distributeKey, 24 * 60 * 60, TimeUnit.SECONDS);
    //         }
    //
    //         redisTemplate.expire(redisKey, 24 * 60 * 60, TimeUnit.SECONDS);
    //     }
    //
    // }


    private List<FlowPoolVideoInfo> findAllDouHotVideoFromFlowPool() {
        List<FlowPoolVideoInfo> result = new ArrayList<>();
        int pageNum = 1;
        while (true) {
            JSONObject paramJson = new JSONObject();
            paramJson.put("flowPoolId", douHotFlowPoolId);
            paramJson.put("appType", 0);
            paramJson.put("pageSize", 1000);
            paramJson.put("pageNum", pageNum++);

            // log.info("[get DouHot flow pool video] paramJson:{}", paramJson);
            CommonResponse<List<FlowPoolVideoInfo>> response = flowPoolFeign.getFlowPoolVideo(paramJson);
            if (0 != response.getCode()) {
                log.error("[get DouHot flow pool video request error] responseJson: {}", response);
                break;
            }

            if (CollectionUtils.isEmpty(response.getData())) {
                // log.error("[get DouHot flow pool video data is empty] responseJson: {}", response);
                break;
            }
            result.addAll(response.getData());
        }
        return result;
    }

    // 根据TGI获取流量池视频和省份的对应关系
    private Map<Long, Map<String, Double>> findAllVideoAndProvinceMapByTGI(List<FlowPoolVideoInfo> allDouHotVideo) {
        List<Long> videoIds = allDouHotVideo.stream()
                .map(FlowPoolVideoInfo::getVideoId)
                .distinct()
                .collect(Collectors.toList());

        // 获取票圈视频ID与热点宝vid的映射
        List<List<Long>> videoIdPartition = Lists.partition(videoIds, 500);
        Map<Long, String> videoIdAndVidMap = videoIdPartition.stream().map(douHotVideoMappingRepository::findAllByVideoIdIn)
                .flatMap(List::stream)
                .collect(Collectors.toMap(DouHotVideoMapping::getVideoId, DouHotVideoMapping::getVid, (o1, o2) -> o1));

        // 获取热点宝vid与地域的映射
        List<List<String>> vidPartition = Lists.partition(new ArrayList<>(videoIdAndVidMap.values()), 500);
        List<DouHotVideoProvince> douHotVideoProvince = vidPartition.stream()
                .map(douHotVideoPortraitDataRepository::findRecordTGIGe100AndRateDesc)
                .flatMap(List::stream)
                .collect(Collectors.toList());

        // 按vid和name分组，并取每组TGI倒排前10条
        Map<String, Map<String, Double>> vidAndProvinceListMap = douHotVideoProvince.stream()
                .collect(Collectors.groupingBy(
                                DouHotVideoProvince::getVid,  // 按 vid 分组
                                Collectors.collectingAndThen(
                                        Collectors.toList(),
                                        list -> list.stream()
                                                .sorted(Comparator.comparingDouble(DouHotVideoProvince::getRate).reversed()) // 按 占比 降序
                                                .limit(3) // 取前10个
                                                .sorted(Comparator.comparingDouble(DouHotVideoProvince::getTgi).reversed()) // 按 TGI 降序
                                                .limit(1) // 取第一个
                                                .collect(Collectors.toMap(i -> RecallUtils.douHotProvinceConvert(i.getName()), DouHotVideoProvince::getTgi, (o1, o2) -> o1)))
                        )
                );
        // vidAndProvinceListMap.forEach((key, value) -> log.info("[DouHot vid and province mapping]: vid: {}, province: {}", key, JSON.toJSONString(value)));
        Map<Long, Map<String, Double>> resultMap = new HashMap<>(videoIdAndVidMap.size());
        for (Map.Entry<Long, String> entry : videoIdAndVidMap.entrySet()) {
            Long videoId = entry.getKey();
            String vid = entry.getValue();
            if (vidAndProvinceListMap.containsKey(vid)) {
                resultMap.put(videoId, vidAndProvinceListMap.get(vid));
            }
        }

        return resultMap;
    }

    // 根据属性获取流量池视频和省份的对应关系
    private Map<String, Map<String, List<FlowPoolVideoInfo>>> findAllVideoAndProvinceMapByAttribute(List<FlowPoolVideoInfo> allDouHotVideo) {
        Map<String, Map<String, List<FlowPoolVideoInfo>>> resultMap = new HashMap<>(16);

        for (FlowPoolVideoInfo flowPoolVideoInfo : allDouHotVideo) {
            if (MapUtils.isEmpty(flowPoolVideoInfo.getAttribute())) {
                continue;
            }

            Map<String, List<FlowPoolVideoInfo>> provinceMap = resultMap.computeIfAbsent(String.valueOf(flowPoolVideoInfo.getLevel()), l -> new HashMap<>());

            String province = RecallUtils.douHotProvinceConvert(flowPoolVideoInfo.getAttribute().getString("province"));
            provinceMap.computeIfAbsent(province, s -> new ArrayList<>()).add(flowPoolVideoInfo);
        }

        return resultMap;
    }

    // 获取某个省份下所有视频的score, 省份 -> video -> score
    private Map<String, Map<Long, Double>> findVideoInProvinceScore(List<FlowPoolVideoInfo> allDouHotVideo) {
        Map<String, Map<Long, Double>> resultMap = new HashMap<>(allDouHotVideo.size());
        for (FlowPoolVideoInfo flowPoolVideoInfo : allDouHotVideo) {
            if (MapUtils.isEmpty(flowPoolVideoInfo.getAttribute())) {
                continue;
            }
            String province = RecallUtils.douHotProvinceConvert(flowPoolVideoInfo.getAttribute().getString("province"));
            resultMap.computeIfAbsent(province, s -> new HashMap<>()).put(flowPoolVideoInfo.getVideoId(), 1d);
        }
        return resultMap;
    }

    private Map<String, Integer> findDouHotVideoDistributeCount(List<FlowPoolVideoInfo> flowPoolVideoInfos) {
        List<JSONObject> paramJsonList = Lists.newArrayList();
        for (FlowPoolVideoInfo flowPoolVideoInfo : flowPoolVideoInfos) {
            JSONObject paramJson = new JSONObject();
            paramJson.put("videoId", flowPoolVideoInfo.getVideoId());
            paramJson.put("flowPool", flowPoolVideoInfo.getFlowPool());
            paramJsonList.add(paramJson);
        }

        List<FlowPoolVideoInfo> remainFlowPoolVideoInfos = new ArrayList<>(flowPoolVideoInfos.size());
        List<List<JSONObject>> partition = Lists.partition(paramJsonList, 10);
        for (List<JSONObject> videos : partition) {
            JSONObject paramJson = new JSONObject();
            paramJson.put("videos", videos);
            CommonResponse<List<FlowPoolVideoInfo>> response = flowPoolFeign.remainViewCount(paramJson);
            if (0 != response.getCode()) {
                log.error("[remain view count error] responseJson: {}", response);
                continue;
            }
            remainFlowPoolVideoInfos.addAll(response.getData());
        }

        Map<String, Integer> distributeCountMap = Maps.newHashMap();
        for (FlowPoolVideoInfo videoInfo : remainFlowPoolVideoInfos) {
            String key = String.format("%s-%s", videoInfo.getVideoId(), videoInfo.getFlowPool());
            distributeCountMap.put(key, videoInfo.getDistributeCount());
        }
        return distributeCountMap;
    }

    private void douHotVideoWriteRedis(Map<String, Map<String, List<FlowPoolVideoInfo>>> levelAndProvinceAndVideoMap, Map<String, Map<Long, Double>> provinceAllVideoScore, Map<String, Integer> videoDistributeCount) {
        if (MapUtils.isEmpty(levelAndProvinceAndVideoMap) || MapUtils.isEmpty(videoDistributeCount)) {
            return;
        }
        if (MapUtils.isEmpty(provinceAllVideoScore)) {
            provinceAllVideoScore = Maps.newHashMap();
        }

        for (Map.Entry<String, Map<String, List<FlowPoolVideoInfo>>> levelEntry : levelAndProvinceAndVideoMap.entrySet()) {

            String level = levelEntry.getKey();
            Map<String, List<FlowPoolVideoInfo>> provinceAndVideoMap = levelEntry.getValue();

            for (Map.Entry<String, List<FlowPoolVideoInfo>> entry : provinceAndVideoMap.entrySet()) {
                String province = entry.getKey();
                List<FlowPoolVideoInfo> flowPoolVideoInfos = entry.getValue();

                String redisKey = String.format(RedisKeyConstants.DouHot.ITEM_REDIS_KEY_FORMAT, province, level);
                // log.info("[DouHot item redis key]: redisKey: {}, video size: {}", redisKey, flowPoolVideoInfos.size());
                redisTemplate.delete(redisKey);

                Map<Long, Double> videoScoreInProvince = provinceAllVideoScore.getOrDefault(province, new HashMap<>());

                // 将视频添加到Redis缓存中，并设置可分发数量
                for (FlowPoolVideoInfo flowPoolVideoInfo : flowPoolVideoInfos) {
                    String item = String.format("%d-%s", flowPoolVideoInfo.getVideoId(), flowPoolVideoInfo.getFlowPool());

                    // 如果剩余的可分发数量不存在或者小于为 则不添加到Redis中
                    if (!videoDistributeCount.containsKey(item) || videoDistributeCount.get(item) <= 0) {
                        continue;
                    }

                    // 获取视频在这个省份里的分数
                    double score = videoScoreInProvince.getOrDefault(flowPoolVideoInfo.getVideoId(), 0d);

                    redisTemplate.opsForZSet().add(redisKey, item, score);

                    String distributeKey = String.format(RedisKeyConstants.DouHot.LOCAL_DISTRIBUTE_KEY_FORMAT, flowPoolVideoInfo.getVideoId(), flowPoolVideoInfo.getFlowPool());
                    redisTemplate.opsForValue().set(distributeKey, String.valueOf(videoDistributeCount.get(item)));
                    redisTemplate.expire(distributeKey, 24 * 60 * 60, TimeUnit.SECONDS);
                }

                redisTemplate.expire(redisKey, 24 * 60 * 60, TimeUnit.SECONDS);
            }
        }
    }
}

