package cn.reghao.bnt.web.devops.builder.tool.repo;

import cn.reghao.bnt.web.devops.builder.model.RepoAuth;
import cn.reghao.bnt.web.devops.builder.model.constant.RepoAuthType;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.*;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * Git 实现
 *
 * @author reghao
 * @date 2019-10-12 22:22:00
 */
public class GitImpl implements CodeUpdater {
    private UsernamePasswordCredentialsProvider credentials;
    private SshSessionFactory sshSessionFactory;

    public GitImpl(RepoAuth repoAuth) {
        switch (RepoAuthType.valueOf(repoAuth.getAuthType())) {
            case http:
                httpAuth(repoAuth.getUsername(), repoAuth.getPassword());
                break;
            case ssh:
                sshAuth(repoAuth.getRsaPrikey());
                break;
            default:
        }
    }

    private void httpAuth(String username, String password) {
        this.credentials = new UsernamePasswordCredentialsProvider(username, password);
    }

    private void sshAuth(String privateKey) {
        this.sshSessionFactory = new CustomSshSessionFactory(privateKey);
    }

    /**
     * 不存在则 clone，存在则 pull
     *
     * @param
     * @return
     * @date 2020-05-13 下午9:22
     */
    @Override
    public CommitInfo pull(String remote, String branch, String local) throws Exception {
        String localRepoDir = local + dirname(remote);
        File file = new File(localRepoDir);
        if (!file.exists() && !file.mkdirs()) {
            throw new Exception("创建 " + localRepoDir + " 目录失败");
        }

        File localRepo = new File(localRepoDir + "/.git");
        CommitInfo commitInfo;
        if (!localRepo.exists()) {
            commitInfo = clone(remote, branch, localRepoDir);
        } else {
            try (Repository repo = new FileRepository(localRepo.getAbsolutePath())) {
                PullCommand gitPull = new Git(repo)
                        .pull()
                        // 超时 60s
                        .setTimeout(60)
                        .setFastForward(MergeCommand.FastForwardMode.FF);
                if (sshSessionFactory != null) {
                    gitPull.setTransportConfigCallback(transport -> {
                        if (transport instanceof SshTransport) {
                            setSshTransport((SshTransport) transport);
                        }
                    });
                    gitPull.call();
                } else if (credentials != null) {
                    gitPull.setCredentialsProvider(credentials);
                    gitPull.call();
                } else {
                    gitPull.call();
                }

                commitInfo = commitInfo(repo, branch);
                commitInfo.setChangedFiles(changedFileList(repo, branch));
            }
        }
        commitInfo.setRemote(remote);
        commitInfo.setBranch(branch);
        return commitInfo;
    }

    private CommitInfo clone(String remote, String branch, String local) throws Exception {
        CloneCommand gitClone;
        if (sshSessionFactory != null) {
            gitClone = Git.cloneRepository()
                    .setTimeout(600)
                    .setURI(remote)
                    .setBranch(branch)
                    .setDirectory(new File(local))
                    .setTransportConfigCallback(transport -> {
                        if (transport instanceof SshTransport) {
                            setSshTransport((SshTransport) transport);
                        }
                    });
        } else if (credentials != null) {
            gitClone = Git.cloneRepository()
                    .setURI(remote)
                    .setBranch(branch)
                    .setDirectory(new File(local))
                    .setCredentialsProvider(credentials);
        } else {
            gitClone = Git.cloneRepository()
                    .setURI(remote)
                    .setBranch(branch)
                    .setDirectory(new File(local));
        }

        try (Git git = gitClone.setDepth(1).call()) {
            Repository repo = git.getRepository();
            CommitInfo commitInfo = commitInfo(repo, branch);
            commitInfo.setChangedFiles(changedFileList(repo, branch));
            return commitInfo;
        }
    }

    private void setSshTransport(SshTransport sshTransport) {
        // Set passphrase using a CustomCredentialsProvider
        sshTransport.setCredentialsProvider(new CustomCredentialProvider(null));
        sshTransport.setSshSessionFactory(sshSessionFactory);
    }

    private CommitInfo commitInfo(Repository repo, String branch) throws Exception {
        Ref head = repo.findRef("refs/heads/" + branch);
        if (head == null) {
            String msg = String.format("%s 分支不存在", branch);
            throw new Exception(msg);
        }

        ObjectId objectId = head.getObjectId();
        try (RevWalk walk = new RevWalk(repo)) {
            // 最近一次提交的信息
            RevCommit lastCommit = walk.parseCommit(objectId);
            long commitTime = lastCommit.getCommitTime()*1000L;
            String commitAuthor = lastCommit.getAuthorIdent().getName();
            String commitMsg = lastCommit.getFullMessage();

            CommitInfo commitInfo = new CommitInfo();
            commitInfo.setCommitId(objectId.name().substring(0, 8));
            commitInfo.setMsCommitTime(commitTime);
            commitInfo.setCommitAuthor(commitAuthor);
            if (commitMsg.length() > 1000) {
                commitInfo.setCommitMsg(commitMsg.substring(0, 1000));
            } else {
                commitInfo.setCommitMsg(commitMsg);
            }
            return commitInfo;
        }
    }

