package com.atlassian.stash.internal.merge;

import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.FeatureUnsupportedScmException;
import com.atlassian.bitbucket.scm.ScmFeature;
import com.atlassian.bitbucket.validation.ArgumentValidationException;

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

/**
 * A helper for managing {@link MergeConfig merge configuration}.
 * <p>
 * Merge configuration can be managed at 3 different levels, listed from most specific to most generic:
 * <ol>
 *     <li>{@link MergeConfigType#REPOSITORY Repository}</li>
 *     <li>{@link MergeConfigType#PROJECT Project} and SCM
 *     <ul>
 *         <li>The SCM must also be considered because projects can contain multiple repositories which
 *         may not always use the same SCM</li>
 *     </ul>
 *     </li>
 *     <li>{@link MergeConfigType#SCM}</li>
 * </ol>
 * When retrieving configuration, if configuration has not been applied at the requested level, less specific
 * levels are also checked. The most specific level <i>at or below</i> the requested level is returned. If no
 * configuration has been applied, SCM-level {@link MergeConfigType#DEFAULT defaults} are returned.
 *
 * @since 4.9
 */
public interface MergeConfigHelper {

    /**
     * Retrieves merge configuration for the specified {@link Project project} and SCM. If configuration has
     * not been applied for the project, the {@link #getForScm SCM} is checked.
     * <p>
     * Projects may contain repositories using different SCMs, which will support different strategies, so
     * project-level configuration is further qualified by SCM.
     *
     * @param project the project to retrieve configuration for
     * @param scmId   the SCM to retrieve configuration for
     * @return the {@link MergeConfigType#PROJECT project} or {@link MergeConfigType#SCM SCM}-level configuration,
     *         or {@link MergeConfigType#DEFAULT SCM defaults} if configuration has not been applied
     * @throws FeatureUnsupportedScmException if the SCM does not support {@link ScmFeature#MERGE_STRATEGIES
     *                                        merge strategies}
     * @throws NullPointerException if the {@code project} or {@code scmId} is {@code null}
     */
    @Nonnull
    MergeConfig getForProjectAndScm(@Nonnull Project project, @Nonnull String scmId);

    /**
     * Retrieves merge configuration for the specified {@link Repository repository}. If configuration has not
     * been applied for the repository, the {@link #getForProjectAndScm project} and {@link #getForScm SCM} are
     * also checked.
     *
     * @param repository the repository to retrieve configuration for
     * @return the {@link MergeConfigType#REPOSITORY repository}, {@link MergeConfigType#PROJECT project} or
     *         {@link MergeConfigType#SCM SCM}-level configuration, or {@link MergeConfigType#DEFAULT SCM defaults}
     *         if configuration has not been applied
     * @throws FeatureUnsupportedScmException if the SCM does not support {@link ScmFeature#MERGE_STRATEGIES
     *                                        merge strategies}
     * @throws NullPointerException if the {@code repository} is {@code null}
     */
    @Nonnull
    MergeConfig getForRepository(@Nonnull Repository repository);

    /**
     * Retrieves merge configuration for the specified SCM. If configuration has not been applied for the SCM,
     * the SCM's defaults are returned.
     *
     * @param scmId the SCM to retrieve configuration for
     * @return the {@link MergeConfigType#SCM SCM}-level configuration, or {@link MergeConfigType#DEFAULT SCM
     *         defaults} if configuration has not been applied
     * @throws FeatureUnsupportedScmException if the SCM does not support {@link ScmFeature#MERGE_STRATEGIES
     *                                        merge strategies}
     * @throws NullPointerException if the {@code scmId} is {@code null}
     */
    @Nonnull
    MergeConfig getForScm(@Nonnull String scmId);

    /**
     * Sets merge configuration for the specified {@link Project project} and SCM.
     *
     * @param project           the project to apply the configuration to
     * @param scmId             the SCM to apply the configuration to
     * @param defaultStrategyId the default strategy to use
     * @param strategyIds       the strategies to enable, which must include the default
     * @throws ArgumentValidationException if the default strategy is not enabled, or if any enabled strategy is
     *                                     not supported by the SCM
     * @throws FeatureUnsupportedScmException if the SCM does not support {@link ScmFeature#MERGE_STRATEGIES
     *                                        merge strategies}
     * @throws NullPointerException if any of the parameters are {@code null}
     */
    void setForProjectAndScm(@Nonnull Project project, @Nonnull String scmId, @Nonnull String defaultStrategyId,
                             @Nonnull Set<String> strategyIds);

    /**
     * Sets merge configuration for the specified {@link Repository repository}.
     *
     * @param repository        the repository to apply the configuration to
     * @param defaultStrategyId the default strategy to use
     * @param strategyIds       the strategies to enable, which must include the default
     * @throws ArgumentValidationException if the default strategy is not enabled, or if any enabled strategy is
     *                                     not supported by the SCM
     * @throws FeatureUnsupportedScmException if the SCM does not support {@link ScmFeature#MERGE_STRATEGIES
     *                                        merge strategies}
     * @throws NullPointerException if any of the parameters are {@code null}
     */
    void setForRepository(@Nonnull Repository repository, @Nonnull String defaultStrategyId,
                          @Nonnull Set<String> strategyIds);

    /**
     * Sets merge configuration for the specified SCM.
     *
     * @param scmId             the SCM to apply the configuration to
     * @param defaultStrategyId the default strategy to use
     * @param strategyIds       the strategies to enable, which must include the default
     * @throws ArgumentValidationException if the default strategy is not enabled, or if any enabled strategy is
     *                                     not supported by the SCM
     * @throws FeatureUnsupportedScmException if the SCM does not support {@link ScmFeature#MERGE_STRATEGIES
     *                                        merge strategies}
     * @throws NullPointerException if any of the parameters are {@code null}
     */
    void setForScm(@Nonnull String scmId, @Nonnull String defaultStrategyId, @Nonnull Set<String> strategyIds);
}
