package com.tzld.piaoquan.ad.engine.service.score.strategy;

import com.alibaba.fastjson.JSON;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.tzld.piaoquan.ad.engine.commons.dto.AdPlatformCreativeDTO;
import com.tzld.piaoquan.ad.engine.commons.param.RankRecommendRequestParam;
import com.tzld.piaoquan.ad.engine.commons.score.ScoreParam;
import com.tzld.piaoquan.ad.engine.commons.util.WeightRandom;
import com.tzld.piaoquan.ad.engine.service.feature.Feature;
import com.tzld.piaoquan.recommend.feature.domain.ad.base.AdRankItem;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
public class RankStrategyBy687 extends RankStrategyBasic {

    @Autowired
    private RankStrategyBy688 rankStrategyBy688;

    @ApolloJsonValue("${cid.open.hot.rank.user.layer:[]}")
    private Set<String> openHotRankUserLayers;

    @Value("${hot.rank.customer.view.threshold:5000}")
    private Integer hotRankCustomerViewThreshold;

    @Value("${hot.rank.creative.view.threshold:5000}")
    private Integer hotRankCreativeViewThreshold;

    @Value("${hot.rank.calc.weight.coefficient:2.5}")
    private Double hotRankCalcWeightCoefficient;

    @Value("${hot.rank.max.weight:1000}")
    private Double hotRankMaxWeight;

    private static final String CUSTOMER_FIELD_NAME = "customer_name";

    @Override
    public List<AdRankItem> adItemRank(RankRecommendRequestParam request, ScoreParam scoreParam) {
        // 没有mid或者未配置分层，走基线排序
        if (StringUtils.isEmpty(request.getMid()) || CollectionUtils.isEmpty(openHotRankUserLayers)) {
            return rankStrategyBy688.adItemRank(request, scoreParam);
        }

        // 当前分层未开启此策略
        String userLayer = this.getUserLayer(request);
        if (!openHotRankUserLayers.contains(userLayer)) {
            return rankStrategyBy688.adItemRank(request, scoreParam);
        }

        filterRequestAdList(request, scoreParam);

        Set<String> noApiAdVerIds = getNoApiAdVerIds();

        List<AdPlatformCreativeDTO> recallCreativeList = request.getAdIdList();

        // 获取所有创意对应的客户列表
        Feature feature = this.getFeature(scoreParam, request);
        if (feature == null) {
            log.warn("adItemRank: feature is null, skip processing. request={}", request);
            return new ArrayList<>();
        }

        List<AdRankItem> rankItems = new ArrayList<>(recallCreativeList.size());
        for (AdPlatformCreativeDTO dto : recallCreativeList) {
            AdRankItem adRankItem = this.creativeCovertRankItem(dto, request, noApiAdVerIds);
            this.fullCustomerInfo(feature.getCidFeature(), adRankItem);
            adRankItem.getExt().put("isHotRank", true);
            rankItems.add(adRankItem);
        }

        // 选择一个客户
        List<String> customers = rankItems.stream().map(i -> i.getExt().getOrDefault(CUSTOMER_FIELD_NAME, ""))
                .map(Objects::toString)
                .filter(StringUtils::isNotEmpty)
                .distinct()
                .collect(Collectors.toList());
        String keyFormat = "ad:engine:customer:layer:info:%s:" + userLayer;
        List<HotRankFeatureInfo> customerFeature = this.multiGetFeature(customers, keyFormat);
        String customer = this.choose(customerFeature, hotRankCustomerViewThreshold);

        // 从当前客户的所有创意中，选择一个创意
        List<String> customerCreativeIds = rankItems.stream().filter(c -> StringUtils.equals(c.getExt().getOrDefault(CUSTOMER_FIELD_NAME, "").toString(), customer))
                .map(AdRankItem::getAdId)
                .map(Objects::toString)
                .collect(Collectors.toList());
        String cidKeyFormat = "ad:engine:cid:layer:info:%s:" + userLayer;
        List<HotRankFeatureInfo> creativeFeature = this.multiGetFeature(customerCreativeIds, cidKeyFormat);
        String chooseCreativeIdStr = this.choose(creativeFeature, hotRankCreativeViewThreshold);
        Long chooseCreativeId = Long.parseLong(chooseCreativeIdStr);

        Map<String, HotRankFeatureInfo> allCustomerFeatureMap = customerFeature.stream()
                .collect(Collectors.toMap(HotRankFeatureInfo::getLabel, Function.identity(), (o1, o2) -> o1));

        Map<Long, HotRankFeatureInfo> allCreativeFeatureInfoMap = creativeFeature.stream()
                .collect(Collectors.toMap(i -> Long.parseLong(i.getLabel()), Function.identity(), (o1, o2) -> o1));

        Map<String, String> sceneFeature = this.handleSceneFeature(System.currentTimeMillis() / 1000);
        Map<String, String> reqFeature = this.getReqFeature(scoreParam, request);

        List<AdRankItem> result = new ArrayList<>(rankItems.size());
        for (AdRankItem rankItem : rankItems) {
            // 补充特征信息
            this.fullMetaFeature(rankItem, userLayer, allCustomerFeatureMap, allCreativeFeatureInfoMap, feature);
            this.putMetaFeature(rankItem, feature, reqFeature, sceneFeature, request);
            if (Objects.equals(rankItem.getAdId(), chooseCreativeId)) {
                result.add(0, rankItem);
            } else {
                result.add(rankItem);
            }
        }

        return result;
    }

