package com.atlassian.bitbucket.scm;

import com.atlassian.bitbucket.compare.CompareRequest;
import com.atlassian.bitbucket.hook.ScmHookHandlerFactory;
import com.atlassian.bitbucket.i18n.KeyedMessage;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.pull.PullRequestRef;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.bulk.ScmBulkContentCommandFactory;
import com.atlassian.bitbucket.scm.compare.ScmCompareCommandFactory;
import com.atlassian.bitbucket.scm.mirror.ScmMirrorCommandFactory;
import com.atlassian.bitbucket.scm.pull.BulkRescopeCommandParameters;
import com.atlassian.bitbucket.scm.pull.RepositoryRescopeCommandParameters;
import com.atlassian.bitbucket.scm.pull.ScmPullRequestCommandFactory;
import com.atlassian.bitbucket.scm.ref.ScmRefCommandFactory;
import com.atlassian.stash.internal.hook.PluginScmHookHandlerFactory;
import com.atlassian.stash.internal.maintenance.latch.LatchMode;
import com.atlassian.stash.internal.maintenance.latch.LatchState;
import com.atlassian.stash.internal.scm.*;
import com.google.common.collect.ImmutableSet;

import javax.annotation.Nonnull;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * A simple {@link InternalScmService ScmService} implementation to facilitate SCM integration testing. This
 * service only supports access to a single fixed {@link Scm}.
 */
@SuppressWarnings({"unused", "ConstantConditions"})
public class SimpleScmService implements InternalScmService {

    private final Scm scm;

    public SimpleScmService(Scm scm) {
        this.scm = scm;
    }

    @Nonnull
    @Override
    public ScmLatch acquireLatch(@Nonnull LatchMode latchMode) {
        return acquireLatch(latchMode, null);
    }

    @Nonnull
    @Override
    public ScmLatch acquireLatch(@Nonnull LatchMode latchMode, String latchId) {
        throw new UnsupportedOperationException("The SimpleScmService cannot be latched");
    }

    @Override
    public void bulkRescope(@Nonnull BulkRescopeCommandParameters parameters) {
        checkScmId(checkNotNull(parameters, "parameters").getRepository())
                .getPullRequestCommandFactory()
                .bulkRescope(parameters)
                .call();
    }

    @Override
    public void create(@Nonnull Repository repository) {
        checkScmId(repository).getCommandFactory()
                .create(repository)
                .call();
    }

    @Nonnull
    @Override
    public ScmCommandBuilder createBuilder(@Nonnull Repository repository) {
        return checkScmId(repository).getCommandBuilderFactory().builder(repository);
    }

    @Override
    public void delete(@Nonnull Repository repository, @Nonnull DeleteCommandParameters parameters) {
        checkScmId(repository).getCommandFactory()
                .delete(repository, parameters)
                .synchronous()
                .call();
    }

    @Nonnull
    @Override
    public Scm findById(@Nonnull String scmId) {
        return checkScmId(scmId);
    }

    @Nonnull
    @Override
    public Scm findByRepository(@Nonnull Repository repository) {
        return checkScmId(repository);
    }

    @Override
    public void fork(@Nonnull Repository repository, @Nonnull ForkCommandParameters parameters) {
        checkScmId(repository).getExtendedCommandFactory()
                .fork(repository, parameters)
                .call();
    }

    @Nonnull
    @Override
    public Set<AvailableScm> getAvailable() {
        //Since this ScmService is only intended to support SCM tests, only a single, fixed SCM is available
        return ImmutableSet.of(new SimpleAvailableScm.Builder(scm.getId()).name(scm.getName()).build());
    }

    @Nonnull
    @Override
    public ScmBulkContentCommandFactory getBulkContentCommandFactory(@Nonnull Repository repository) {
        checkNotNull(repository, "repository");

        return new PluginScmBulkContentCommandFactory(repository, checkScmId(repository).getBulkContentCommandFactory());
    }

    @Nonnull
    @Override
    public ScmCommandFactory getCommandFactory(@Nonnull Repository repository) {
        return new PluginScmCommandFactory(repository, checkScmId(repository).getCommandFactory());
    }

