package com.lanyuanxiaoyao.service.web.controller;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.service.common.entity.FlinkJob;
import com.lanyuanxiaoyao.service.common.utils.NameHelper;
import com.lanyuanxiaoyao.service.configuration.ExecutorProvider;
import com.lanyuanxiaoyao.service.configuration.entity.info.JobIdAndAlias;
import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnApplication;
import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnRootQueue;
import com.lanyuanxiaoyao.service.configuration.entity.zookeeper.ZookeeperNode;
import com.lanyuanxiaoyao.service.forest.service.*;
import com.lanyuanxiaoyao.service.web.controller.base.AmisCrudResponse;
import com.lanyuanxiaoyao.service.web.controller.base.AmisMapResponse;
import com.lanyuanxiaoyao.service.web.controller.base.AmisResponse;
import com.lanyuanxiaoyao.service.web.controller.base.BaseController;
import com.lanyuanxiaoyao.service.web.entity.JobIdAndAliasVO;
import com.lanyuanxiaoyao.service.web.entity.ScheduleTimeVO;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 lanyuanxiaoyao
 * @date 2023-06-12
 */
@RestController
@RequestMapping("overview")
public class OverviewController extends BaseController {
    private static final Logger logger = LoggerFactory.getLogger(OverviewController.class);
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
    private final InfoService infoService;
    private final YarnService yarnService;
    private final QueueService queueService;
    private final ScheduleService scheduleService;
    private final ZookeeperService zookeeperService;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public OverviewController(InfoService infoService, YarnService yarnService, QueueService queueService, ScheduleService scheduleService, ZookeeperService zookeeperService) {
        this.infoService = infoService;
        this.yarnService = yarnService;
        this.queueService = queueService;
        this.scheduleService = scheduleService;
        this.zookeeperService = zookeeperService;
    }

    @GetMapping("")
    public AmisMapResponse overview() throws ExecutionException, InterruptedException {
        CompletableFuture<Long> tableCountFuture = CompletableFuture.supplyAsync(infoService::tableCount, ExecutorProvider.EXECUTORS);
        CompletableFuture<Long> tableFocusCountFuture = CompletableFuture.supplyAsync(infoService::tableFocusCount, ExecutorProvider.EXECUTORS);
        CompletableFuture<Long> hudiCountFuture = CompletableFuture.supplyAsync(infoService::hudiCount, ExecutorProvider.EXECUTORS);
        CompletableFuture<Long> hudiFocusCountFuture = CompletableFuture.supplyAsync(infoService::hudiFocusCount, ExecutorProvider.EXECUTORS);
        CompletableFuture<Long> hiveCountFuture = CompletableFuture.supplyAsync(infoService::hiveCount, ExecutorProvider.EXECUTORS);
        CompletableFuture<Long> hiveFocusCountFuture = CompletableFuture.supplyAsync(infoService::hiveFocusCount, ExecutorProvider.EXECUTORS);
        CompletableFuture.allOf(
                tableCountFuture,
                tableFocusCountFuture,
                hudiCountFuture,
                hudiFocusCountFuture,
                hiveCountFuture,
                hiveFocusCountFuture
        ).get();
        return AmisResponse.responseMapData()
                .setData("table_count", tableCountFuture.get())
                .setData("table_focus_count", tableFocusCountFuture.get())
                .setData("hudi_count", hudiCountFuture.get())
                .setData("hudi_focus_count", hudiFocusCountFuture.get())
                .setData("hive_count", hiveCountFuture.get())
                .setData("hive_focus_count", hiveFocusCountFuture.get());
    }

    @GetMapping("yarn-job")
    private AmisMapResponse yarnOverview(@RequestParam("cluster") String cluster, @RequestParam("search") String text) {
        boolean isSearch = StrUtil.isNotBlank(text);
        ImmutableList<YarnApplication> applications = yarnService.jobList(cluster).select(app -> !isSearch || StrUtil.contains(app.getName(), text));
        return AmisResponse.responseMapData()
                .setData("name", cluster)
                .setData("total", applications.size())
                .setData("running", applications.count(app -> StrUtil.equals(app.getState(), "RUNNING")))
                .setData("running", applications.count(app -> StrUtil.equals(app.getState(), "RUNNING")))
                .setData("scheduling", applications.count(app -> StrUtil.equals(app.getState(), "ACCEPTED")))
                .setData("failure", applications.count(app -> StrUtil.equals(app.getState(), "FAILED")));
    }

    @GetMapping("yarn-cluster")
    public AmisMapResponse yarnClusterOverview(@RequestParam("cluster") String cluster, @RequestParam("queue") String queue) {
        AmisMapResponse response = AmisResponse.responseMapData();
        YarnRootQueue root = yarnService.cluster(cluster);
        response.setData("root", root);
        if (StrUtil.isNotBlank(queue)) {
            yarnService.queueList(cluster)
                    .select(q -> StrUtil.equals(q.getQueueName(), queue))
                    .getFirstOptional()
                    .ifPresent(yarnQueue -> response.setData("target", yarnQueue));
        }
        return response;
    }

    @GetMapping("queue")
    public AmisMapResponse queueOverview(@RequestParam("queue") String queue) {
        return AmisResponse.responseMapData("size", queueService.size(queue));
    }