    private List<ChangedFile> changedFileList(Repository repo, String branch) throws Exception {
        Ref headMaster = repo.findRef("refs/heads/" + branch);
        ObjectId objectId = headMaster.getObjectId();
        try (RevWalk walk = new RevWalk(repo)) {
            // 最近一次提交
            RevCommit newCommit = walk.parseCommit(objectId);
            walk.markStart(newCommit);
            // 遍历两次找到 newCommit 的前一次提交，newCommit 不是第一次提交
            walk.next();
            RevCommit oldCommit = walk.next();
            if (oldCommit == null) {
                // TODO 仓库中只有一个 commit 时(第一次提交)的处理
                return Collections.emptyList();
            }

            ObjectId newHead = newCommit.getTree().getId();
            ObjectId oldHead = oldCommit.getTree().getId();

            try (ObjectReader reader = repo.newObjectReader()) {
                CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
                newTreeIter.reset(reader, newHead);
                CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
                oldTreeIter.reset(reader, oldHead);
                try (Git git = new Git(repo)) {
                    List<DiffEntry> diffs= git.diff()
                            .setNewTree(newTreeIter)
                            .setOldTree(oldTreeIter)
                            .call();

                    List<ChangedFile> changedFiles = new ArrayList<>();
                    for (DiffEntry entry : diffs) {
                        ChangedFile changedFile = new ChangedFile();
                        changedFile.setChangeType(entry.getChangeType().name());
                        changedFile.setOldFilePath(entry.getOldPath());
                        changedFile.setNewFilePath(entry.getNewPath());
                        changedFiles.add(changedFile);
                    }

                    return changedFiles;
                }
            }
        }
    }

    @Override
    public CommitInfo update(String remote, String branch, String local, String commitId) throws Exception {
        // TODO 待实现
        return null;
    }

    @Override
    public CommitInfo latestCommitInfo(String local, String branch) throws Exception {
        File localRepo = new File(local + "/.git");
        if (!localRepo.exists()) {
            return null;
        }

        try (Repository repo = new FileRepository(localRepo.getAbsolutePath())) {
            return commitInfo(repo, branch);
        }
    }

    private String dirname(String gitUrl) {
        int lastIndex = gitUrl.lastIndexOf("/");
        return gitUrl.substring(lastIndex).split(".git")[0];
    }

    public List<String> remoteBranches(String remote) {
        List<String> branches = new ArrayList<>();
        try {
            Collection<Ref> refList = Git.lsRemoteRepository().setRemote(remote)
                    .setCredentialsProvider(credentials).call();
            for (Ref ref : refList) {
                String refName = ref.getName();
                if (refName.startsWith("refs/heads/")) {
                    // 需要进行筛选
                    String branchName = refName.replace("refs/heads/", "");
                    branches.add(branchName);
                }
            }
            return branches;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return branches;
    }

    /**
     * 提交代码并 push 到远程仓库
     *
     * @param
     * @return
     * @date 2022-08-03 下午2:56
     */
    public void commitAndPush(String local, String commitMsg) throws Exception {
        Repository repository = new FileRepositoryBuilder().setGitDir(new File(local + "/.git")).build();
        Git git = new Git(repository);
        RemoteListCommand remoteListCommand = git.remoteList();
        List<RemoteConfig> list = remoteListCommand.call();
        if (list.isEmpty()) {
            throw new Exception("RemoteConfig 不存在");
        }

        RemoteConfig remoteConfig = list.get(0);
        List<URIish> urIishList = remoteConfig.getURIs();
        if (urIishList.isEmpty()) {
            throw new Exception("远程仓库 URL 不存在");
        }

        URIish urIish = urIishList.get(0);
        String pushUrl = urIish.toString();

        AddCommand addCommand = git.add();
        addCommand.addFilepattern(".").call();

        CommitCommand commitCommand = git.commit();
        commitCommand.setCommitter("reghao", "reghaodev@gmail.com");
        commitCommand.setAuthor("reghao", "reghaodev@gmail.com");
        commitCommand.setAll(true);

        // git commit -m "commit message"
        RevCommit revCommit = commitCommand.setMessage(commitMsg).call();
        PushCommand pushCommand = git.push();
        pushCommand.setRemote(pushUrl).setCredentialsProvider(credentials).call();
    }
}
