package com.lanyuanxiaoyao.service.scheduler.utils;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.loki4j.slf4j.marker.LabelMarker;
import com.lanyuanxiaoyao.service.common.Constants;
import com.lanyuanxiaoyao.service.common.entity.SyncState;
import com.lanyuanxiaoyao.service.common.entity.compaction.ScheduleJob;
import com.lanyuanxiaoyao.service.common.utils.TagsHelper;
import com.lanyuanxiaoyao.service.configuration.ExecutorProvider;
import com.lanyuanxiaoyao.service.configuration.entity.info.SimpleTableMeta;
import com.lanyuanxiaoyao.service.configuration.entity.queue.QueueItem;
import com.lanyuanxiaoyao.service.configuration.utils.QueueUtil;
import com.lanyuanxiaoyao.service.forest.service.HudiService;
import com.lanyuanxiaoyao.service.forest.service.InfoService;
import java.util.Comparator;
import java.util.function.Predicate;
import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.map.ImmutableMap;
import org.eclipse.collections.api.map.MutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.springframework.cloud.client.discovery.DiscoveryClient;

/**
 * 压缩调度工具
 *
 * @author lanyuanxiaoyao
 * @date 2023-05-12
 */
public class ScheduleHelper {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleHelper.class);

    private static Marker makeMarker(Long flinkJobId, String alias) {
        return LabelMarker.of(() -> Maps.mutable.of(Constants.LOG_FLINK_JOB_ID, flinkJobId.toString(), Constants.LOG_ALIAS, alias));
    }

    public static void schedule(
            DiscoveryClient discoveryClient,
            InfoService infoService,
            HudiService hudiService,
            ObjectMapper mapper,
            Predicate<SimpleTableMeta> predicate,
            String comment
    ) {
        schedule(discoveryClient, infoService, hudiService, mapper, predicate, comment, Maps.immutable.empty());
    }

    public static void schedule(
            DiscoveryClient discoveryClient,
            InfoService infoService,
            HudiService hudiService,
            ObjectMapper mapper,
            Predicate<SimpleTableMeta> predicate,
            String comment,
            ImmutableMap<String, String> metadata
    ) {
        String batchId = IdUtil.nanoId(10);
        infoService.simpleTableMetas()
                // 只调度 MOR 表
                .select(meta -> StrUtil.equals(Constants.MOR, meta.getTargetTableType()))
                .asParallel(ExecutorProvider.EXECUTORS, 1)
                .select(predicate::test)
                // 没有 Hudi 表的过滤掉
                .select(meta -> {
                    try {
                        return hudiService.existsHudiTable(meta.getFlinkJobId(), meta.getAlias());
                    } catch (Throwable throwable) {
                        logger.error(makeMarker(meta.getFlinkJobId(), meta.getAlias()), "Get hudi status failure", throwable);
                    }
                    return false;
                })
                // 没有压缩计划的过滤掉
                .select(meta -> {
                    try {
                        return hudiService.existsCompactionPlan(meta.getFlinkJobId(), meta.getAlias());
                    } catch (Throwable throwable) {
                        logger.error(makeMarker(meta.getFlinkJobId(), meta.getAlias()), "Get compaction status failure", throwable);
                    }
                    return false;
                })
                // 拒绝不压缩标志的任务
                .reject(meta -> TagsHelper.existsTag(meta.getTags(), Constants.TAGS_NO_COMPACT))
                // 拒绝不调度压缩标志的任务
                .reject(meta -> TagsHelper.existsTag(meta.getTags(), Constants.TAGS_NO_SCHEDULE_COMPACT))
                .collect(meta -> {
                    long compactionDuration = 0L;
                    try {
                        // 计算压缩耗时
                        SyncState syncState = infoService.syncStateDetail(meta.getFlinkJobId(), meta.getAlias());
                        if (ObjectUtil.isNotNull(syncState)
                            && ObjectUtil.isNotNull(syncState.getCompactionFinishTime())
                            && ObjectUtil.isNotNull(syncState.getCompactionStartTime())) {
                            compactionDuration = syncState.getCompactionFinishTime() - syncState.getCompactionStartTime();
                        }
                    } catch (Throwable e) {
                        logger.error(makeMarker(meta.getFlinkJobId(), meta.getAlias()), "Get sync state failure for {} {}", meta.getFlinkJobId(), meta.getAlias());
                    }
                    return new TableMetaWrapper(meta, compactionDuration);
                })
                .toSortedList(
                        Comparator
                                // 比较 Bucket 数，数量大的在前面
                                .comparing(TableMetaWrapper::getBucketNumber, Comparator.reverseOrder())
                                // 比较压缩耗时，压缩耗时长的在前面
                                .thenComparing(TableMetaWrapper::getCompactionDuration, Comparator.reverseOrder()))
                .collect(meta -> {
                    MutableMap<String, String> finalMetadata = metadata
                            .toMap()
                            .withKeyValue(Constants.METRICS_LABEL_FLINK_JOB_ID, meta.getFlinkJobId().toString())
                            .withKeyValue(Constants.METRICS_LABEL_ALIAS, meta.getAlias())
                            .withKeyValue(Constants.METRICS_LABEL_TAGS, meta.getTags());

                    // 统一在这里覆盖特定请求
                    // CRM重点表独占A4集群
                    if (TagsHelper.existsTag(meta.getTags(), Constants.TAGS_CRM_FOCUS)) {
                        finalMetadata.put(Constants.SCHEDULE_RECOMMEND, Constants.CLUSTER_A4);
                    } else {
                        finalMetadata.put(Constants.SCHEDULE_ESCAPE, Constants.CLUSTER_A4);
                    }

                    return new QueueItem<>(
                            StrUtil.format("{}-{}", meta.getFlinkJobId(), meta.getAlias()),
                            finalMetadata,
                            meta.getPriority(),
                            ScheduleJob.builder()
                                    .id(IdUtil.nanoId(10))
                                    .flinkJobId(meta.getFlinkJobId())
                                    .alias(meta.getAlias())
                                    .batch(batchId)
                                    .status(Constants.COMPACTION_STATUS_SCHEDULE)
                                    .comment(comment)
                                    .build()
                    );
                })
                .forEach(item -> {
                    // 将任务放入预处理队列等待处理
                    QueueUtil.add(discoveryClient, mapper, Constants.COMPACTION_QUEUE_PRE, item);
                    if (ObjectUtil.isNotNull(item.getData())) {
                        logger.info(makeMarker(item.getData().getFlinkJobId(), item.getData().getAlias()), "Schedule {}", item);
                    } else {
                        logger.warn("Item data is null, {}", item);
                    }
                });
    }

    private static final class TableMetaWrapper {
        private final SimpleTableMeta meta;
        private final Long compactionDuration;

        private TableMetaWrapper(SimpleTableMeta meta, Long compactionDuration) {
            this.meta = meta;
            this.compactionDuration = compactionDuration;
        }

        public Long getFlinkJobId() {
            return meta.getFlinkJobId();
        }

        public String getAlias() {
            return meta.getAlias();
        }

        public String getTags() {
            return meta.getTags();
        }

        public Integer getBucketNumber() {
            return meta.getBucketNumber();
        }

        public Integer getPriority() {
            return meta.getPriority();
        }

        public Long getCompactionDuration() {
            return compactionDuration;
        }
    }
}
