package de.hftstuttgart.utils;

import de.hftstuttgart.config.ModocotProperties;
import io.gitea.model.Repository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Arrays;
import java.util.List;

@Component
public class JGitUtil {

    private static final Logger LOG = LogManager.getLogger(JGitUtil.class);

    private final ModocotProperties modocotProperties;

    public JGitUtil(ModocotProperties modocotProperties) {
        this.modocotProperties = modocotProperties;
    }

    /**
     * Clone Repository from {@code uriToClone} into Directory {@code cloneDirectory}, and using token {@code token}.<br>
     * {@code boolean proxy} determines, if a proxy should be used
     *
     * @param uriToClone     Repository cloneUrl
     * @param cloneDirectory {@link File} directory in which Repository should be cloned into
     * @param token          AuthToken, when cloning Repository. If {@code null} use {@code ModocotProperties}
     * @param proxy          boolean if a proxy should be used
     */
    public void cloneRepository(String uriToClone, File cloneDirectory, String token, boolean useToken, boolean proxy) {
        if (!cloneDirectory.isDirectory()) {
            String error = String.format("%s is not a directory, cannot clone", cloneDirectory.getAbsolutePath());
            LOG.error(error);
            throw new IllegalArgumentException(error);
        }
        LOG.info(String.format("Cloning all files from %s into %s", uriToClone, cloneDirectory.getAbsolutePath()));
        try {
            deleteDotGitFolder(cloneDirectory);
            UsernamePasswordCredentialsProvider credProvider = null;
            LOG.info("using token: " + useToken);
            if (useToken) {
                LOG.info("token: " + token);
                if (token != null) {
                    LOG.info("Using token: " + token);
                    String[] credentials = token.split(":");
                    credProvider = new UsernamePasswordCredentialsProvider(credentials[0], credentials[1]);
                } else {
                    LOG.info("Using credentials: " + this.modocotProperties.getGitTeaUsername() + ", " + this.modocotProperties.getGitTeaPassword());
                    credProvider = new UsernamePasswordCredentialsProvider(
                            this.modocotProperties.getGitTeaUsername(),
                            this.modocotProperties.getGitTeaPassword());
                }
            }
            LOG.info("Starting cloning, url: " + uriToClone);
            setProxy(proxy);
            if (useToken) {
                Git.cloneRepository()
                        .setCredentialsProvider(credProvider)
                        .setURI(uriToClone)
                        .setDirectory(cloneDirectory)
                        .call()
                        .close();
            } else {
                Git.cloneRepository()
                        .setURI(uriToClone)
                        .setDirectory(cloneDirectory)
                        .call()
                        .close();
            }
        } catch (GitAPIException e) {
            LOG.error(String.format("Error while cloning from %s", uriToClone), e);
        }
    }

    /**
     * Commit {@code directory} and push all files to the given {@code repository}.
     *
     * @param directory  {@link File} directory to commit
     * @param repository {@link Repository} Repository to push to
     * @param proxy      boolean if a proxy should be used
     * @return {@code Iterable<PushResult>}
     */
    public Iterable<PushResult> commitAllAndPush(File directory, Repository repository, boolean proxy) {
        if (!directory.isDirectory()) {
            String error = String.format("%s is not a directory, cannot commit or push", directory.getAbsolutePath());
            LOG.error(error);
            throw new IllegalArgumentException(error);
        }
        LOG.info(String.format("Committing all files in: %s and pushing them to: %s", directory.getAbsolutePath(), repository.getCloneUrl()));
        try {
            deleteDotGitFolder(directory);
            // "git init" new repository
            Git.init()
                    .setDirectory(directory)
                    .call();

            // open new repository
            Git git = Git.open(directory);

            // "git add ." on repository
            git.add()
                    .addFilepattern(".")
                    .call();

            // "git commit -m %gitTeaCommitMessage%" on repository
            git.commit()
                    .setMessage(this.modocotProperties.getGitTeaDefaultCommitMessage())
                    .call();

            // add new remote from repository
            git.remoteAdd()
                    .setName(this.modocotProperties.getGitTeaOrigin())
                    .setUri(new URIish(repository.getCloneUrl()))
                    .call();
            // "git push" to new origin
            setProxy(proxy);
            return git.push()
                    .setCredentialsProvider(new UsernamePasswordCredentialsProvider(
                            this.modocotProperties.getGitTeaUsername(),
                            this.modocotProperties.getGitTeaPassword()))
                    .call();

        } catch (Exception e) {
            LOG.error(String.format("Error while committing to repo: %s from file: %s", repository, directory.getAbsolutePath()), e);
        }
        return null;
    }

    private void deleteDotGitFolder(File directory) {
        File temp = new File(directory.getPath() + "/src/UnitTests/.git");
        if (temp.exists() && temp.isDirectory() && temp.canWrite()) {
            FileUtil.deleteFolderRecursively(temp);
        }
        temp = new File(directory.getPath() + "/src/.git");
        if (temp.exists() && temp.isDirectory() && temp.canWrite()) {
            FileUtil.deleteFolderRecursively(temp);
        }
        temp = new File(directory.getPath() + "/.git");
        if (temp.exists() && temp.isDirectory() && temp.canWrite()) {
            FileUtil.deleteFolderRecursively(temp);
        }
    }

    private void setProxy(boolean proxy) {
        if (proxy) {
            ProxySelector.setDefault(new ProxySelector() {
                final ProxySelector delegate = ProxySelector.getDefault();

                @Override
                public List<Proxy> select(URI uri) {
                    // Filter the URIs to be proxied
                    if (uri.toString().contains("https")) {
                        return Arrays.asList(new Proxy(Proxy.Type.HTTP, InetSocketAddress
                                .createUnresolved("proxy.hft-stuttgart.de", 80)));
                    }
                    if (uri.toString().contains("http")) {
                        return Arrays.asList(new Proxy(Proxy.Type.HTTP, InetSocketAddress
                                .createUnresolved("proxy.hft-stuttgart.de", 80)));
                    }
                    // revert to the default behaviour
                    return delegate == null ? Arrays.asList(Proxy.NO_PROXY)
                            : delegate.select(uri);
                }

                @Override
                public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
                    if (uri == null || sa == null || ioe == null) {
                        throw new IllegalArgumentException(
                                "Arguments can't be null.");
                    }
                }
            });
        }
    }
}
