package com.lanyuanxiaoyao.service.cli.runner;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.Template;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.TemplateUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.service.cli.core.RuntimeInfo;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
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.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 *
 * @author lanyuanxiaoyao
 * @date 2023-05-17
 */
@SpringBootApplication
public class RunnerApplication implements ApplicationRunner {
    private static final Logger logger = LoggerFactory.getLogger(RunnerApplication.class);
    private static final TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
    private final DeployInformationProperties deployInformationProperties;
    private final RuntimeInfo runtimeInfo;
    private final ImmutableList<ServiceInfoWrapper> serviceInfoList;
    private final ImmutableList<HostInfoWrapper> hostInfoList;
    private final ObjectMapper mapper;
    @Value("${deploy.runtime.executor.task-jar-path}")
    private String taskJarPath;

    public RunnerApplication(DeployInformationProperties deployInformationProperties) {
        this.deployInformationProperties = deployInformationProperties;
        runtimeInfo = deployInformationProperties.getRuntime();
        serviceInfoList = Lists.immutable.ofAll(deployInformationProperties.getServices().entrySet())
                .collect(entry -> new ServiceInfoWrapper(entry.getKey(), entry.getValue()))
                .select(ServiceInfoWrapper::getEnabled)
                .toSortedListBy(ServiceInfoWrapper::getOrder)
                .toImmutable();
        hostInfoList = Lists.immutable.ofAll(deployInformationProperties.getHosts().entrySet())
                .collect(entry -> new HostInfoWrapper(entry.getKey(), entry.getValue()))
                .toSortedListBy(HostInfoWrapper::getHost)
                .toImmutable();
        mapper = new ObjectMapper();
    }

    public static void main(String[] args) {
        SpringApplication.run(RunnerApplication.class, args);
    }

    private static void generateTemplate(String templatePath, Map<?, ?> data, Path targetScriptPath) throws IOException {
        Template template = engine.getTemplate(templatePath);
        String script = template.render(data);
        Files.deleteIfExists(targetScriptPath);
        Files.write(targetScriptPath, script.getBytes());
    }

    private List<String> selectHosts(ServiceInfoWrapper serviceInfo) {
        return serviceInfo.getReplicas() == 0
                ? hostInfoList
                .stream()
                .filter(HostInfoWrapper::getEnabled)
                .map(HostInfoWrapper::getIp)
                .sorted(Comparator.naturalOrder())
                .collect(Collectors.toList())
                : ListUtil.sort(
                RandomUtil.randomEleList(
                        hostInfoList
                                .stream()
                                .filter(HostInfoWrapper::getEnabled)
                                .map(HostInfoWrapper::getIp)
                                .collect(Collectors.toList()
                                ), serviceInfo.getReplicas()
                ), Comparator.naturalOrder());
    }

    @Override
    public void run(ApplicationArguments args) throws IOException {
        logger.info("Loading runtime info: {}", mapper.writeValueAsString(runtimeInfo));

        if (deployInformationProperties.getGenerate().getCheck())
            generateCheck(Paths.get("check"));
        if (deployInformationProperties.getGenerate().getCloud())
            generateCloud(Paths.get("cloud"));
        if (deployInformationProperties.getGenerate().getCommand())
            generateCommand(Paths.get("command"));
        if (deployInformationProperties.getGenerate().getCommandPro())
            generateCommand(Paths.get("command-pro"));
        if (deployInformationProperties.getGenerate().getUploader())
            generateUploader(Paths.get("uploader"));
        if (deployInformationProperties.getGenerate().getUpdateJar())
            generateUpdateJar(Paths.get(""));
    }

    private void generateCheck(Path root) throws IOException {
        Files.createDirectories(root);
        String absolutRootPath = root.toAbsolutePath().toString();
        logger.info("Current path: {}", absolutRootPath);

        generateTemplate(
                "check/check.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .build(),
                Paths.get(root.toString(), "check.sh")
        );
    }

