package com.atlassian.stash.internal.repository.sync.command;

import com.atlassian.bitbucket.commit.NoSuchCommitException;
import com.atlassian.bitbucket.repository.*;
import com.atlassian.bitbucket.scm.git.command.GitCommand;
import com.atlassian.bitbucket.validation.ArgumentValidationException;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.scm.MergeCommandParameters;
import com.atlassian.bitbucket.scm.SimpleCommand;
import com.atlassian.bitbucket.scm.git.GitAgent;
import com.atlassian.bitbucket.scm.git.command.GitCommandFactory;
import com.atlassian.bitbucket.user.ApplicationUser;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.atlassian.stash.internal.repository.sync.SimpleRejectedRef.isTag;

/**
 * @since 2.6
 */
public class MergeRefCommand extends SimpleCommand<RefChange> {

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

    private final GitAgent agent;
    private final GitCommandFactory commandFactory;
    private final I18nService i18nService;
    private final String message;
    private final String refId;
    private final Repository repository;
    private final Repository upstream;
    private final ApplicationUser user;

    private long executionTimeout;
    private long idleTimeout;

    public MergeRefCommand(GitAgent agent, GitCommandFactory commandFactory, I18nService i18nService,
                           MergeRefCommandParameters parameters) {
        this.agent = agent;
        this.commandFactory = commandFactory;
        this.i18nService = i18nService;

        message = parameters.getMessage();
        refId = parameters.getRefId();
        repository = parameters.getRepository();
        upstream = parameters.getUpstream();
        user = parameters.getUser();
    }

    @Override
    public RefChange call() {
        if (isTag(refId)) {
            throw new ArgumentValidationException(i18nService.createKeyedMessage("bitbucket.service.repository.refsync.merge.tag"));
        }

        Branch toBranch = agent.resolveBranch(repository, refId, true);
        if (toBranch == null) {
            KeyedMessage message = i18nService.createKeyedMessage("bitbucket.service.repository.refsync.merge.nosuchbranch",
                    repository.getProject().getKey(), repository.getSlug(), refId);
            throw new NoSuchCommitException(message, refId);
        }
        Branch fromBranch = agent.resolveBranch(upstream, refId, true);
        if (fromBranch == null) {
            KeyedMessage message = i18nService.createKeyedMessage("bitbucket.service.repository.refsync.merge.nothingupstream",
                    repository.getProject().getKey(), repository.getSlug(), refId,
                    upstream.getProject().getKey(), upstream.getSlug());
            throw new NoSuchCommitException(message, refId);
        }
        if (StringUtils.equals(fromBranch.getLatestCommit(), toBranch.getLatestCommit())) {
            log.debug("{}/{}:{} is already in sync with {}/{} ({})",
                    repository.getProject().getKey(), repository.getSlug(), refId,
                    upstream.getProject().getKey(), upstream.getSlug(), toBranch.getLatestCommit());

            return null;
        }

        GitCommand<Branch> command = commandFactory.merge(repository,
                new MergeCommandParameters.Builder()
                        .author(user)
                        .fromBranch(fromBranch.getId())
                        .fromCommitId(fromBranch.getLatestCommit())
                        .fromRepository(upstream)
                        .message(message)
                        .toBranch(refId)
                        .build());
        command.setExecutionTimeout(executionTimeout);
        command.setIdleTimeout(idleTimeout);

        //Any exception thrown is intentionally propagated back to the caller
        Branch branch = command.call();

        return new SimpleRefChange.Builder()
                .from(toBranch)
                .to(branch == null ? fromBranch : branch) //branch will never be null, but...
                .type(RefChangeType.UPDATE)
                .build();
    }

    @Override
    public void setExecutionTimeout(long executionTimeout) {
        this.executionTimeout = executionTimeout;
    }

    @Override
    public void setIdleTimeout(long idleTimeout) {
        this.idleTimeout = idleTimeout;
    }
}