    private String getUserLayer(RankRecommendRequestParam request) {


        Map<String, String> userLayer = this.getUserLayer(request.getMid());

        String layer = userLayer.getOrDefault("layer", "无曝光");
        String clazz = userLayer.getOrDefault("class", "近期未出现");
        if (request.getIsFilterUser()) {
            return String.join("-", layer, "炸", clazz);
        } else {
            return String.join("-", layer, clazz);
        }
    }

    private List<HotRankFeatureInfo> multiGetFeature(List<String> customers, String keyFormat) {
        List<HotRankFeatureInfo> hotRankFeatureInfos = new ArrayList<>(customers.size());

        List<String> keys = new ArrayList<>(customers.size());
        for (String customer : customers) {
            String key = String.format(keyFormat, customer);
            keys.add(key);
        }

        List<String> values = algRedisHelper.mget(keys);

        for (int i = 0; i < customers.size(); i++) {
            String value = values.get(i);
            if (StringUtils.isEmpty(value)) {
                value = "{}";
            }
            HotRankFeatureInfo hotRankFeatureInfo = JSON.parseObject(value, HotRankFeatureInfo.class);
            hotRankFeatureInfo.setLabel(customers.get(i));
            hotRankFeatureInfos.add(hotRankFeatureInfo);
        }

        return hotRankFeatureInfos;
    }

    private void fullCustomerInfo(Map<String, Map<String, Map<String, String>>> cidFeature, AdRankItem rankItem) {
        Map<String, Map<String, String>> feature = cidFeature.getOrDefault(String.valueOf(rankItem.getAdId()), new HashMap<>());
        Map<String, String> basicInfo = feature.getOrDefault("alg_cid_feature_basic_info", new HashMap<>());
        String customer = basicInfo.getOrDefault(CUSTOMER_FIELD_NAME, "");
        rankItem.getExt().put(CUSTOMER_FIELD_NAME, customer);
    }

    private String choose(List<HotRankFeatureInfo> hotRankFeatureInfos, int viewThreshold) {
        // 按CPM排序
        hotRankFeatureInfos.sort(Comparator.comparing(HotRankFeatureInfo::getCpm).reversed());

        // 按照曝光拆分
        List<HotRankFeatureInfo> highView = new ArrayList<>();
        List<HotRankFeatureInfo> tailView = new ArrayList<>();
        int highIdx = 1, tailIdx = 1;
        for (HotRankFeatureInfo hotRankFeatureInfo : hotRankFeatureInfos) {
            if (hotRankFeatureInfo.getView() >= viewThreshold) {
                hotRankFeatureInfo.setRank(highIdx++);
                highView.add(hotRankFeatureInfo);
            } else {
                hotRankFeatureInfo.setRank(tailIdx++);
                tailView.add(hotRankFeatureInfo);
            }
        }

        // 设置高曝光列表的初始权重
        this.calcFirstItemWeight(highView, tailView, true);
        // 计算高曝光列表中其他的权重
        this.calcListOtherWeight(highView);

        // 设置低曝光列表的初始权重
        this.calcFirstItemWeight(highView, tailView, false);
        // 计算低曝光列表中其他的权重
        this.calcListOtherWeight(tailView);

        List<WeightRandom.ItemWithWeight<String>> itemWithWeights = new ArrayList<>(hotRankFeatureInfos.size());
        for (HotRankFeatureInfo hotRankFeatureInfo : hotRankFeatureInfos) {
            WeightRandom.ItemWithWeight<String> itemWeight = new WeightRandom.ItemWithWeight<>(hotRankFeatureInfo.getLabel(), hotRankFeatureInfo.getWeight());
            itemWithWeights.add(itemWeight);
        }

        return new WeightRandom<>(itemWithWeights).choose();
    }