    private void generateCloud(Path root) throws IOException {
        Files.createDirectories(root);
        String absolutRootPath = root.toAbsolutePath().toString();
        logger.info("Current path: {}", absolutRootPath);

        Path planPath = Paths.get(root.toString(), "deploy.plan");
        Map<String, List<String>> deployPlans = new HashMap<>();
        if (Files.exists(planPath) && !deployInformationProperties.getShuffler()) {
            deployPlans = mapper.readValue(new String(Files.readAllBytes(planPath)), new TypeReference<Map<String, List<String>>>() {
            });
        }

        for (ServiceInfoWrapper serviceInfo : serviceInfoList) {
            logger.info("Generate script for {}", serviceInfo.getName());
            List<String> selectedHosts;
            if (deployPlans.containsKey(serviceInfo.getName())) {
                selectedHosts = deployPlans.get(serviceInfo.getName());
                if (ObjectUtil.notEqual(selectedHosts.size(), serviceInfo.getReplicas())) {
                    selectedHosts = selectHosts(serviceInfo);
                    deployPlans.put(serviceInfo.getName(), selectedHosts);
                }
                // 排除不可用的主机
                List<String> validIps = hostInfoList.stream()
                        .filter(HostInfoWrapper::getEnabled)
                        .map(HostInfoWrapper::getIp)
                        .collect(Collectors.toList());
                selectedHosts = selectedHosts
                        .stream()
                        .filter(validIps::contains)
                        .collect(Collectors.toList());
            } else {
                selectedHosts = selectHosts(serviceInfo);
                deployPlans.put(serviceInfo.getName(), selectedHosts);
            }
            generateTemplate(
                    "cloud/deploy.ftl",
                    MapUtil.builder()
                            .put("currentPath", absolutRootPath)
                            .put("hosts", hostInfoList
                                    .collect(HostInfoWrapper::getHostnameIp)
                                    .toSortedList((o1, o2) -> Comparator.<String>naturalOrder().compare(o1.getIp(), o2.getIp())))
                            .put("selectedHosts", selectedHosts)
                            .put("runtime", runtimeInfo)
                            .put("info", serviceInfo)
                            .put("classpath", String.join(":", serviceInfo.getClasspath()))
                            .put("arguments", serviceInfo.getArguments())
                            .put("environments", serviceInfo.getEnvironments())
                            .build(),
                    Paths.get(
                            root.toString(),
                            StrUtil.format("deploy-{}.sh", serviceInfo.getName())
                    )
            );
            generateTemplate(
                    "cloud/stop.ftl",
                    MapUtil.builder()
                            .put("currentPath", absolutRootPath)
                            .put("hosts", hostInfoList
                                    .collect(HostInfoWrapper::getHostnameIp)
                                    .toSortedList((o1, o2) -> Comparator.<String>naturalOrder().compare(o1.getIp(), o2.getIp())))
                            .put("runtime", runtimeInfo)
                            .put("info", serviceInfo)
                            .put("arguments", serviceInfo.getArguments())
                            .put("environments", serviceInfo.getEnvironments())
                            .build(),
                    Paths.get(
                            root.toString(),
                            StrUtil.format("stop-{}.sh", serviceInfo.getName())
                    )
            );
            generateTemplate(
                    "cloud/log.ftl",
                    MapUtil.builder()
                            .put("currentPath", absolutRootPath)
                            .put("hosts", hostInfoList
                                    .collect(HostInfoWrapper::getHostnameIp)
                                    .toSortedList((o1, o2) -> Comparator.<String>naturalOrder().compare(o1.getIp(), o2.getIp())))
                            .put("selectedHosts", selectedHosts)
                            .put("runtime", runtimeInfo)
                            .put("info", serviceInfo)
                            .put("arguments", serviceInfo.getArguments())
                            .put("environments", serviceInfo.getEnvironments())
                            .build(),
                    Paths.get(
                            root.toString(),
                            StrUtil.format("log-{}.sh", serviceInfo.getName())
                    )
            );
        }

        generateTemplate(
                "cloud/ssh-script.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("hosts", hostInfoList
                                .collect(HostInfoWrapper::getHostnameIp)
                                .toSortedList((o1, o2) -> Comparator.<String>naturalOrder().compare(o1.getIp(), o2.getIp())))
                        .put("runtime", runtimeInfo)
                        .build(),
                Paths.get(root.toString(), "ssh.sh")
        );