    @GetMapping("version")
    public AmisMapResponse versionOverview() throws ExecutionException, InterruptedException {
        String version = LocalDateTime.now().minusDays(1).format(FORMATTER);
        CompletableFuture<Long> unReceiveNormalTableCount = CompletableFuture.supplyAsync(() -> infoService.unReceiveVersionNormalTableCount(version), ExecutorProvider.EXECUTORS);
        CompletableFuture<Long> unReceiveFocusCount = CompletableFuture.supplyAsync(() -> infoService.unReceiveVersionFocusTableCount(version), ExecutorProvider.EXECUTORS);
        CompletableFuture<Long> unScheduledNormalTableCount = CompletableFuture.supplyAsync(() -> infoService.unScheduledNormalTableCount(version), ExecutorProvider.EXECUTORS);
        CompletableFuture<Long> unScheduledFocusTableCount = CompletableFuture.supplyAsync(() -> infoService.unScheduledFocusTableCount(version), ExecutorProvider.EXECUTORS);
        CompletableFuture.allOf(unReceiveNormalTableCount, unReceiveFocusCount, unScheduledNormalTableCount, unScheduledFocusTableCount).get();
        return AmisResponse.responseMapData()
                .setData("version", version)
                .setData("unReceive", Maps.immutable.of("normal", unReceiveNormalTableCount.get(), "focus", unReceiveFocusCount.get()))
                .setData("unSchedule", Maps.immutable.of("normal", unScheduledNormalTableCount.get(), "focus", unScheduledFocusTableCount.get()));
    }

    @GetMapping("version_detail")
    public AmisCrudResponse versionDetail(String target, String version) throws Exception {
        ImmutableList<JobIdAndAlias> jobIdAndAliases;
        if (StrUtil.equals("unReceive_focus", target)) {
            jobIdAndAliases = infoService.unReceiveVersionFocusTable(version);
        } else if (StrUtil.equals("unScheduled_focus", target)) {
            jobIdAndAliases = infoService.unScheduledFocusTable(version);
        } else if (StrUtil.equals("unReceive_normal", target)) {
            jobIdAndAliases = infoService.unReceiveVersionNormalTable(version);
        } else if (StrUtil.equals("unScheduled_normal", target)) {
            jobIdAndAliases = infoService.unScheduledNormalTable(version);
        } else {
            throw new Exception("Target not found " + target);
        }
        return AmisResponse.responseCrudData(jobIdAndAliases.collect(JobIdAndAliasVO::new));
    }

    @GetMapping("schedule_jobs")
    public AmisCrudResponse scheduleJobs() {
        return AmisResponse.responseCrudData(scheduleService.scheduleJobs());
    }

    @GetMapping("schedule_times")
    public AmisCrudResponse scheduleTimes() {
        long now = Instant.now().toEpochMilli();
        return AmisCrudResponse.responseCrudData(
                scheduleService.scheduleJobTimes()
                        .collect(time -> {
                            if (time < now) {
                                return new ScheduleTimeVO("inactive", time);
                            } else {
                                return new ScheduleTimeVO("active", time);
                            }
                        })
        );
    }

    @GetMapping("sync_running_status")
    public AmisMapResponse syncRunningStatus() {
        ImmutableList<String> locks = zookeeperService.getChildren(NameHelper.ZK_SYNC_RUNNING_LOCK_PATH).collect(ZookeeperNode::getPath);
        MutableList<JobIdAndAlias> runningJob = Lists.mutable.<JobIdAndAlias>empty().asSynchronized();
        MutableList<JobIdAndAlias> unRunningJob = Lists.mutable.<JobIdAndAlias>empty().asSynchronized();
        MutableList<JobIdAndAlias> runningTable = Lists.mutable.<JobIdAndAlias>empty().asSynchronized();
        MutableList<JobIdAndAlias> unRunningTable = Lists.mutable.<JobIdAndAlias>empty().asSynchronized();
        ImmutableList<JobIdAndAlias> idAliases = infoService.allFlinkJobIdAndAlias();
        ImmutableList<Long> ids = idAliases.collect(JobIdAndAlias::getId).reject(ObjectUtil::isNull).distinct();
        ids
                .asParallel(ExecutorProvider.EXECUTORS, 1)
                .forEach(id -> {
                    String lockPath = NameHelper.syncRunningLockPath(id);
                    if (locks.contains(lockPath)) {
                        runningJob.add(new JobIdAndAlias(id, ""));
                    } else {
                        FlinkJob flinkJob = infoService.flinkJobDetail(id);
                        unRunningJob.add(new JobIdAndAlias(id, flinkJob.getName()));
                    }
                });
        idAliases
                .forEach(ia -> {
                    String lockPath = NameHelper.syncRunningLockPath(ia.getId(), ia.getAlias());
                    if (locks.contains(lockPath)) {
                        runningTable.add(ia);
                    } else {
                        unRunningTable.add(ia);
                    }
                });
        return AmisResponse.responseMapData()
                .setData("totalJob", ids.size())
                .setData("runningJob", runningJob.size())
                .setData("runningJobList", runningJob)
                .setData("unRunningJob", unRunningJob.size())
                .setData("unRunningJobList", unRunningJob)
                .setData("totalTable", idAliases.size())
                .setData("runningTable", runningTable.size())
                .setData("runningTableList", runningTable)
                .setData("unRunningTable", unRunningTable.size())
                .setData("unRunningTableList", unRunningTable);
    }
}