    private void calcFirstItemWeight(List<HotRankFeatureInfo> high, List<HotRankFeatureInfo> tail, boolean isHigh) {
        if (CollectionUtils.isEmpty(high) && CollectionUtils.isEmpty(tail)) {
            return;
        }

        // 设置高曝光的初始权重
        if (isHigh && CollectionUtils.isNotEmpty(high)) {
            high.get(0).setWeight(Math.max(1, hotRankMaxWeight));
            return;
        }

        // 设置低曝光的初始权重，从高曝光列表中找到第一个CPM小于等于低曝光最高CPM的元素权重
        if (!isHigh && CollectionUtils.isNotEmpty(tail)) {
            HotRankFeatureInfo firstTail = tail.get(0);
            double firstCpm = firstTail.getCpm();
            for (HotRankFeatureInfo info : high) {
                if (firstCpm >= info.getCpm()) {
                    // 权重最小为1
                    double weight = Math.max(1, info.getWeight() / hotRankCalcWeightCoefficient);
                    firstTail.setWeight(weight);
                    return;
                }
            }
            firstTail.setWeight(1);
        }
    }

    /**
     * 计算列表中第二个元素往后的权重
     */
    private void calcListOtherWeight(List<HotRankFeatureInfo> rankItem) {
        for (int i = 1; i < rankItem.size(); i++) {
            HotRankFeatureInfo curr = rankItem.get(i);
            if (curr.getCpm() == 0) {
                curr.setWeight(1);
                continue;
            }
            HotRankFeatureInfo prev = rankItem.get(i - 1);
            double currWeight1 = prev.getWeight() * (1 - (1 - curr.getCpm() / prev.getCpm()) * hotRankCalcWeightCoefficient);
            double currWeight2 = prev.getWeight() / hotRankCalcWeightCoefficient;
            double currWeight = Math.min(currWeight1, currWeight2);
            if (currWeight <= 1) {
                currWeight = Math.max(currWeight1, currWeight2);
            }
            curr.setWeight(Math.max(1, currWeight));
        }
    }

    private void fullMetaFeature(AdRankItem rankItem, String userLayer, Map<String, HotRankFeatureInfo> allCustomerFeatureMap, Map<Long, HotRankFeatureInfo> allCreativeFeatureMap, Feature feature) {
        String customer = rankItem.getExt().getOrDefault(CUSTOMER_FIELD_NAME, "").toString();

        Map<String, Double> scoreMap = new HashMap<>();
        scoreMap.put("hotRankCustomerViewThreshold", hotRankCustomerViewThreshold.doubleValue());
        scoreMap.put("hotRankCreativeViewThreshold", hotRankCreativeViewThreshold.doubleValue());
        scoreMap.put("hotRankCalcWeightCoefficient", hotRankCalcWeightCoefficient);
        scoreMap.put("hotRankMaxWeight", hotRankMaxWeight);

        if (StringUtils.isNotBlank(customer) && allCustomerFeatureMap.containsKey(customer)) {
            HotRankFeatureInfo customerFeatureInfo = allCustomerFeatureMap.get(customer);
            rankItem.getMetaFeatureMap().put("customerFeature", customerFeatureInfo.covertToFeatureMap());

            scoreMap.put("customer_weight", customerFeatureInfo.getWeight());
            scoreMap.put("customer_cpm", customerFeatureInfo.getCpm());
            scoreMap.put("customer_rank", (double) customerFeatureInfo.getRank());
            scoreMap.put("customer_view", (double) customerFeatureInfo.getView());
        }

        if (allCreativeFeatureMap.containsKey(rankItem.getAdId())) {
            HotRankFeatureInfo creativeFeatureInfo = allCreativeFeatureMap.get(rankItem.getAdId());
            rankItem.getMetaFeatureMap().put("creativeFeature", creativeFeatureInfo.covertToFeatureMap());

            scoreMap.put("weight", creativeFeatureInfo.getWeight());
            scoreMap.put("cpm", creativeFeatureInfo.getCpm());
            scoreMap.put("rank", (double) creativeFeatureInfo.getRank());
            scoreMap.put("view", (double) creativeFeatureInfo.getView());
        }

        Map<String, String> midFeature = new HashMap<>(4);
        midFeature.put("userLayer", userLayer);
        rankItem.getMetaFeatureMap().put("midFeature", midFeature);

        rankItem.getScoreMap().putAll(scoreMap);
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class HotRankFeatureInfo {
        private String label;

        private double cpm = 0;

        private int view = 0;

        private double weight = 1;

        private int rank;

        public Map<String, String> covertToFeatureMap() {
            Map<String, String> featureMap = new HashMap<>(4);
            featureMap.put("label", label);
            featureMap.put("rank", String.valueOf(this.getRank()));
            featureMap.put("cpm", String.valueOf(this.getCpm()));
            featureMap.put("view", String.valueOf(this.getView()));
            featureMap.put("weight", String.valueOf(this.getWeight()));
            return featureMap;
        }
    }
}
