package com.atlassian.bitbucket.repository;

import com.atlassian.bitbucket.event.repository.*;
import com.atlassian.bitbucket.project.NoSuchProjectException;
import com.atlassian.bitbucket.project.PersonalProject;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.scm.FeatureUnsupportedScmException;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.util.NamedLink;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;

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

/**
 * Describes a service for interacting with repositories.
 */
public interface RepositoryService extends RepositorySupplier {

    /**
     * Get the number of repositories that belong to a project.
     *
     * @param project the project
     * @return the number of repositories in the project.
     */
    int countByProject(@Nonnull Project project);

    /**
     * Create a new repository. The repository's {@link Repository#getSlug() slug} will be derived from the name. Both
     * the name <i>and</i> the generated slug must be unique within the {@link RepositoryCreateRequest#getProject()
     * project}.
     * <p>
     * Before the repository is created, a {@link RepositoryCreationRequestedEvent creation
     * requested event} is raised. This event is cancelable, allowing plugins to prevent repository creation based on
     * arbitrary considerations. If <i>any</i> listener cancels creation, the repository will not be created. It does
     * not require consensus among all registered listeners. If no listeners cancel, the repository will be created.
     * <p>
     * Events raised:
     * <ul>
     *     <li>{@link RepositoryCreationRequestedEvent RepositoryCreationRequestedEvent}</li>
     *     <li>{@link RepositoryCreatedEvent RepositoryCreatedEvent}</li>
     * </ul>
     *
     * @param request describes the repository to create
     * @return the new repository
     * @throws RepositoryCreationCanceledException if creation is canceled by an event listener
     */
    @Nonnull
    Repository create(@Nonnull RepositoryCreateRequest request);

    /**
     * Delete the repository. Both the on-disk repository and the database record are deleted. Any child and
     * parent relationships are updated. The repository will be scheduled for deletion, which may happen after this
     * call completes.
     * <p>
     * Before the repository is deleted, a {@link RepositoryDeletionRequestedEvent deletion
     * requested event} is raised. This event is cancelable, allowing plugins to prevent repository deletion based on
     * arbitrary considerations. If <i>any</i> listener cancels deletion, the repository will not be deleted. It does
     * not require consensus among all registered listeners. If no listeners cancel, the repository will be deleted.
     * <p>
     * Events raised:
     * <ul>
     *     <li>{@link RepositoryDeletionRequestedEvent RepositoryDeletionRequestedEvent}</li>
     *     <li>{@link RepositoryDeletedEvent RepositoryDeletedEvent}</li>
     * </ul>
     *
     * @param repository the repository to be deleted.
     * @throws RepositoryDeletionCanceledException if deletion is canceled by an event listener
     */
    void delete(@Nonnull Repository repository);

    /**
     * Find all repositories in the system in the default namespace.
     *
     * @param pageRequest the page of repositories to return.
     * @return a page of repositories.
     */
    @Nonnull
    Page<Repository> findAll(@Nonnull PageRequest pageRequest);

    /**
     * Retrieves a page of {@link Repository repositories} which have been forked from the specified {@code origin}.
     * <p>
     * The returned list is not guaranteed to be complete; it will be filtered to contain only those repositories
     * which the requesting user has access to. This may not include forks in other users' personal projects, for
     * example.
     *
     * @param origin      the origin repository for which forks should be found
     * @param pageRequest the bounds of the page
     * @return a page containing zero or more forks for the specified origin
     */
    @Nonnull
    Page<Repository> findByOrigin(@Nonnull Repository origin, @Nonnull PageRequest pageRequest);

    /**
     * Retrieves a page of {@link Repository repositories} owned by the specified {@link ApplicationUser user}. These
     * are {@link PersonalProject personal} repositories, which may be personal forks of other repositories or
     * the user's own repositories.
     * <p>
     * Only repositories <i>visible to the current user</i> will be returned, which may not be the full set of
     * repositories owned by the specified user.
     *
     * @param owner       the user to retrieve personal repositories for
     * @param pageRequest the bounds of the page
     * @return a page containing zero or more personal repositories for the specified user
     */
    @Nonnull
    Page<Repository> findByOwner(@Nonnull ApplicationUser owner, @Nonnull PageRequest pageRequest);

    /**
     * List the names of the repositories in the project with the given key and with a default (null) namespace.
     *
     * @param projectKey the project for which to list the repositories.
     * @param pageRequest The page parameters for this query.
     * @return the request page of repositories within the specified project, which may be empty but never {@code null}
     * @throws NoSuchProjectException if the specified project does not exist
     */
    @Nonnull
    Page<Repository> findByProjectKey(@Nonnull String projectKey, @Nonnull PageRequest pageRequest)
            throws NoSuchProjectException;

