package com.atlassian.bitbucket.mesh.git.revert;

import com.atlassian.bitbucket.mesh.annotation.Profiled;
import com.atlassian.bitbucket.mesh.event.EventPublisher;
import com.atlassian.bitbucket.mesh.git.GitAgent;
import com.atlassian.bitbucket.mesh.git.GitRefPattern;
import com.atlassian.bitbucket.mesh.git.GitTimers;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.git.builder.revert.GitRevertBuilder;
import com.atlassian.bitbucket.mesh.git.builder.revert.RevertCompletionHandler;
import com.atlassian.bitbucket.mesh.git.builder.updateref.GitUpdateRefSetBuilder;
import com.atlassian.bitbucket.mesh.git.builder.updateref.UpdateRefCompletionHandler;
import com.atlassian.bitbucket.mesh.git.exception.RevertException;
import com.atlassian.bitbucket.mesh.git.porcelain.FetchRefParameters;
import com.atlassian.bitbucket.mesh.git.porcelain.GitPorcelain;
import com.atlassian.bitbucket.mesh.git.porcelain.WorkTreeParameters;
import com.atlassian.bitbucket.mesh.git.ref.Branch;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.repository.RefsUpdatedEvent;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.util.ShaUtils;
import com.atlassian.util.profiling.Ticker;
import com.atlassian.util.profiling.Timers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.nio.file.Path;

import static com.atlassian.bitbucket.mesh.git.GitConstants.PATH_GIT;
import static java.util.Objects.requireNonNull;

@Profiled
public class DefaultRevertManager implements RevertManager {

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

    private final GitAgent agent;
    private final GitProcessBuilderFactory builderFactory;
    private final EventPublisher eventPublisher;
    private final GitPorcelain porcelain;

    public DefaultRevertManager(GitAgent agent, GitProcessBuilderFactory builderFactory, EventPublisher eventPublisher,
                                GitPorcelain porcelain) {
        this.agent = agent;
        this.builderFactory = builderFactory;
        this.eventPublisher = eventPublisher;
        this.porcelain = porcelain;
    }

    @Nonnull
    @Override
    public String revert(@Nonnull RevertParameters parameters) {
        requireNonNull(parameters, "parameters");
        Repository repository = parameters.getRepository();
        Path workTree = null;
        try (Ticker ignored = GitTimers.REVERT.start(parameters)) {
            String startingCommit = parameters.getBranch().getLatestCommit();
            WorkTreeParameters.Builder builder =
                    new WorkTreeParameters.Builder(repository, startingCommit)
                            .pristine(true)
                            .alternate(repository);
            workTree = porcelain.workTree(builder.build());

            log.debug("{}: Reverting commit {} onto {} in {}", repository, parameters.getCommitId(), startingCommit,
                    workTree);
            doRevert(workTree, parameters);
            return doFetch(workTree, parameters);
        } finally {
            if (workTree != null) {
                MoreFiles.deleteQuietly(workTree);
            }
        }
    }

    @Nonnull
    @Override
    public Branch updateRef(@Nonnull RevertParameters parameters, @Nonnull String revertHash) {
        requireNonNull(parameters, "parameters");
        Repository repository = parameters.getRepository();
        String targetRef = parameters.getTargetBranch()
                .map(GitRefPattern.HEADS::qualify)
                .orElse(parameters.getBranch().getId());
        log.debug("{}: Updating branch {} to {}", repository, targetRef, revertHash);
        GitUpdateRefSetBuilder builder = builderFactory.builder(repository)
                .updateRef()
                .set(targetRef, revertHash)
                .author(parameters.getCommitter())
                .completionHandler(new UpdateRefCompletionHandler());
        if (parameters.getBranch().getId().equals(targetRef)) {
            builder.oldValue(parameters.getBranch().getLatestCommit());
        } else {
            builder.oldValue(ShaUtils.NULL_SHA1);
        }
        try (Ticker ignored = Timers.start("git: update ref " + repository)) {
            parameters.configureAndRun(builder.build());
        }
        eventPublisher.publish(new RefsUpdatedEvent(repository));
        Branch branch = agent.resolveBranch(repository, targetRef, true);
        if (branch == null) {
            branch = agent.resolveBranch(repository, targetRef);
        }
        if (branch == null) {
            log.error("{}: Ref {} could not be resolved", repository, targetRef);
            throw new RevertException("Failed to resolve branch", parameters.getCommitId());
        }
        return branch;
    }

    private String doFetch(Path workTree, RevertParameters parameters) {
        String revertCommit = agent.revParse(workTree.resolve(PATH_GIT), GitAgent.HEAD);
        porcelain.fetchRef(new FetchRefParameters.Builder(parameters.getRepository(), workTree.resolve(PATH_GIT),
                parameters.getCommitter())
                .source(revertCommit)
                .build());
        return revertCommit;
    }

    private void doRevert(Path workTree, RevertParameters parameters) {
        GitRevertBuilder builder = builderFactory.builder()
                .workDir(workTree)
                .revert()
                .commit(parameters.getCommitId())
                .completionHandler(new RevertCompletionHandler(parameters))
                .committer(parameters.getCommitter());
        if (parameters.getMergedCommitParentNumber().isPresent()) {
            builder.parentNumber(parameters.getMergedCommitParentNumber().get());
        }
        parameters.configureAndRun(builder.build());
    }
}