    @Nonnull
    @Override
    public ScmCompareCommandFactory getCompareCommandFactory(@Nonnull CompareRequest compareRequest) {
        checkNotNull(compareRequest, "compareRequest");
        return new PluginScmCompareCommandFactory(compareRequest,
                checkScmId(checkNotNull(compareRequest.getToRef(), "compareRequest.toRef").getRepository()).getCompareCommandFactory());
    }

    @Override
    public ScmLatch getCurrentLatch() {
        return null;
    }

    @Nonnull
    @Override
    public ScmHookHandlerFactory getHookHandlerFactory(@Nonnull Repository repository) {
        return new PluginScmHookHandlerFactory(repository, checkScmId(repository).getHookHandlerFactory());
    }

    @Nonnull
    @Override
    public PluginMergeStrategies getMergeStrategies(@Nonnull String scmId) {
        return checkScmId(scmId).getMergeStrategies();
    }

    @Nonnull
    @Override
    public ScmMirrorCommandFactory getMirrorCommandFactory(@Nonnull Repository repository) {
        checkNotNull(repository, "repository");

        return new PluginScmMirrorCommandFactory(repository, checkScmId(repository).getMirrorCommandFactory());
    }

    @Nonnull
    @Override
    public ScmExtendedCommandFactory getExtendedCommandFactory(@Nonnull Repository repository) {
        checkNotNull(repository, "repository");

        return new PluginScmExtendedCommandFactory(repository,
                checkScmId(repository).getExtendedCommandFactory(),
                (scmId, feature) -> {
                    String message = scmId + " does not support the " + feature +
                            " feature; its tests should not attempt to use it.";

                    throw new FeatureUnsupportedScmException(
                            new KeyedMessage("bitbucket.scm.feature.unsupported", message, message),
                            scmId, feature);
                });
    }

    @Nonnull
    @Override
    public Set<ScmProtocol> getProtocols(@Nonnull Repository repository) {
        throw new UnsupportedOperationException("Not implemented for this test service");
    }

    @Nonnull
    @Override
    public ScmPullRequestCommandFactory getPullRequestCommandFactory(@Nonnull PullRequest pullRequest) {
        PullRequestRef toRef = checkNotNull(checkNotNull(pullRequest, "pullRequest").getToRef(), "pullRequest.toRef");

        return new PluginScmPullRequestCommandFactory(pullRequest,
                checkScmId(toRef.getRepository()).getPullRequestCommandFactory());
    }

    @Nonnull
    @Override
    public ScmRefCommandFactory getRefCommandFactory(@Nonnull Repository repository) {
        return new PluginScmRefCommandFactory(repository, checkScmId(repository).getRefCommandFactory());
    }

    @Nonnull
    @Override
    public String getScmName(@Nonnull Repository repository) {
        return checkScmId(repository).getName();
    }

    @Override
    public long getSize(@Nonnull Repository repository) {
        return checkScmId(repository).getSize(repository);
    }

    @Nonnull
    @Override
    public LatchState getState() {
        return LatchState.AVAILABLE;
    }

    @Override
    public boolean isEmpty(@Nonnull Repository repository) {
        return checkScmId(repository).isEmpty(repository);
    }

    @Override
    public boolean isLatched() {
        return false;
    }

    @Override
    public boolean isSupported(@Nonnull Repository repository, @Nonnull ScmFeature feature) {
        return checkScmId(repository).getFeatures().contains(checkNotNull(feature, "feature"));
    }

    @Override
    public boolean isSupported(@Nonnull String scmId, @Nonnull ScmFeature feature) {
        return checkScmId(scmId).getFeatures().contains(checkNotNull(feature, "feature"));
    }

    @Deprecated
    @Override
    @SuppressWarnings("deprecation")
    public void rescope(@Nonnull RepositoryRescopeCommandParameters parameters) {
        checkScmId(checkNotNull(parameters, "parameters").getRepository())
                .getPullRequestCommandFactory()
                .rescope(parameters)
                .call();
    }

    private Scm checkScmId(Repository repository) {
        return checkScmId(checkNotNull(repository, "repository").getScmId());
    }

    private Scm checkScmId(String scmId) {
        checkArgument(scm.getId().equals(checkNotNull(scmId, "scmId")),
                "This ScmService only supports the [" + scm.getId() + "] Scm");

        return scm;
    }
}
