package cn.reghao.autodop.dmaster.app.service.bd;

import cn.reghao.autodop.common.msg.rpc.dto.app.PackType;
import cn.reghao.autodop.dmaster.app.model.constant.build.CompileType;
import cn.reghao.autodop.dmaster.app.model.constant.build.RepoType;
import cn.reghao.autodop.dmaster.app.model.po.config.build.CompilerConfig;
import cn.reghao.autodop.dmaster.app.model.po.config.build.PackerConfig;
import cn.reghao.autodop.dmaster.app.model.po.config.build.RepoAuthConfig;
import cn.reghao.autodop.dmaster.app.model.po.config.AppConfig;
import cn.reghao.autodop.dmaster.app.service.bd.tools.compiler.CodeCompiler;
import cn.reghao.autodop.dmaster.app.service.bd.tools.compiler.MavenCompiler;
import cn.reghao.autodop.dmaster.app.service.bd.tools.compiler.NullCompiler;
import cn.reghao.autodop.dmaster.app.service.bd.tools.compiler.ShellCompiler;
import cn.reghao.autodop.dmaster.app.service.bd.tools.packer.CodePacker;
import cn.reghao.autodop.dmaster.app.service.bd.tools.packer.DockerPack;
import cn.reghao.autodop.dmaster.app.service.bd.tools.packer.ZipPack;
import cn.reghao.autodop.dmaster.app.service.bd.tools.repo.CodeUpdater;
import cn.reghao.autodop.dmaster.app.service.bd.tools.repo.CommitInfo;
import cn.reghao.autodop.dmaster.app.service.bd.tools.repo.GitImpl;
import cn.reghao.autodop.dmaster.app.service.bd.tools.repo.SvnImpl;
import cn.reghao.autodop.dmaster.app.model.po.config.build.LocalBuildDir;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * 应用构建部署，每个应用持有一个对象
 *
 * @author reghao
 * @date 2019-10-09 10:54:43
 */
@Slf4j
public class AppIntegrate {
    private String appLocalRepo;
    private String appCompileDir;
    // TODO 应用二进制文件存储到本地时使用
    private String appPackDir;

    private AppConfig app;
    // 远程仓库最近一次提交的信息
    private CommitInfo latestCommitInfo;

    private CodeUpdater codeUpdater;
    private CodeCompiler codeCompiler;
    private CodePacker codePacker;

    public AppIntegrate(AppConfig app) {
        this.app = app;
        init();
    }

    public AppConfig app() {
        return app;
    }

    /**
     * TODO 若 AppOrchestration 发生变化则重新初始化
     *
     * @param
     * @return
     * @date 2020-08-06 上午10:39
     */
    public void reInit(AppConfig app) {
        this.app = app;
        init();
    }

    private void init() {
        initLocalDir();
        initBuildConfig();
    }

    private void initLocalDir() {
        appLocalRepo = LocalBuildDir.localRepo + "/" + app.getAppId();
        appCompileDir = LocalBuildDir.compileDir + "/" + app.getAppId();
        appPackDir = LocalBuildDir.packDir + "/" + app.getAppId();
    }

    /**
     * 初始化应用构建配置
     *
     * @param
     * @return
     * @date 2021-02-05 下午11:01
     */
    private void initBuildConfig() {
        // 初始化更新配置
        RepoAuthConfig repoAuthConfig = app.getRepoAuthConfig();
        switch (RepoType.valueOf(repoAuthConfig.getType())) {
            case git:
                codeUpdater = new GitImpl(repoAuthConfig);
                break;
            case svn:
                codeUpdater = new SvnImpl(repoAuthConfig);
                break;
            default:
        }

        // 初始化编译配置
        CompilerConfig compilerConfig = app.getCompilerConfig();
        switch (CompileType.valueOf(compilerConfig.getType())) {
            case shell:
                codeCompiler = new ShellCompiler(compilerConfig);
                break;
            case maven:
                codeCompiler = new MavenCompiler(compilerConfig.getHomePath(), app.getEnv(), app.getCodeDirname());
                break;
            case docker:
                codeCompiler = new NullCompiler();
                break;
            case no_need_compile:
                codeCompiler = new NullCompiler();
                break;
            default:
                log.info("暂未实现，使用 NullCompiler");
                codeCompiler = new NullCompiler();
        }

        // 初始化打包配置
        PackerConfig packerConfig = app.getPackerConfig();
        switch (PackType.valueOf(packerConfig.getType())) {
            case docker:
                codePacker = new DockerPack(packerConfig, app.getPackScript());
                break;
            case zip:
                codePacker = new ZipPack(packerConfig);
                break;
            default:
        }
    }

    public CommitInfo update() throws Exception {
        String remote;
        String branch;
        // TODO 区分 GIT 和 SVN pull 代码时的不同
        String local = appLocalRepo;
        remote = app.getAppRepo();
        branch = app.getRepoBranch();
        if (latestCommitInfo == null) {
            latestCommitInfo = codeUpdater.latestCommitInfo(local, branch);
        }

        UpdateStatus updateStatus = update0(remote, branch, local);
        if (updateStatus.isUpdated) {
            latestCommitInfo = updateStatus.getLatestCommitInfo();
            // 将代码由本地仓库复制到编译目录
            BuilderUtil.copyToCompileDir(local, app);
        }

        return latestCommitInfo;
    }

    private UpdateStatus update0(String remote, String branch, String local) throws Exception {
        CommitInfo current = codeUpdater.update(remote, branch, local);
        if (latestCommitInfo != null) {
            if (latestCommitInfo.getCommitId().equals(current.getCommitId())) {
                return new UpdateStatus(current, false);
            } else if (latestCommitInfo.getMsCommitTime() > current.getMsCommitTime()) {
                throw new Exception("本地仓库代码比远程仓库更新");
            }
        }

        // TODO 拉取应用依赖的其他仓库(若存在)
        return new UpdateStatus(current, true);
    }

    public void compile() throws Exception {
        // TODO 通过脚本调用编译器时会卡住
        codeCompiler.compile(app.getAppId(), compileDir());
    }

    public String pack() throws Exception {
        return codePacker.pack(app.getAppId(), latestCommitInfo.getCommitId(), compileDir());
    }

    private String compileDir() {
        return "/".equals(app.getCompileHome()) ? appCompileDir : appCompileDir + app.getCompileHome();
    }

    @Data
    @AllArgsConstructor
    static class UpdateStatus {
        private CommitInfo latestCommitInfo;
        private boolean isUpdated;
    }
}
