package com.atlassian.bitbucket.scm;

import com.atlassian.bitbucket.compare.CompareRequest;
import com.atlassian.bitbucket.hook.HookHandler;
import com.atlassian.bitbucket.hook.ScmHookHandlerFactory;
import com.atlassian.bitbucket.pull.PullRequest;
import com.atlassian.bitbucket.repository.Branch;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.Tag;
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.ScmPullRequestCommandFactory;
import com.atlassian.bitbucket.scm.ref.ScmRefCommandFactory;

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

/**
 * Describes a service for interacting with an SCM, allowing commands to be created/executed to perform SCM operations.
 */
public interface ScmService {

    /**
     * Creates a {@link ScmCommandBuilder} which can be used to construct free-form commands to interact directly with
     * the underlying SCM implementation and perform custom functions on repositories.
     * <p>
     * Command builders are provided as a very powerful extension point for plugin developers, allowing them to create
     * new functionality that uses commands the application does not, or uses them in different ways. However, plugin
     * developers should be aware that:
     * <ul>
     *     <li>The system may support multiple versions of an SCM, and those versions may support different commands
     *     and/or arguments, and even the "same" command may produce different output between versions</li>
     *     <li>In a free-form builder, the command and arguments are not checked until the command is run; the system
     *     does not, and cannot, validate that the arguments are correct in advance</li>
     *     <li>Above all, <i>the system cannot verify the safety of commands being executed</i>; plugin developers
     *     <i>must</i> apply due diligence to ensure the operations they perform do not damage the repository</li>
     * </ul>
     * <p>
     * The created {@link ScmCommandBuilder} will use the provided {@link Repository repository's} directory as its
     * initial working directory. After the builder has been created, it is possible to change the working directory
     * on the instance, before commands are constructed.
     * <p>
     * Note: Per the contract of the {@link ScmCommandBuilder} API, the returned builder is <i>not thread-safe</i>. If
     * multiple threads require use of a builder, each thread should <i>always</i> create its own builder.
     *
     * @param repository the repository to create a builder for, used to determine the correct SCM implementation to
     *                   use and to provide the initial working directory for built commands
     * @return a new command builder
     * @throws FeatureUnsupportedScmException if the target SCM does not support command builders
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     * @see ScmCommandBuilder
     */
    @Nonnull
    ScmCommandBuilder<?> createBuilder(@Nonnull Repository repository);

    /**
     * Retrieves the set of {@link AvailableScm available SCMs}. SCMs that are installed but not in a good state will
     * not be included in the returned set. The returned set will never be empty; the system requires at least one
     * available SCM or it will be locked out on startup.
     * <p>
     * Each SCM implementation is different, but examples of reasons why an installed SCM might not be available
     * include:
     * <ul>
     *     <li>The binary associated with the SCM, such as {@code git} or {@code hg}, may not be installed</li>
     *     <li>The installed binary might be of an unsupported version, too old or too new</li>
     *     <li>Another plugin the SCM depends on may not be installed or may be disabled</li>
     * </ul>
     * This list is by no means exhaustive; the individual SCM implementations are free to have their own requirements
     * and dependencies.
     *
     * @return the set of <i>available</i> installed SCMs
     */
    @Nonnull
    Set<AvailableScm> getAvailable();

    /**
     * Retrieves an {@link ScmBulkContentCommandFactory}, used to create {@link Command commands} for getting content
     * from a repository in bulk.
     * <p>
     * SCMs are <i>not required</i> to implement support for bulk content.
     *
     * @param repository the repository to retrieve a command factory for, used to determine the correct SCM
     *                   implementation to use
     * @return a command factory which will create commands operating on the specified repository
     * @throws FeatureUnsupportedScmException if the target SCM does not support bulk content
     * @throws UnavailableScmException        if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException        if the target SCM has been uninstalled or disabled
     * @see ScmBulkContentCommandFactory
     * @since 4.2
     */
    @Nonnull
    ScmBulkContentCommandFactory getBulkContentCommandFactory(@Nonnull Repository repository);

    /**
     * Retrieves an {@link ScmCommandFactory}, used to create {@link Command commands} for performing standard SCM
     * operations such as retrieving commits and viewing diffs.
     *
     * @param repository the repository to retrieve a command factory for, used to determine the correct SCM
     *                   implementation to use and to specify the repository created commands operate on
     * @return a command factory which will create commands operating on the specified repository
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     * @see ScmCommandFactory
     */
    @Nonnull
    ScmCommandFactory getCommandFactory(@Nonnull Repository repository);

    /**
     * Retrieves an {@link ScmCompareCommandFactory}, used to create {@link Command commands} tailored for
     * comparing refs.
     * <p>
     * This differs from the normal {@link #getCommandFactory(Repository)} in that any comparison will be rooted
     * into the closest common ancestor between the refs. In other words, any comparison will be <em>one sided</em>
     * and only includes the changes the {@link CompareRequest#getFromRef() source ref} would bring to the
     * {@link CompareRequest#getToRef() target ref} if they were merged.
     *
     * @param compareRequest the request describing the refs to compare
     * @return a command factory that will create commands allowing to compare two refs using their closest common ancestor
     * @throws FeatureUnsupportedScmException if the target SCM does not support comapring refs using their closest common ancestor
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     * @see ScmCommandFactory
     * @see ScmPullRequestCommandFactory
     */
    @Nonnull
    ScmCompareCommandFactory getCompareCommandFactory(@Nonnull CompareRequest compareRequest);