        MutableMap<String, MutableList<ServiceInfoWrapper>> groups = Maps.mutable.empty();
        for (ServiceInfoWrapper service : serviceInfoList) {
            service.getGroups().add(0, "all");
            for (String group : service.getGroups()) {
                if (!groups.containsKey(group)) {
                    groups.put(group, Lists.mutable.empty());
                }
                List<ServiceInfoWrapper> list = groups.get(group);
                list.add(service);
            }
        }
        for (Map.Entry<String, MutableList<ServiceInfoWrapper>> entry : groups.entrySet()) {
            String group = entry.getKey();
            MutableList<ServiceInfoWrapper> infos = entry.getValue();

            generateTemplate(
                    "cloud/batch-deploy.ftl",
                    MapUtil.builder()
                            .put("currentPath", absolutRootPath)
                            .put("services", infos.collect(ServiceInfoWrapper::getName))
                            .build(),
                    Paths.get(
                            root.toString(),
                            StrUtil.format("batch-deploy-{}.sh", group)
                    )
            );
            generateTemplate(
                    "cloud/batch-stop.ftl",
                    MapUtil.builder()
                            .put("currentPath", absolutRootPath)
                            .put("services", infos.collect(ServiceInfoWrapper::getName))
                            .build(),
                    Paths.get(
                            root.toString(),
                            StrUtil.format("batch-stop-{}.sh", group)
                    )
            );
        }

        Files.deleteIfExists(planPath);
        Files.write(planPath, mapper.writeValueAsString(deployPlans).getBytes());
    }

    private void generateCommand(Path root) throws IOException {
        Files.createDirectories(root);
        String absolutRootPath = root.toAbsolutePath().toString();
        logger.info("Current path: {}", absolutRootPath);

        generateTemplate(
                root.toFile().getName() + "/cli.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .put("directly", false)
                        .build(),
                Paths.get(root.toString(), "cli")
        );
        generateTemplate(
                root.toFile().getName() + "/cli.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .put("directly", true)
                        .build(),
                Paths.get(root.toString(), "cli_d")
        );
        generateTemplate(
                root.toFile().getName() + "/update.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .build(),
                Paths.get(root.toString(), "update.sh")
        );
    }

    private void generateUploader(Path root) throws IOException {
        Files.createDirectories(root);
        String absolutRootPath = root.toAbsolutePath().toString();
        logger.info("Current path: {}", absolutRootPath);

        generateTemplate(
                "uploader/start.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .build(),
                Paths.get(root.toString(), "start.sh")
        );
        generateTemplate(
                "uploader/update.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .build(),
                Paths.get(root.toString(), "update.sh")
        );

        generateTemplate(
                "uploader/stop.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .build(),
                Paths.get(root.toString(), "stop.sh")
        );
    }

    private void generateUpdateJar(Path root) throws IOException {
        String absolutRootPath = root.toAbsolutePath().toString();
        logger.info("Current path: {}", absolutRootPath);

        generateTemplate(
                "update-jar.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .put("jarPrefix", "sync")
                        .put("jarName", "sync-1.0.0-SNAPSHOT.jar")
                        .put("uploadPath", runtimeInfo.getHudi().getAppHdfsPath())
                        .build(),
                Paths.get(root.toString(), "update-sync.sh")
        );
        generateTemplate(
                "update-jar.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .put("jarPrefix", "sync")
                        .put("jarName", "sync-1.0.0-SNAPSHOT.jar")
                        .put("uploadPath", runtimeInfo.getHudi().getAppTestHdfsPath())
                        .build(),
                Paths.get(root.toString(), "update-test.sh")
        );
        generateTemplate(
                "update-jar.ftl",
                MapUtil.builder()
                        .put("currentPath", absolutRootPath)
                        .put("runtime", runtimeInfo)
                        .put("jarPrefix", "task")
                        .put("jarName", "service-executor-task-1.0.0-SNAPSHOT.jar")
                        .put("uploadPath", taskJarPath)
                        .build(),
                Paths.get(root.toString(), "update-task.sh")
        );
    }
}