    /**
     *  List the names of the repositories in the project with the given key and namespace. Unless in
     *  {@link com.atlassian.bitbucket.server.ApplicationMode#MIRROR mirror mode} the namespace specified should always
     *  be {@code null}.
     *
     * @param projectNamespace the namespace of the project for which to list the repositories.
     * @param projectKey the project for which to list the repositories.
     * @param pageRequest The page parameters for this query.
     * @return the request page of repositories within the specified project, which may be empty but never {@code null}
     * @throws NoSuchProjectException if the specified project does not exist in the specified namespace
     * @since 4.6
     */
    @Nonnull
    Page<Repository> findByProjectKey(@Nullable String projectNamespace, @Nonnull String projectKey, @Nonnull PageRequest pageRequest)
            throws NoSuchProjectException;

    /**
     * Retrieves the current user's personal fork of the specified {@link Repository repository}. If the current
     * user has not yet forked the repository, or has forked it with a different {@link Repository#getName() name},
     * {@code null} will be returned.
     *
     * @param repository the repository to retrieve the current user's personal fork for
     * @return the current user's personal fork of the specified repository, or {@code null} if the current user
     *         has not forked the repository or has forked it with a different name
     */
    @Nullable
    Repository findPersonalFork(@Nonnull Repository repository);

    /**
     * Retrieves a page of {@link Repository repositories} which belong to the same {@link Repository#getHierarchyId()
     * hierarchy} as the specified repository. The specified repository will never itself be returned, even though it
     * is also part of the hierarchy.
     * <p>
     * The related repositories returned are not guaranteed to be the complete hierarchy; they will be filtered to
     * contain only those repositories which the requesting user has access to. This may not include other users'
     * personal forks of the repository, for example.
     *
     * @param repository  the repository to retrieve related repositories for
     * @param pageRequest the bounds of the page
     * @return a page containing zero or more repositories from the same hierarchy as the specified repository
     */
    @Nonnull
    Page<Repository> findRelated(@Nonnull Repository repository, @Nonnull PageRequest pageRequest);

    /**
     * Create a new repository by forking from an existing repository. As with {@link #create(RepositoryCreateRequest)
     * creating} a repository from scratch, the fork's {@link Repository#getSlug() slug} will be derived from its name,
     * and both the name and the slug must be unique within the target project.
     * <ul>
     *     <li>If no explicit {@link RepositoryForkRequest#getProject() project} is provided, the fork will be created
     *     in the forking user's {@link PersonalProject personal project}.</li>
     *     <li>If no explicit {@link RepositoryForkRequest#getName() name} is provided, the
     *     {@link RepositoryForkRequest#getParent() parent} repository's name is retained for the fork.</li>
     * </ul>
     * Before the fork is created, a {@link RepositoryForkRequestedEvent fork requested event}
     * is raised. This event is cancelable, allowing plugins to prevent fork creation based on arbitrary considerations.
     * If <i>any</i> listener cancels forking, the fork will not be created. It does not require consensus among all
     * registered listeners. If no listeners cancel, the fork will be created.
     * <p>
     * Events raised:
     * <ul>
     *     <li>{@link RepositoryForkRequestedEvent RepositoryForkRequestedEvent}</li>
     *     <li>{@link RepositoryForkedEvent RepositoryForkedEvent}</li>
     * </ul>
     *
     * @param request describes the repository to fork, as well as the project in which the fork should be created and
     *                the name that should be assigned to it
     * @return the newly-forked repository
     * @throws RepositoryForkCanceledException if forking is canceled by an event listener
     * @throws FeatureUnsupportedScmException if the SCM for the parent repository does not
     *                                                                support forking
     * @throws javax.validation.ConstraintViolationException if the fork's slug matches another repository within the
     *                                                       user's personal project
     */
    @Nonnull
    Repository fork(@Nonnull RepositoryForkRequest request);

    /**
     * Retrieves a {@link Repository} by its {@link Repository#getId() ID}.
     *
     * @param id the repository's ID
     * @return the request repository, or {@code null} if there is no repository with the specified ID or the current
     *         user does not have access to it
     */
    @Nullable
    @Override
    Repository getById(int id);

    /**
     * Retrieves a {@link Repository} by its {@link Repository#getSlug() slug}. Slugs are only unique within a given
     * {@link Project}, so the {@link Project#getKey() project key} is also required.
     *
     * @param projectKey the {@link Project#getKey() key} of the project to search in
     * @param slug       the {@link Repository#getSlug() slug} of the repository to search for
     * @return the repository, or {@code null} if no repository matches the specified slug within the specified project
     */
    @Nullable
    @Override
    Repository getBySlug(@Nonnull String projectKey, @Nonnull String slug);