    /**
     * Retrieves an {@link ScmExtendedCommandFactory}, used to create commands for extended SCM functionality.
     * <p>
     * SCMs are <i>not required</i> to implement support for any of the commands on this factory, and implementing
     * support for any one command does not require supporting any other command. Attempting to use a command which
     * has not been implemented by the SCM will trigger a {@link FeatureUnsupportedScmException}. Callers should
     * {@link #isSupported(Repository, ScmFeature) check support} prior to using any command on this factory. Each
     * command is associated with its own {@link ScmFeature feature}. The mapping between features and commands is
     * documented on the {@link ScmExtendedCommandFactory} interface.
     * <p>
     * <b>Note</b>: Unlike other command factories, like the {@link #getPullRequestCommandFactory pull request
     * command factory}, calling this method will <i>never</i> throw a {@link FeatureUnsupportedScmException}.
     * Feature support is checked against each method on the returned factory, not against retrieving the factory
     * itself.
     *
     * @param repository the repository to retrieve a command factory for, used to determine the correct SCM
     *                   implementation to use
     * @return a command factory providing optional SCM functionality
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     * @see ScmExtendedCommandFactory
     * @since 4.6
     */
    @Nonnull
    ScmExtendedCommandFactory getExtendedCommandFactory(@Nonnull Repository repository);

    /**
     * Retrieves a {@link ScmHookHandlerFactory}, used to create {@link HookHandler hook
     * handlers} that manage scm hook callbacks.
     * <p>
     * SCMs are <i>not required</i> to implement support for SCM hooks.
     *
     * @param repository the repository for which to retrieve the Scm
     * @return the {@link ScmHookHandlerFactory} used to create hook handlers
     */
    @Nonnull
    ScmHookHandlerFactory getHookHandlerFactory(@Nonnull Repository repository);

    /**
     * Retrieves an {@link ScmMirrorCommandFactory}, used to create {@link Command commands} tailored for mirroring
     * repositories.
     * <p>
     * SCMs are <i>not required</i> to implement support for mirroring.
     *
     * @param repository the repository to retrieve a command factory for, used to determine the correct SCM
     *                   implementation to use
     * @return a command factory which will create commands operating on the specified repository
     * @throws FeatureUnsupportedScmException if the target SCM does not support mirroring
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     * @see ScmMirrorCommandFactory
     * @since 4.1
     */
    @Nonnull
    ScmMirrorCommandFactory getMirrorCommandFactory(@Nonnull Repository repository);

    /**
     * Retrieves an {@link ScmPullRequestCommandFactory}, used to create {@link Command commands} tailored for use
     * operating on pull requests.
     * <p>
     * SCMs are <i>not required</i> to implement support for pull requests.
     *
     * @param pullRequest the pull request to retrieve a command factory for, used to determine the correct SCM
     *                    implementation to use and to specify the pull request created commands operate on
     * @return a command factory which will create commands operating on the specified pull request
     * @throws FeatureUnsupportedScmException if the target SCM does not support pull requests
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     * @see ScmPullRequestCommandFactory
     */
    @Nonnull
    ScmPullRequestCommandFactory getPullRequestCommandFactory(@Nonnull PullRequest pullRequest);

    /**
     * Retrieves an {@link ScmRefCommandFactory}, used to create {@link Command commands} tailored for creating
     * {@link Branch branches} and {@link Tag tags}.
     * <p>
     * SCMs are <i>not required</i> to implement support for creating branches or tags.
     *
     * @throws FeatureUnsupportedScmException if the target SCM does not support mutable refs
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     * @see ScmFeature#REFS
     */
    @Nonnull
    ScmRefCommandFactory getRefCommandFactory(@Nonnull Repository repository);

    /**
     * Retrieves the name for the {@link Repository#getScmId() SCM} which powers the specified repository.
     *
     * @param repository the repository to retrieve the SCM name for
     * @return the name of the SCM powering the specified repository
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     */
    @Nonnull
    String getScmName(@Nonnull Repository repository);

    /**
     * Calculates the size (in bytes) for the specified repository. If the provided {@link Repository} does not exist,
     * its size is always {@code 0}.
     * <p>
     * Note: Calculating the size can be an expensive operation. The returned value may be a best effort estimation of
     * the repository size.
     *
     * @param repository the repository whose size should be calculated
     * @return the size of the specified repository in bytes
     */
    long getSize(@Nonnull Repository repository);

    /**
     * Tests whether the specified {@link Repository} is empty.
     *
     * @param repository the repository to check
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     * @return {@code true} if the repository is empty; otherwise, {@code false}
     */
    boolean isEmpty(@Nonnull Repository repository);

    /**
     * Tests whether the SCM for the specified {@link Repository} supports the requested {@link ScmFeature feature}.
     *
     * @param repository the repository to check
     * @param feature    the feature to test
     * @return {@code true} if the SCM for the specified repository supports the requested {@link ScmFeature feature};
     *         otherwise, {@code false}
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     */
    boolean isSupported(@Nonnull Repository repository, @Nonnull ScmFeature feature);

    /**
     * Tests whether the specified SCM supports the requested {@link ScmFeature feature}.
     *
     * @param scmId   identifier of the SCM to check
     * @param feature the feature to test
     * @return {@code true} if the SCM for the specified repository supports the requested {@link ScmFeature feature};
     *         otherwise, {@code false}
     * @throws UnavailableScmException if the target SCM is installed and enabled but unavailable for some reason
     * @throws UnsupportedScmException if the target SCM has been uninstalled or disabled
     */
    boolean isSupported(@Nonnull String scmId, @Nonnull ScmFeature feature);
}
