package com.atlassian.stash.internal.scm.git.command.fetch;

import com.atlassian.bitbucket.Product;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.scm.mirror.MirrorSyncCallback;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.scm.git.GitAgent;
import com.atlassian.bitbucket.scm.git.command.GitCommandBuilderFactory;
import com.atlassian.bitbucket.scm.git.command.fetch.GitFetchBuilder;
import com.atlassian.bitbucket.scm.mirror.MirrorSyncCommandParameters;
import com.atlassian.bitbucket.util.UrlUtils;
import com.atlassian.stash.internal.scm.git.GitAuthUtils;
import com.atlassian.stash.internal.scm.git.InternalGitScmConfig;
import com.atlassian.stash.internal.scm.git.command.SimpleGitCommand;
import com.atlassian.stash.internal.scm.git.command.foreachref.AllRefsForEachRefOutputHandler;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutorService;

public class MirrorSynchronizeCommand extends SimpleGitCommand<Void> {

    private final Logger log = LoggerFactory.getLogger(MirrorSynchronizeCommand.class);

    private final GitAgent agent;
    private final GitCommandBuilderFactory builderFactory;
    private final MirrorSyncCallback callback;
    private final InternalGitScmConfig config;
    private final I18nService i18nService;
    private final MirrorSyncCommandParameters parameters;
    private final Repository repository;

    public MirrorSynchronizeCommand(@Nonnull ExecutorService executorService, @Nonnull GitCommandBuilderFactory builderFactory,
                                    @Nonnull GitAgent agent, @Nonnull InternalGitScmConfig config,
                                    @Nonnull I18nService i18nService, @Nonnull Repository repository,
                                    @Nonnull MirrorSyncCommandParameters parameters, @Nonnull MirrorSyncCallback callback) {
        super(executorService);

        this.agent = agent;
        this.builderFactory = builderFactory;
        this.callback = callback;
        this.config = config;
        this.parameters = parameters;
        this.repository = repository;
        this.i18nService = i18nService;
    }

    @Override
    public Void call() {
        String remoteUrl = parameters.getUsername()
                .map(username -> {
                    String url = parameters.getRemoteUrl();
                    try {
                        return UrlUtils.interpolateUserInfo(url, username);
                    } catch (URISyntaxException e) {
                        log.warn("Could not set user in remote URL {}", url, e);
                        return url;
                    }
                })
                .orElse(parameters.getRemoteUrl());

        List<Ref> preFetchRefs = getRefs();
        GitFetchBuilder builder = builderFactory.builder(repository)
                .fetch()
                .author(Product.FULL_NAME, Product.NAME.toLowerCase(Locale.US) + "@atlassian.com")
                .force(true)
                .prune(true)
                .progress(true)
                .refspec("+refs/*:refs/*")
                .repository(remoteUrl);

        // disregard user localisation settings so error messages are consistent
        builder.withEnvironment("LC_ALL", "C");

        // if an SSH key is configured, configure the command to use it
        parameters.getPrivateKey().ifPresent(keyFile ->
                GitAuthUtils.configureSsh(builder, config, keyFile));
        // if a password is configured, configure the command to use it
        parameters.getPassword().ifPresent(password ->
                GitAuthUtils.configurePassword(builder, config, password));

        return configureAndCall(new MirrorFetchCommandHandler(
                agent, callback, i18nService, repository, remoteUrl, preFetchRefs)
                .build(builder));
    }

    private List<Ref> getRefs() {
        CollectingRefCallback refCallback = new CollectingRefCallback();

        configureAndCall(builderFactory.builder(repository)
                .forEachRef()
                .format(AllRefsForEachRefOutputHandler.FORMAT)
                .build(new AllRefsForEachRefOutputHandler(refCallback, agent.getHead(repository))));

        return refCallback.getRefs();
    }

    static class CollectingRefCallback extends AbstractRefCallback {
        private final List<Ref> refs;

        CollectingRefCallback() {
            refs = Lists.newArrayList();
        }

        public List<Ref> getRefs() {
            return refs;
        }

        @Override
        public boolean onRef(@Nonnull Ref ref) throws IOException {
            refs.add(ref);
            return true;
        }
    }
}
