package com.atlassian.bitbucket.internal.mirroring.mirror;

import com.atlassian.bitbucket.mirroring.mirror.NoSuchUpstreamException;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamServer;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamServerType;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamService;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.RefChange;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.server.IncompatibleApplicationModeException;
import com.atlassian.util.concurrent.Promise;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public interface InternalUpstreamService extends UpstreamService {

    /**
     * {@inheritDoc}
     */
    @Nullable
    @Override
    InternalUpstreamServer get();

    /**
     * @param upstreamId the ID of the upstream to find.
     * @return the upstream with the passed {@code upstreamId} or {@link Optional#empty()} if no such upstream exists.
     */
    @Nonnull
    Optional<InternalUpstreamServer> getById(@Nonnull String upstreamId);

    /**
     * Create an {@link UpstreamServerType#BITBUCKET_SERVER} upstream server for the passed URL.
     * Only one upstream may be registered. Attempting to create more than one upstream will result
     * in an {@code IllegalStateException}.
     *
     * @param url the URL of the upstream.
     * @throws IllegalStateException if an upstream is already registered.
     * @throws IllegalArgumentException if url is not a valid URL.
     */
    @Nonnull
    InternalUpstreamServer createForServer(@Nonnull String url) throws IllegalStateException;

    /**
     * Create an {@link UpstreamServerType#BITBUCKET_CLOUD} upstream server.
     * Only one upstream may be registered. Attempting to create more than one upstream will result
     * in a {@code IllegalStateException}.
     *
     * @throws IllegalStateException if an upstream is already registered or if the Cloud URL
     * is not correctly configured in the distribution.
     */
    @Nonnull
    InternalUpstreamServer createForCloud() throws IllegalStateException;

    /**
     * @return {@code true} if the current instance is configured to be a mirror server
     */
    boolean isMirror();

    /**
     * @param upstreamId the ID of the {@link UpstreamServer#getId() upstream server} to retrieve the local mapping for
     * @param upstreamProjectIds the {@link Project#getId() Project IDs} to retrieve the local mapping for
     * @return a mapping of {@code upstreamProjectIds} to their local counterpart
     */
    @Nonnull
    Map<String, Integer> mapToLocalProjects(@Nonnull String upstreamId, @Nonnull Iterable<String> upstreamProjectIds);

    /**
     * @param upstreamId the ID of the {@link UpstreamServer#getId() upstream server} to retrieve the local mapping for
     * @param upstreamRepositoryIds the {@link Repository#getId() Repository IDs} to retrieve the local mapping for
     * @return a mapping of {@code upstreamRepositoryIds} to their local counterpart
     */
    @Nonnull
    Map<String, Integer> mapToLocalRepositories(@Nonnull String upstreamId, @Nonnull Iterable<String> upstreamRepositoryIds);

    /**
     * Marks the current instance as having been installed with the passed parameters.
     *
     * @param installedParameters the parameters passed from the upstream to the mirror on install.
     * @return the upstream server
     * @throws NoSuchUpstreamException if the upstream server is not found
     */
    @Nonnull
    UpstreamServer onInstalled(@Nonnull UpstreamInstallationParameters installedParameters);

    /**
     * Marks the upstream project as having been modified. The local project is immediately brought into sync with
     * the updated details if the caller is authenticated as the upstream service user.
     *
     * @param upstreamId the ID of the {@link UpstreamServer}
     * @param externalProject the external project
     * @throws IncompatibleApplicationModeException if this is not a mirror
     * @throws NoSuchUpstreamException if this is a mirror and is not yet registered with the specified upstream server
     */
    void onProjectModified(@Nonnull String upstreamId, @Nonnull ExternalProject externalProject);

    /**
     * Marks the upstream repository as having been changed in some way (created, renamed, or pushed to) and starts
     * synchronisation with the upstream server to detect the current state.
     *
     * @param upstreamId the ID of the {@link UpstreamServer}
     * @param externalRepository the external repository
     * @throws IncompatibleApplicationModeException if this is not a mirror
     * @throws NoSuchUpstreamException if this is a mirror and is not yet registered with the specified upstream server
     */
    void onRepositoryContentsChanged(@Nonnull String upstreamId, @Nonnull MinimalExternalRepository externalRepository,
                                     @Nonnull List<RefChange> changes);

    /**
     * Notifies the service the upstream repository has been created. The local repository is immediately brought into
     * sync with the updated details if the caller is authenticated as the upstream service user and the upstream
     * settings include the repository's project in those that should be mirrored.
     *
     * @param upstreamId the ID of the {@link UpstreamServer}
     * @param externalRepository the external repository
     * @throws IncompatibleApplicationModeException if this is not a mirror
     * @throws NoSuchUpstreamException if this is a mirror and is not yet registered with the specified upstream server
     */
    void onRepositoryCreated(@Nonnull String upstreamId, @Nonnull MinimalExternalRepository externalRepository);

    /**
     * Marks the upstream repository as having been deleted which either removes it locally immediately if the caller
     * is authenticated as the upstream service user.
     *
     * @param upstreamId the ID of the {@link UpstreamServer}
     * @param externalRepository the external repository
     * @throws IncompatibleApplicationModeException if this is not a mirror
     * @throws NoSuchUpstreamException if this is a mirror and is not yet registered with the specified upstream server
     */
    void onRepositoryDeleted(@Nonnull String upstreamId, @Nonnull MinimalExternalRepository externalRepository);

    /**
     * Marks the upstream repository as having been modified. The local repository is immediately brought into sync with
     * the updated details if the caller is authenticated as the upstream service user.
     *
     * @param upstreamId the ID of the {@link UpstreamServer}
     * @param externalRepository the external repository
     * @throws IncompatibleApplicationModeException if this is not a mirror
     * @throws NoSuchUpstreamException if this is a mirror and is not yet registered with the specified upstream server
     */
    void onRepositoryModified(@Nonnull String upstreamId, @Nonnull MinimalExternalRepository externalRepository);

    /**
     * Marks the current instance as having been uninstalled as a mirror at the upstream server identified by {@code upstreamId}
     *
     * @param upstreamId the ID of the {@link UpstreamServer}
     * @throws NoSuchUpstreamException if the upstream server is not found
     */
    void onUninstalled(@Nonnull String upstreamId);

    /**
     * Update the local state with the upstream state of the server
     *
     * @param upstream the upstream server
     * @return a promise for the updated upstream server
     * @throws NoSuchUpstreamException if the upstream server is not found
     */
    Promise<UpstreamServer> refresh(@Nonnull UpstreamServer upstream);

    /**
     * Registers the current instance at the configured upstream server, if not yet integrated
     *
     * @throws MirrorRegistrationFailedException if registration fails for some reason
     * @throws InvalidUpstreamServerUrlException if the configured URL is invalid
     */
    Promise<Void> register() throws MirrorRegistrationFailedException;
}
