package com.virtuslab.gitmachete.frontend.actions.backgroundables;

import static com.virtuslab.gitmachete.frontend.resourcebundles.GitMacheteBundle.getNonHtmlString;
import static com.virtuslab.gitmachete.frontend.resourcebundles.GitMacheteBundle.getString;

import java.util.Collections;

import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.vcs.VcsNotifier;
import git4idea.branch.GitBranchUiHandlerImpl;
import git4idea.branch.GitBranchWorker;
import git4idea.branch.GitRebaseParams;
import git4idea.commands.Git;
import git4idea.rebase.GitRebaseUtils;
import git4idea.repo.GitRepository;
import lombok.CustomLog;
import lombok.experimental.ExtensionMethod;
import lombok.val;

import com.virtuslab.gitmachete.backend.api.GitMacheteMissingForkPointException;
import com.virtuslab.gitmachete.backend.api.IGitRebaseParameters;
import com.virtuslab.gitmachete.backend.api.INonRootManagedBranchSnapshot;
import com.virtuslab.gitmachete.frontend.actions.hooks.PreRebaseHookExecutor;
import com.virtuslab.gitmachete.frontend.resourcebundles.GitMacheteBundle;
import com.virtuslab.qual.guieffect.UIThreadUnsafe;

@ExtensionMethod(GitMacheteBundle.class)
@CustomLog
public class RebaseOnParentBackgroundable extends SideEffectingBackgroundable {

  private static final String NL = System.lineSeparator();

  private final GitRepository gitRepository;
  private final INonRootManagedBranchSnapshot branchToRebase;
  private final boolean shouldExplicitlyCheckout;

  public RebaseOnParentBackgroundable(GitRepository gitRepository,
      INonRootManagedBranchSnapshot branchToRebase,
      boolean shouldExplicitlyCheckout) {
    super(gitRepository.getProject(), getNonHtmlString("action.GitMachete.RebaseOnParentBackgroundable.task-title"),
        /* shortName */ "rebase");

    this.gitRepository = gitRepository;
    this.branchToRebase = branchToRebase;
    this.shouldExplicitlyCheckout = shouldExplicitlyCheckout;
    LOG.debug(() -> "Entering: gitRepository = ${gitRepository}, branchToRebase = ${branchToRebase}");
  }

  @UIThreadUnsafe
  private GitRebaseParams getIdeaRebaseParamsOf(GitRepository repository, IGitRebaseParameters gitRebaseParams) {
    val gitVersion = repository.getVcs().getVersion();
    val currentBranchName = gitRebaseParams.getCurrentBranch().getName();
    val newBaseBranchFullName = gitRebaseParams.getNewBaseBranch().getFullName();
    val forkPointCommitHash = gitRebaseParams.getForkPointCommit().getHash();

    return new GitRebaseParams(gitVersion, currentBranchName, newBaseBranchFullName,
        /* upstream */ forkPointCommitHash, /* interactive */ true, /* preserveMerges */ false);
  }

  @Override
  @UIThreadUnsafe
  public void doRun(ProgressIndicator indicator) {
    IGitRebaseParameters gitRebaseParameters;
    try {
      gitRebaseParameters = branchToRebase.getParametersForRebaseOntoParent();
      LOG.debug(() -> "Queuing the machete-pre-rebase hook and the rebase background task for branch " +
          "'${branchToRebase.getName()}");
    } catch (GitMacheteMissingForkPointException e) {
      val message = e.getMessage() == null ? "Unable to get rebase parameters." : e.getMessage();
      LOG.error(message);
      VcsNotifier.getInstance(project).notifyError(/* displayId */ null,
          getString("action.GitMachete.RebaseOnParentBackgroundable.notification.title.rebase-fail"), message);

      return;
    }

    val preRebaseHookExecutor = new PreRebaseHookExecutor(gitRepository);
    if (!preRebaseHookExecutor.executeHookFor(gitRebaseParameters)) {
      return;
    }

    val params = getIdeaRebaseParamsOf(gitRepository, gitRebaseParameters);
    LOG.info("Rebasing '${gitRebaseParameters.getCurrentBranch().getName()}' branch " +
        "until ${gitRebaseParameters.getForkPointCommit().getHash()} commit " +
        "onto ${gitRebaseParameters.getNewBaseBranch().getName()}");

    /*
     * Git4Idea ({@link git4idea.rebase.GitRebaseUtils#rebase}) does not allow rebasing in detached head state. However, it is
     * possible with Git (performing checkout implicitly) and should be allowed in the case of "Checkout and Rebase Onto Parent"
     * Action. To pass the git4idea check in such a case, we checkout the branch explicitly and then perform the actual rebase.
     */
    if (shouldExplicitlyCheckout) {
      val uiHandler = new GitBranchUiHandlerImpl(project, indicator);
      new GitBranchWorker(project, Git.getInstance(), uiHandler)
          .checkout(/* reference */ gitRebaseParameters.getCurrentBranch().getName(), /* detach */ false,
              Collections.singletonList(gitRepository));
    }
    GitRebaseUtils.rebase(project, Collections.singletonList(gitRepository), params, indicator);
  }
}