    /**
     * Retrieves a {@link Repository} by its {@link Repository#getSlug() slug}. Slugs are only unique within a given
     * {@link Project}, so the {@link Project#getKey() project key} and {@link Project#getNamespace() project namespace}
     * are required. Unless in {@link com.atlassian.bitbucket.server.ApplicationMode#MIRROR mirror mode} the namespace
     * specified should always be {@code null}.
     *
     * @param projectNamespace the {@link Project#getNamespace() namespace} of the project to search in
     * @param projectKey the {@link Project#getKey() key} of the project to search in
     * @param slug       the {@link Repository#getSlug() slug} of the repository to search for
     * @return the repository, or {@code null} if no repository matches the specified slug within the specified project
     * and namespace
     */
    @Nullable
    @Override
    Repository getBySlug(@Nullable String projectNamespace, @Nonnull String projectKey, @Nonnull String slug);

    /**
     * Retrieves {@link NamedLink links} that can be used to clone the supplied
     * {@link Repository repository} via the repository's {@link Repository#getScmId() SCM's} supported protocols.
     * <p>
     * Where appropriate to the underlying SCM type, SCM protocols may return link URLs that are customised to the
     * {@link RepositoryCloneLinksRequest#getUser() supplied user} or the
     * {@link RepositoryCloneLinksRequest#isUseCurrentUser() currently authenticated user} (if any). This might be
     * achieved, for instance, by inserting the {@link ApplicationUser#getName() user's name} in the
     * URL's authority part. Whether and how this customisation takes place is entirely up to each protocol.
     * <p>
     * Each link has a {@link NamedLink#getName() name} that indicates the protocol it supports
     * e.g. {@code "ssh"} or {@code "http"}.
     *
     * @param request the request used to generate clone links
     * @return the set of clone links for the supplied repository
     */
    @Nonnull
    Set<NamedLink> getCloneLinks(@Nonnull RepositoryCloneLinksRequest request);

    /**
     * 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);

    /**
     * Retrieves a flag indicating whether the specified repository is empty or not. While the definition of "empty" is
     * SCM-specific, it generally distills to a repository which contains no branches or tags.
     *
     * @param repository the repository to check
     * @return {@code true} if the repository is empty; otherwise {@code false}
     */
    boolean isEmpty(@Nonnull Repository repository);

    /**
     * Retrieves a flag indicating whether the system has been configured to allow forking repositories.
     * <p>
     * In addition to this system-wide setting, each {@link Repository} may be explicitly configured as
     * {@link Repository#isForkable()} or not. If forking is disabled system-wide, <i>even repositories
     * configured as forkable cannot be forked</i>. By the same token, even if forking is enabled system-wide,
     * any repository which is explicitly configured to disallow forks cannot be forked.
     *
     * @return {@code true} if the system is configured to allow repository forking; otherwise, {@code false}
     * @see Repository#isForkable()
     */
    boolean isForkingEnabled();

    /**
     * If a create operation fails, calling this method will clean up the broken repository and try again.
     * <p>
     * The repository must be in {@link Repository.State#INITIALISATION_FAILED INITIALISATION_FAILED}
     * {@link Repository#getState() state} in order to retry creation.
     *
     * @param repository the repository to retry
     * @return the new repository
     * @throws IllegalRepositoryStateException if the repository is not in the required state
     */
    @Nonnull
    Repository retryCreate(@Nonnull Repository repository);

    /**
     * Searches for {@link Repository repositories} that match the provided {@link RepositorySearchRequest request}.
     *
     * @param request     a request object describing the repositories to return
     * @param pageRequest the bounds of the page
     * @return a page containing 0 or more {@link Repository repositories} that match the provided
     *         {@link RepositorySearchRequest criteria}
     * @see RepositorySearchRequest
     */
    Page<Repository> search(@Nonnull RepositorySearchRequest request, @Nonnull PageRequest pageRequest);

    /**
     * Update the metadata of a repository. The possible updates are described in detail by the the
     * {@link RepositoryUpdateRequest} documentation.
     * <p>
     * Repository {@link Repository#getSlug() slugs} are derived from their {@link Repository#getName() names}.
     * Updating a repository's name may change its slug, which will also change all browser and clone URLs for the
     * affected repository. <i>Old URLs will no longer work after such an update.</i>
     * <p>
     * Before the repository is updated, a {@link RepositoryModificationRequestedEvent
     * modification requested event} is raised. This event is cancelable, allowing plugins to prevent repository
     * modification based on arbitrary considerations. If <i>any</i> listener cancels modification, the repository
     * will not be updated. It does not require consensus among all registered listeners. If no listeners cancel,
     * the repository will be updated with the provided {@code name}.
     * <p>
     * Events raised:
     * <ul>
     *     <li>{@link RepositoryModificationRequestedEvent RepositoryModificationRequestedEvent}</li>
     *     <li>{@link RepositoryModifiedEvent RepositoryModifiedEvent}</li>
     * </ul>
     *
     * @param request describes the repository to update and the updates to apply
     * @return the updated repository instance
     * @throws RepositoryModificationCanceledException if modification is canceled by an event listener
     */
    @Nonnull
    Repository update(@Nonnull RepositoryUpdateRequest request);
}
