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.TableMeta;
import com.lanyuanxiaoyao.service.common.entity.compaction.ScheduleJob;
import com.lanyuanxiaoyao.service.common.utils.TableMetaHelper;
import com.lanyuanxiaoyao.service.configuration.ExecutorProvider;
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.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<TableMeta> predicate,
            String comment,
            ImmutableMap<String, String> metadata
    ) {
        String batchId = IdUtil.nanoId(10);
        infoService.tableMetaList()
                // 只调度 MOR 表
                .select(meta -> StrUtil.equals(Constants.MOR, meta.getHudi().getTargetTableType()))
                .asParallel(ExecutorProvider.EXECUTORS, 1)
                .select(predicate::test)
                // 没有 Hudi 表的过滤掉
                .select(meta -> {
                    try {
                        return hudiService.existsHudiTable(meta.getJob().getId(), meta.getAlias());
                    } catch (Throwable throwable) {
                        logger.error(makeMarker(meta.getJob().getId(), meta.getAlias()), "Get hudi status failure", throwable);
                    }
                    return false;
                })
                // 没有压缩计划的过滤掉
                .select(meta -> {
                    try {
                        return hudiService.existsCompactionPlan(meta.getJob().getId(), meta.getAlias());
                    } catch (Throwable throwable) {
                        logger.error(makeMarker(meta.getJob().getId(), meta.getAlias()), "Get compaction status failure", throwable);
                    }
                    return false;
                })
                // 拒绝不压缩标志的任务
                .reject(meta -> TableMetaHelper.existsTag(meta, Constants.TAGS_NO_COMPACT))
                .collect(meta -> {
                    long compactionDuration = 0L;
                    try {
                        // 计算压缩耗时
                        SyncState syncState = infoService.syncStateDetail(meta.getJob().getId(), 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.getJob().getId(), meta.getAlias()), "Get sync state failure for {} {}", meta.getJob().getId(), meta.getAlias());
                    }
                    return new TableMetaWrapper(meta, compactionDuration);
                })
                .toSortedList(
                        Comparator
                                // 比较 Bucket 数，数量大的在前面
                                .comparing(TableMetaWrapper::getBucketIndexNumber, Comparator.reverseOrder())
                                // 比较压缩耗时，压缩耗时长的在前面
                                .thenComparing(TableMetaWrapper::getCompactionDuration, Comparator.reverseOrder()))
                .collect(meta -> new QueueItem<>(
                        StrUtil.format("{}-{}", meta.getFlinkJobId(), meta.getAlias()),
                        metadata.toMap(),
                        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 TableMeta tableMeta;
        private final Long compactionDuration;

        private TableMetaWrapper(TableMeta tableMeta, Long compactionDuration) {
            this.tableMeta = tableMeta;
            this.compactionDuration = compactionDuration;
        }

        public Long getFlinkJobId() {
            return tableMeta.getJob().getId();
        }

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

        public Integer getBucketIndexNumber() {
            return tableMeta.getHudi().getBucketIndexNumber();
        }

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

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