package com.lanyuanxiaoyao.service.scheduler.controller;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.cronutils.model.Cron;
import com.cronutils.model.CronType;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.lanyuanxiaoyao.service.common.Constants;
import com.lanyuanxiaoyao.service.configuration.utils.QueueUtil;
import com.lanyuanxiaoyao.service.forest.service.HudiService;
import com.lanyuanxiaoyao.service.forest.service.InfoService;
import com.lanyuanxiaoyao.service.forest.service.launcher.LaunchersService;
import com.lanyuanxiaoyao.service.scheduler.quartz.compaction.DailyScheduleJob;
import com.lanyuanxiaoyao.service.scheduler.quartz.compaction.FocusScheduleJob;
import com.lanyuanxiaoyao.service.scheduler.quartz.compaction.FocusUnVersionUpdateScheduleJob;
import com.lanyuanxiaoyao.service.scheduler.strategy.ScheduleStrategyVO;
import com.lanyuanxiaoyao.service.scheduler.utils.ScheduleHelper;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
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.MutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 调度器对外接口
 *
 * @author ZhangJiacheng
 * @date 2023-05-12
 */
@RestController
@RequestMapping("schedule")
public class ScheduleController {
    private static final Logger logger = LoggerFactory.getLogger(ScheduleController.class);

    private final DiscoveryClient discoveryClient;
    private final InfoService infoService;
    private final HudiService hudiService;
    private final ImmutableList<ScheduleStrategyVO> strategyVOS;
    private final LaunchersService launchersService;
    private final ObjectMapper mapper;
    private final LoadingCache<String, ImmutableList<ZonedDateTime>> scheduleJobTimeCache = Caffeine.newBuilder()
            .expireAfterWrite(Duration.of(10, ChronoUnit.MINUTES))
            .build(cronExpression -> {
                MutableList<ZonedDateTime> times = Lists.mutable.empty();
                CronParser cronParser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
                ZonedDateTime now = ZonedDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
                ZonedDateTime night = now.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
                Cron cron = cronParser.parse(cronExpression);
                ExecutionTime executionTime = ExecutionTime.forCron(cron);
                Optional<ZonedDateTime> zonedDateTime = executionTime.nextExecution(now);
                while (zonedDateTime.isPresent() && zonedDateTime.get().isBefore(night)) {
                    times.add(zonedDateTime.get());
                    zonedDateTime = executionTime.nextExecution(zonedDateTime.get());
                }
                return times.toImmutable();
            });

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public ScheduleController(DiscoveryClient discoveryClient, InfoService infoService, HudiService hudiService, ImmutableList<ScheduleStrategyVO> strategyVOS, LaunchersService launchersService, Jackson2ObjectMapperBuilder builder) {
        this.discoveryClient = discoveryClient;
        this.infoService = infoService;
        this.hudiService = hudiService;
        this.strategyVOS = strategyVOS;
        this.launchersService = launchersService;

        this.mapper = builder.build();
    }

    @GetMapping("schedule_jobs")
    public ImmutableList<ScheduleStrategyVO> scheduleJobs() {
        return strategyVOS;
    }

    @GetMapping("schedule_times")
    public ImmutableList<Long> scheduleJobTimes() {
        /*return strategies
                .select(ScheduleStrategy::enable)
                .select(ScheduleStrategy::show)
                .select(strategy -> (strategy.trigger()) instanceof CronTrigger)
                .flatCollect(strategy -> scheduleJobTimeCache.get(((CronTrigger) strategy.trigger()).getCronExpression()))
                .toSortedList(ZonedDateTime::compareTo)
                .collect(time -> time.toInstant().toEpochMilli())
                .toImmutable();*/
        return Lists.immutable.empty();
    }

    @GetMapping("all")
    public void scheduleAllTable() {
        DailyScheduleJob.schedule(discoveryClient, infoService, hudiService, mapper, "Daily schedule manually");
    }

    @GetMapping("all_focus")
    public void scheduleAllFocus() {
        FocusScheduleJob.schedule(discoveryClient, infoService, hudiService, mapper, "Focus schedule manually");
    }

    @GetMapping("all_un_scheduled")
    public void scheduleAllUnScheduledTable() {
        FocusUnVersionUpdateScheduleJob.schedule(discoveryClient, infoService, hudiService, mapper, "Un-updated version schedule manually");
    }

    @GetMapping("table")
    public void scheduleTable(
            @RequestParam("flink_job_id") Long flinkJobId,
            @RequestParam("alias") String alias,
            @RequestParam(value = "force", required = false) String forceCluster,
            @RequestParam(value = "recommend", required = false) String recommendCluster
    ) {
        MutableMap<String, String> metadata = Maps.mutable.empty();
        if (StrUtil.isNotBlank(forceCluster)) {
            metadata.put(Constants.SCHEDULE_FORCE, forceCluster);
        }
        if (StrUtil.isNotBlank(recommendCluster)) {
            metadata.put(Constants.SCHEDULE_RECOMMEND, recommendCluster);
        }
        ScheduleHelper.schedule(
                discoveryClient,
                infoService,
                hudiService,
                mapper,
                meta -> StrUtil.equals(meta.getAlias(), alias) && NumberUtil.equals(meta.getJob().getId(), flinkJobId),
                "Schedule manually",
                metadata.toImmutable()
        );
    }

    @GetMapping("stop_all")
    public void stopTheWorld(
            @RequestParam("flink_job_id") Long flinkJobId,
            @RequestParam("alias") String alias,
            @RequestParam(value = "disable_meta", defaultValue = "true") Boolean disableMeta
    ) {
        QueueUtil.remove(discoveryClient, Constants.COMPACTION_QUEUE_PRE, StrUtil.format("{}-{}", flinkJobId, alias));
        launchersService.compactionStop(flinkJobId, alias, disableMeta);
        // 再删除一次，避免时间差导致任务重新放回预调度队列
        QueueUtil.remove(discoveryClient, Constants.COMPACTION_QUEUE_PRE, StrUtil.format("{}-{}", flinkJobId, alias));
    }
}
