package com.lanyuanxiaoyao.service.scheduler.quartz.compaction;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.service.common.Constants;
import com.lanyuanxiaoyao.service.common.entity.compaction.ScheduleJob;
import com.lanyuanxiaoyao.service.common.utils.LogHelper;
import com.lanyuanxiaoyao.service.configuration.entity.queue.QueueItem;
import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnClusters;
import com.lanyuanxiaoyao.service.configuration.utils.QueueUtil;
import com.lanyuanxiaoyao.service.scheduler.quartz.distribute.cluster.Cluster;
import java.util.Optional;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.map.ImmutableMap;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

/**
 * 分发压缩任务
 *
 * @author lanyuanxiaoyao
 * @date 2023-05-25
 */
@DisallowConcurrentExecution
public class DistributeScheduleJob extends BaseScheduleJob {
    private static final Logger logger = LoggerFactory.getLogger(DistributeScheduleJob.class);
    private static final String DISTRIBUTE_SCHEDULE_RETRY = "distribute_schedule_retry";

    private final DiscoveryClient discoveryClient;
    private final ObjectMapper mapper;
    private final ImmutableList<Cluster> clusters;
    private final String defaultCluster;

    public DistributeScheduleJob(DiscoveryClient discoveryClient, YarnClusters yarnClusters, ApplicationContext applicationContext, Jackson2ObjectMapperBuilder builder) {
        this.discoveryClient = discoveryClient;

        this.mapper = builder.build();
        MutableList<Cluster> list = Lists.mutable.empty();
        for (String cluster : yarnClusters.getActiveClusters()) {
            list.add(applicationContext.getBean(StrUtil.format("{}Cluster", StrUtil.toCamelCase(cluster)), Cluster.class));
        }
        this.clusters = list.toImmutable();
        this.defaultCluster = yarnClusters.getDefaultSyncCluster();
    }

    private String getAvailableQueue(QueueItem<ScheduleJob> item) {
        ImmutableMap<String, String> metadata = Maps.immutable.ofAll(item.getMetadata());
        if (metadata.containsKey(Constants.SCHEDULE_FORCE)) {
            String forceCluster = metadata.get(Constants.SCHEDULE_FORCE);
            Optional<Cluster> cluster = clusters.select(s -> StrUtil.equals(forceCluster, s.cluster())).getFirstOptional();
            if (cluster.isPresent()) {
                return cluster.get().queue();
            } else {
                logger.warn(StrUtil.format("{} cluster not found", forceCluster));
            }
        }
        if (metadata.containsKey(Constants.SCHEDULE_RECOMMEND)) {
            String recommendCluster = metadata.get(Constants.SCHEDULE_RECOMMEND);
            Optional<Cluster> cluster = clusters.select(s -> StrUtil.equals(recommendCluster, s.cluster())).getFirstOptional();
            if (cluster.isPresent() && cluster.get().available()) {
                return cluster.get().queue();
            } else {
                logger.warn(StrUtil.format("{} cluster not found or busy"));
            }
        }
        for (Cluster cluster : clusters) {
            if (cluster.available()) {
                return cluster.queue();
            }
        }
        return null;
    }

    @Override
    @SuppressWarnings("DataFlowIssue")
    protected void executeInternal(JobExecutionContext context) {
        QueueItem<ScheduleJob> item = QueueUtil.poll(discoveryClient, this.mapper, Constants.COMPACTION_QUEUE_PRE);
        if (ObjectUtil.isNotNull(item)) {
            LogHelper.setMdc(Constants.LOG_JOB_ID_LABEL, item.getTraceId());
            if (ObjectUtil.isNotNull(item.getData())) {
                ScheduleJob data = item.getData();
                LogHelper.setMdcFlinkJobAndAlias(data.getFlinkJobId(), data.getAlias());
            }
            try {
                String availableQueue = getAvailableQueue(item);
                // 如果没有获取到可用的调度集群
                if (ObjectUtil.isNull(availableQueue)) {
                    // 获取任务里的调度重试次数
                    long retryTime = item.getMetadata().containsKey(DISTRIBUTE_SCHEDULE_RETRY) ? Long.parseLong(item.getMetadata(DISTRIBUTE_SCHEDULE_RETRY)) : 0L;
                    // 如果重试次数小于6
                    if (retryTime < 6) {
                        // 重试次数加1
                        item.getMetadata().put(DISTRIBUTE_SCHEDULE_RETRY, String.valueOf(retryTime + 1));
                        // 暂停15秒
                        ThreadUtil.safeSleep(15 * Constants.SECOND);
                        // 重新放回pre队列
                        QueueUtil.add(discoveryClient, this.mapper, Constants.COMPACTION_QUEUE_PRE, item);
                    }
                    // 如果重试次数大于6
                    else {
                        logger.warn("Cannot found available cluster six time for item " + item.getId());
                        // 移除重试字段，因为已经交给保底队列，如果保底队列再次把任务放回pre队列，那就当作全新的任务重试计数
                        item.getMetadata().remove(DISTRIBUTE_SCHEDULE_RETRY);
                        QueueUtil.add(discoveryClient, this.mapper, this.defaultCluster, item);
                    }
                } else {
                    QueueUtil.add(discoveryClient, this.mapper, availableQueue, item);
                }
            } catch (Throwable e) {
                logger.error("Distribute error", e);
                QueueUtil.add(discoveryClient, this.mapper, Constants.COMPACTION_QUEUE_PRE, item);
            } finally {
                LogHelper.removeMdc(Constants.LOG_JOB_ID_LABEL, Constants.LOG_FLINK_JOB_ID_LABEL, Constants.LOG_ALIAS_LABEL);
            }
        }
    }
}
