package com.tzld.piaoquan.recommend.server.framework.merger;


import com.google.common.collect.Maps;
import com.tzld.piaoquan.recommend.server.common.base.RankItem;
import com.tzld.piaoquan.recommend.server.framework.candidiate.Candidate;
import com.tzld.piaoquan.recommend.server.framework.common.User;
import com.tzld.piaoquan.recommend.server.gen.recommend.RecommendRequest;
import lombok.Data;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

@Data
public abstract class StrategyQueue {
    // 得等召回结果，定义为RankItem
    protected final Map<String, Pair<MergeRule, List<RankItem>>> rankerItemsList = new HashMap<>();
    private final Logger LOGGER = LoggerFactory.getLogger(StrategyQueue.class);
    protected Map<String, StrategyQueue> children = Maps.newHashMap();

    protected List<RankItem> items = new ArrayList<>();
    private StrategyQueueInfo strategyQueueInfo;
    private List<StrategyQueue> allQueues = null;

    public StrategyQueue(StrategyQueueInfo strategyQueueInfo, StrategyQueueConfig strategyQueueConfig) {
        this.strategyQueueInfo = strategyQueueInfo;

        // 自顶向下构建child queue 初始化
        for (String childName : strategyQueueInfo.getChildren()) {
            if (!strategyQueueConfig.getStrategyQueueInfoMap().containsKey(childName)) {
                LOGGER.error("No config for Child queue [{}]", childName);
                continue;
            }
            StrategyQueueInfo childQueueInfo = strategyQueueConfig.getStrategyQueueInfoMap().get(childName);

            StrategyQueue childQueue = null;
            String childQueueClass = childQueueInfo.getQueueClass();
            try {
                childQueue = (StrategyQueue) Class.forName(childQueueClass)
                        .getConstructor(childQueueInfo.getClass(), strategyQueueConfig.getClass())
                        .newInstance(childQueueInfo, strategyQueueConfig);
            } catch (InstantiationException e) {
                LOGGER.error("construct StrategyQueue {}: [{}]", childName + " [" + childQueueClass + "]", e);
            } catch (IllegalAccessException e) {
                LOGGER.error("construct StrategyQueue {}: [{}]", childName + " [" + childQueueClass + "]", e);
            } catch (ClassNotFoundException e) {
                LOGGER.error("construct StrategyQueue {}: [{}]", childName + " [" + childQueueClass + "]", e);
            } catch (NoSuchMethodException e) {
                LOGGER.error("construct StrategyQueue {}: [{}]", childName + " [" + childQueueClass + "]", e);
            } catch (InvocationTargetException e) {
                LOGGER.error("construct StrategyQueue {}: [{}]", childName + " [" + childQueueClass + "]", e);
            }
            if (childQueue != null) {
                putChild(childName, childQueue);
            } else {
                LOGGER.error("construct child queue [{}] failed", childName);
            }
        }

    }

    public List<StrategyQueue> getAllQueues() {
        if (allQueues == null) {
            allQueues = new LinkedList<>();

            if (!strategyQueueInfo.isLeaf()) {
                for (Map.Entry<String, StrategyQueue> child : children.entrySet()) {
                    allQueues.addAll(child.getValue().getAllQueues());
                }
            }

            allQueues.add(this);
        }
        return allQueues;
    }


    private void putChild(String queueName, StrategyQueue child) {
        children.put(queueName, child);
    }


    public void clearItems() {
        items.clear();
    }

    public void addItem(RankItem item) {
        this.items.add(item);
    }

    public abstract int doMerge(final Map<String, Pair<MergeRule, List<RankItem>>> rankerItemsListMap, final int recNum, final User user, final RecommendRequest requestData, final int requestIndex, final int expId);

    // 定义索引Key 生成方式
    public abstract int candidate(Map<String, Candidate> candidateMap, final int recallNum, final User user, final RecommendRequest requestData, final int requestIndex, final int expId);

    public final int merge(final int recNum, final User user, final RecommendRequest requestData, final int requestIndex, final int expId) {
        this.beforeMerge(recNum, user, requestData, requestIndex, expId);
        this.doMerge(rankerItemsList, recNum, user, requestData, requestIndex, expId);
        this.doDeDup();
        return this.afterMerge();
    }

    // Merge 前从每个Queue中获取Item
    // 放入RankerItem中
    public void beforeMerge(final int recNum, final User user, final RecommendRequest requestData, final int requestIndex, final int expId) {
        if (strategyQueueInfo.isLeaf()) {
            LOGGER.debug("leaf queue [{}] with [{}] items to merge", getStrategyQueueInfo().getQueueName(), items.size());
        } else {
            rankerItemsList.clear();
            clearItems();
            int queueCount = 0, itemCount = 0;
            for (MergeRule rule : strategyQueueInfo.getRulesList()) {
                if (rule.isDisabled(expId)) {
                    continue;
                }
                int myRecNum = Math.min(recNum, rule.maxMergeNum);
                //递归的执行子节点的merge逻辑
                int actualRecNum = children.get(rule.queueName).merge(myRecNum, user, requestData, requestIndex, expId);
                queueCount += 1;
                itemCount += actualRecNum;
                rankerItemsList.put(rule.queueName, Pair.of(rule, children.get(rule.queueName).getItems()));
            }
            LOGGER.debug(getStrategyQueueInfo().getQueueName() + " merge info: [{}] queues with [{}] items to merge", queueCount, itemCount);
        }
    }

    /**
     * id dedup 去重操作
     *
     * @return
     */
    public final int doDeDup() {
        List<RankItem> pureItems = new ArrayList<RankItem>();
        Set<String> deDupItems = new HashSet<String>();
        for (RankItem item : items) {
            if (deDupItems.contains(item.getId())) {
                continue;
            }
            deDupItems.add(item.getId());
            pureItems.add(item);
        }
        items = pureItems;
        return items.size();
    }

    // 给item增加融合队列名称
    public int afterMerge() {
        int n = items.size();
        for (RankItem item : items) {
            item.addMergeQueuePath(getStrategyQueueInfo().getQueueName());
        }
        LOGGER.debug("number of items after [{}] merge: [{}]", getStrategyQueueInfo().getQueueName(), n);
        return n;
    }

    // 读取子队列的merge规则，按队列生成candidate
    protected int simpleCandidateByPercentage(Map<String, Candidate> candidateMap, int recallNum, User user, RecommendRequest requestData, int requestIndex, int expId) {
        int n = 0;

        for (MergeRule rule : strategyQueueInfo.getRulesList()) {
            if (rule.isDisabled(expId)) {
                continue;
            }

            int myRecallNum = (int) (recallNum * rule.recallPercentage);
            if (!children.containsKey(rule.queueName)) {
                LOGGER.error("Rule for not exist child queue [{}]", rule.queueName);
            }

            Map<String, Candidate> myCandidates = new HashMap<>();
            StrategyQueue strategyQueue = children.get(rule.queueName);
            if (strategyQueue != null) {
                n += strategyQueue.candidate(myCandidates, myRecallNum, user, requestData, requestIndex, expId);
            }
            candidateMap.putAll(myCandidates);
        }

        return n;
    }

    @Override
    public String toString() {
        return "StrategyQueue{" +
                "rankerItemsList=" + rankerItemsList +
                ", children=" + children +
                ", items=" + items +
                ", strategyQueueInfo=" + strategyQueueInfo +
                ", allQueues=" + allQueues +
                '}';
    }
}
