package com.ruoyi.platform.utils;

import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Component
public class DVCUtils {
    private static final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private static final Logger log = LoggerFactory.getLogger(DVCUtils.class);
    private static final ExecutorCompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
    @Value("${proxy.useProxy:false}")
    private boolean useProxy;

    @Value("${proxy.host}")
    private String host;

    @Value("${proxy.port}")
    private Integer port;

    private class ProxyConfigCallback implements TransportConfigCallback {
        @Override
        public void configure(Transport transport) {
            if (useProxy) {
                System.setProperty("http.proxyHost", host);
                System.setProperty("http.proxyPort", String.valueOf(port));
                System.setProperty("https.proxyHost", host);
                System.setProperty("https.proxyPort", String.valueOf(port));
                log.info("Proxy configured: {}:{}", host, port);
            }
        }
    }

    private static void runCommand(String command, String workingDir) throws Exception {
        ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
        processBuilder.directory(new File(workingDir));
        Process process = processBuilder.start();
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;
        while ((line = reader.readLine()) != null) {
            log.info(line);
        }
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new Exception("Command failed with exit code " + exitCode);
        }
    }

    public void moveFiles(String sourcePath, String targetPath) throws Exception {
        Path sourceDir = Paths.get(sourcePath);
        Path targetDir = Paths.get(targetPath);

        if (!Files.exists(targetDir)) {
            Files.createDirectories(targetDir);
        }

        Files.move(sourceDir, targetDir, StandardCopyOption.REPLACE_EXISTING);
    }

    public void deleteDirectory(String dirPath) throws IOException {
        Path directory = Paths.get(dirPath);

        // 检查是否目录存在
        if (Files.exists(directory)) {
            // 使用Files.walk来删除目录及其内容
            Files.walk(directory)
                    .filter(path -> !path.equals(directory))
                    .sorted(Comparator.reverseOrder()) // 先删除子文件夹，再删除父文件夹
                    .forEach(this::deletePath);
        }
    }

    public void deletePath(Path path) {
        try {
            Files.deleteIfExists(path);
        } catch (IOException e) {
            log.error("Unable to delete: " + path + " " + e.getMessage());
        }
    }

    public void gitClone(String localPath, String repoUrl, String branch, String username, String password) throws GitAPIException {
        CloneCommand cloneCommand = Git.cloneRepository()
                .setURI(repoUrl)
                .setBranch(branch)
                .setDirectory(new java.io.File(localPath))
                .setTransportConfigCallback(new ProxyConfigCallback())
                .setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password));

        cloneCommand.call();
    }

    public void gitClone(String localPath, String repoUrl, String username, String password) throws GitAPIException {
        CloneCommand cloneCommand = Git.cloneRepository()
                .setURI(repoUrl)
                .setDirectory(new java.io.File(localPath))
                .setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password));

        cloneCommand.call();
    }


    public void gitAdd(String localPath, String filePath) throws IOException, GitAPIException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        Repository repository = builder.setGitDir(new File(localPath, ".git"))
                .readEnvironment()
                .findGitDir()
                .build();

        try (Git git = new Git(repository)) {
            AddCommand addCommand = git.add();
            addCommand.addFilepattern(filePath).call();
        }
    }

    public void gitCommit(String localPath, String commitMessage) throws IOException, GitAPIException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        Repository repository = builder.setGitDir(new File(localPath, ".git"))
                .readEnvironment()
                .findGitDir()
                .build();

        try (Git git = new Git(repository)) {
            // 添加所有文件
            AddCommand addCommand = git.add();
            addCommand.addFilepattern(".").call();

            // 提交更改
            CommitCommand commitCommand = git.commit();
            commitCommand.setMessage(commitMessage).call();
        }
    }

    public void gitPush(String localPath, String username, String password) throws IOException, GitAPIException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        Repository repository = builder.setGitDir(new File(localPath, ".git"))
                .readEnvironment()
                .findGitDir()
                .build();

        try (Git git = new Git(repository)) {
            CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);
            PushCommand pushCommand = git.push();
            pushCommand.setCredentialsProvider(credentialsProvider)
                    .setTransportConfigCallback(new ProxyConfigCallback())
                    .setForce(true)
                    .call();
        }
    }


    /**
     * 更新本地仓库中的所有分支
     *
     * @param localPath 本地仓库路径
     * @param username  远程仓库用户名
     * @param password  远程仓库密码
     */
    public void updateAllBranches(String localPath, String username, String password) {
        try (Git git = Git.open(new File(localPath))) {
            // 设置凭证，用于远程仓库的认证
            UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);

            // 获取远程分支列表
            List<Ref> remoteBranches = git.branchList().setListMode(org.eclipse.jgit.api.ListBranchCommand.ListMode.REMOTE).call();

            // 遍历远程分支并更新本地对应的分支
            for (Ref remoteBranch : remoteBranches) {
                // 获取分支名称，去掉前缀 "refs/remotes/origin/"
                String branchName = remoteBranch.getName().replace("refs/remotes/origin/", "");
                log.info("Updating branch: " + branchName);

                // 检查本地是否存在该分支，如果不存在则创建
                if (!branchExistsLocally(git, branchName)) {
                    git.branchCreate().setName(branchName).setStartPoint("origin/" + branchName).call();
                }

                // 切换到对应的分支
                git.checkout().setName(branchName).call();

                // 拉取远程分支的最新内容
                PullCommand pull = git.pull().setRemoteBranchName(branchName).setCredentialsProvider(credentialsProvider);
                pull.call();
            }

            log.info("All branches updated successfully.");

        } catch (IOException | GitAPIException e) {
            log.error("Error occurred while updating all branches", e);
        }
    }

    /**
     * 更新本地仓库中的指定分支
     *
     * @param localPath  本地仓库路径
     * @param username   远程仓库用户名
     * @param password   远程仓库密码
     * @param branchName 需要更新的分支名称
     */
    public void updateBranch(String localPath, String username, String password, String branchName) {
        try (Git git = Git.open(new File(localPath))) {
            // 设置凭证，用于远程仓库的认证
            UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);

            // 检查本地是否存在该分支，如果不存在则创建
            if (!branchExistsLocally(git, branchName)) {
                git.branchCreate().setName(branchName).setStartPoint("origin/" + branchName).call();
            }

            // 切换到对应的分支
            git.checkout().setName(branchName).call();

            // 拉取远程分支的最新内容
            PullCommand pull = git.pull().setRemoteBranchName(branchName).setCredentialsProvider(credentialsProvider);
            pull.call();

            log.info("Branch " + branchName + " updated successfully.");

        } catch (IOException | GitAPIException e) {
            log.error("Error occurred while updating branche", e);
        }
    }


    /**
     * 创建本地分支
     *
     * @param localPath  本地仓库路径
     * @param branchName 要创建的分支名称
     */
    public void createLocalBranch(String localPath, String branchName) {
        try (Git git = Git.open(new File(localPath))) {
            // 创建本地分支
            git.branchCreate().setName(branchName).call();
            log.info("本地分支 " + branchName + " 创建成功。");

        } catch (IOException | GitAPIException e) {
            log.error("Error occurred while creating local branch", e);
        }
    }

    /**
     * 删除本地分支
     *
     * @param localPath  本地仓库路径
     * @param branchName 要删除的分支名称
     */
    public void deleteLocalBranch(String localPath, String branchName) throws IOException, GitAPIException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        Repository repository = builder.setGitDir(new File(localPath + "/.git"))
                .readEnvironment()
                .findGitDir()
                .build();

        try (Git git = new Git(repository)) {
            // 获取所有本地分支
            for (Ref ref : git.branchList().call()) {
                String refName = ref.getName();
                if (refName.endsWith("/" + branchName)) {
                    // 删除本地分支
                    git.branchDelete().setBranchNames(refName).setForce(true).call();
                    log.info("Deleted branch: " + branchName);
                    return;
                }
            }
            log.info("Branch not found: " + branchName);
        }
    }

    /**
     * 基于 master 创建本地分支
     *
     * @param localPath  本地仓库路径
     * @param branchName 要创建的分支名称
     */
    public void createLocalBranchBasedOnMaster(String localPath, String branchName) {
        try (Git git = Git.open(new File(localPath))) {
            // 切换到 master 分支
            git.checkout()
                    .setCreateBranch(true)
                    .setName(branchName)
                    .setStartPoint("origin/master")
                    .call();
            log.info("基于 master 的远程分支 " + branchName + " 创建成功。");
        } catch (IOException | GitAPIException e) {
            log.error("Exception occurred while creating local branch based on master", e);
        }
    }

    /**
     * 将本地分支推送到远程仓库
     *
     * @param localPath  本地仓库路径
     * @param username   远程仓库用户名
     * @param password   远程仓库密码
     * @param branchName 要推送的分支名称
     */
    public void pushLocalBranchToRemote(String localPath, String username, String password, String branchName) {
        try (Git git = Git.open(new File(localPath))) {
            // 设置凭证，用于远程仓库的认证
            UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);

            // 切换到要推送的分支
            git.checkout().setName(branchName).call();

            // 添加所有更改到暂存区
            git.add().addFilepattern(".").call();

            // 提交更改
            git.commit().setMessage("commit from ci4s").call();

            // 推送本地分支到远程仓库
            git.push()
                    .setRemote("origin")
                    .add(branchName)
                    .setCredentialsProvider(credentialsProvider)
                    .call();

            log.info("本地分支 " + branchName + " 推送成功。");

        } catch (IOException | GitAPIException e) {
            log.error("Error occurred while pushing local branch to remote", e);
        }
    }

    /**
     * 将本地分支推送到远程（新增远程分支）
     *
     * @param localPath  本地仓库路径
     * @param username   用户名
     * @param password   密码
     * @param branchName 要推送的分支名称
     * @throws IOException     如果仓库路径无效
     * @throws GitAPIException 如果Git操作失败
     */
    public void pushNewBranchToRemote(String localPath, String username, String password, String branchName) throws IOException, GitAPIException {
        try (Git git = Git.open(new File(localPath))) {
            // 设置凭证，用于远程仓库的认证
            UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);

            // 切换到要推送的分支，确保分支已存在，并且切换至该分支
            git.checkout().setName(branchName).call();

            // 创建一个新的 RefSpec
            RefSpec refSpec = new RefSpec(branchName + ":" + branchName);

            // 添加所有更改到暂存区
            git.add().addFilepattern(".").call();

            // 提交更改
            git.commit().setMessage("commit from ci4s").call();
            // 推送本地分支到远程端（如果远程无该分支，则新增）
            Iterable<PushResult> pushResults = git.push()
                    .setRemote("origin")
                    .setRefSpecs(refSpec)
                    .setCredentialsProvider(credentialsProvider)
                    .setTransportConfigCallback(new ProxyConfigCallback())
                    .call();

            // 打印结果
            pushResults.forEach(pr -> log.info(pr.getMessages()));

            log.info("分支 " + branchName + " 已成功推送到远程仓库。");
        }
    }

    /**
     * 检查本地是否存在指定分支
     *
     * @param git        Git 实例
     * @param branchName 分支名称
     * @return 如果本地存在该分支返回 true，否则返回 false
     */
    private static boolean branchExistsLocally(Git git, String branchName) throws GitAPIException {
        List<Ref> localBranches = git.branchList().call();
        for (Ref localBranch : localBranches) {
            if (localBranch.getName().endsWith("/" + branchName)) {
                return true;
            }
        }
        return false;
    }

    public void refreshRemoteBranches(String localPath, String username, String password, String branch) throws Exception {
        try (Repository repository = new FileRepositoryBuilder()
                .setGitDir(new File(localPath + "/.git"))
                .readEnvironment()
                .findGitDir()
                .build()) {

            Git git = new Git(repository);

            // 检查仓库状态
            if (repository.getRepositoryState().equals(RepositoryState.MERGING) ||
                    repository.getRepositoryState().equals(RepositoryState.MERGING_RESOLVED)) {
                log.info("Repository is in a merging state, please resolve conflicts manually.");
                return;
            }

            // 设置凭证提供者
            UsernamePasswordCredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);

            // 获取远程分支
            FetchResult fetchResult = git.fetch()
                    .setRemote("origin")
                    .setCredentialsProvider(credentialsProvider)
                    .setTransportConfigCallback(new ProxyConfigCallback()) // 设置代理
                    .call();

            // 打印获取的远程分支
            Collection<Ref> fetchedRefs = fetchResult.getAdvertisedRefs();
            for (Ref ref : fetchedRefs) {
                log.info("Fetched branch: " + ref.getName());
            }

            // 更新本地分支信息
            git.branchList()
                    .setListMode(org.eclipse.jgit.api.ListBranchCommand.ListMode.REMOTE)
                    .call()
                    .forEach(ref -> {
                        String fullBranchName = ref.getName();
                        String branchName = fullBranchName.replace("refs/remotes/origin/", "");
                        if (!"master".equals(branchName)) {
                            try {
                                completionService.submit(() -> {
                                    try {
                                        processBranch(git, repository, credentialsProvider, fullBranchName, branchName);
                                    } catch (Exception e) {
                                        log.error("Failed to process branch: " + branchName, e);
                                    }
                                    return null;
                                });
                            } catch (Exception e) {
                                log.error("Task submission rejected", e);
                            }
                        }
                    });

            executorService.shutdown();
            try {
                executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
            } catch (InterruptedException e) {
                log.error("Executor service interrupted", e);
            }
            log.info("远程分支刷新到本地完成。");
        } catch (Exception e) {
            log.error("Error occurred while refreshing remote branches ", e);
        }

        // 切换分支
        gitCheckoutBranch(localPath, branch);
        dvcPull(localPath);
        dvcCheckout(localPath);
    }

    private void processBranch(Git git, Repository repository, UsernamePasswordCredentialsProvider credentialsProvider, String fullBranchName, String branchName) throws Exception {
        // 检查本地分支是否存在
        Ref localRef = repository.findRef("refs/heads/" + branchName);
        if (localRef != null) {
            // 如果存在，检查是否已经关联到远程分支
            if (!isBranchUpstreamSet(repository, branchName)) {
                // 如果没有关联，设置上游分支
                git.branchCreate()
                        .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.SET_UPSTREAM)
                        .setStartPoint(fullBranchName)
                        .setName(branchName)
                        .call();
            }
        } else {
            // 如果不存在，创建本地分支并设置上游分支
            git.branchCreate()
                    .setName(branchName)
                    .setStartPoint(fullBranchName)
                    .call();
            // 设置上游分支
            git.branchCreate()
                    .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.SET_UPSTREAM)
                    .setStartPoint(fullBranchName)
                    .setName(branchName)
                    .call();
        }
        // 执行 git pull
        PullCommand pullCommand = git.pull()
                .setTransportConfigCallback(new ProxyConfigCallback())
                .setCredentialsProvider(credentialsProvider);
        pullCommand.call();
    }

    /**
     * 检查本地分支是否已经关联到远程分支
     *
     * @param repository 仓库对象
     * @param branchName 分支名称
     * @return 如果已经关联，返回 true；否则返回 false
     */
    private static boolean isBranchUpstreamSet(Repository repository, String branchName) {
        try {
            org.eclipse.jgit.lib.Config config = repository.getConfig();
            String upstreamBranch = config.getString("branch", branchName, "merge");
            return upstreamBranch != null && !upstreamBranch.isEmpty();
        } catch (Exception e) {
            log.error("Failed to check upstream for branch: " + branchName, e);
            return false;
        }
    }


    public void gitFetch(String localPath, String username, String password) throws IOException, GitAPIException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        Repository repository = builder.setGitDir(new File(localPath, ".git"))
                .readEnvironment()
                .findGitDir()
                .build();

        try (Git git = new Git(repository)) {
            CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);
            FetchCommand fetchCommand = git.fetch();
            fetchCommand.setCredentialsProvider(credentialsProvider).call();
        }
    }

    //强制切换分支
    public void gitCheckoutBranch(String localPath, String branchName) throws IOException, GitAPIException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        Repository repository = builder.setGitDir(new File(localPath, ".git"))
                .readEnvironment()
                .findGitDir()
                .build();

        try (Git git = new Git(repository)) {
            CheckoutCommand checkoutCommand = git.checkout();
            checkoutCommand.setName(branchName).setForce(true).call();
        }
    }

    public void gitPull(String localPath, String username, String password) throws IOException, GitAPIException {
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        Repository repository = builder.setGitDir(new File(localPath, ".git"))
                .readEnvironment()
                .findGitDir()
                .build();

        try (Git git = new Git(repository)) {
            CredentialsProvider credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);
            PullCommand pullCommand = git.pull();
            pullCommand.setCredentialsProvider(credentialsProvider).call();
        }
    }

    public void dvcInit(String localPath) throws Exception {
        String command = "dvc init";
        runCommand(command, localPath);
    }

    public void dvcAdd(String localPath, String filePath) throws Exception {
        String command = "dvc add " + filePath;
        runCommand(command, localPath);
    }

    public void dvcRemoteAdd(String localPath, String s3RemoteUrl) throws Exception {
        String command = "dvc remote add -d myremote s3://" + s3RemoteUrl;
        runCommand(command, localPath);
    }

    public void dvcConfigS3Credentials(String localPath, String endpointurl) throws Exception {
        String command = "dvc remote modify myremote endpointurl " + endpointurl;
        runCommand(command, localPath);
    }

    public void dvcConfigS3Credentials2(String localPath, String accessKeyId) throws Exception {
        String command = "dvc remote modify myremote access_key_id " + accessKeyId;
        runCommand(command, localPath);
    }

    public void dvcConfigS3Credentials3(String localPath, String secretAccessKey) throws Exception {
        String command = "dvc remote modify myremote secret_access_key " + secretAccessKey;
        runCommand(command, localPath);
    }

    public void dvcPush(String localPath) throws Exception {
        String command = "dvc push -v";
        runCommand(command, localPath);
    }

    // 强制刷新 dvcPull 方法
    public void dvcPull(String localPath) throws Exception {
        String command = "dvc pull -v --force";
        runCommand(command, localPath);
    }


    //  方法
    public void dvcCheckout(String localPath) throws Exception {
        String command = "dvc checkout -v --force";
        runCommand(command, localPath);
    }


    /**
     * 切换到指定分支并执行git pull，然后获取data文件夹下所有文件的路径、名称和大小
     *
     * @param localPath  本地路径
     * @param repoFolder 仓库文件夹名称
     * @param branch     分支名称
     * @return 包含文件路径、名称和大小的List<Map<String, Object>>
     */
    public List<Map<String, Object>> getFileDetailsAfterGitPull(String localPath, String repoFolder, String branch, String filePath, String username, String password) {
        try {
            //刷新
            refreshRemoteBranches(localPath + "/" + repoFolder + "/" + branch, username, password, branch);
            // 读取data文件夹中的文件列表
            String path = localPath + "/" + repoFolder + "/" + branch + "/" + filePath;

            return FileUtil.getFiles(path);

        } catch (Exception e) {
            log.error("Error occurred while getting file details after git pull", e);
        }
        return new ArrayList<>();
    }
}
