package com.atlassian.bitbucket.pull;

import com.atlassian.bitbucket.event.pull.PullRequestMergedEvent;
import com.google.common.collect.ImmutableMap;

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

/**
 * Describes a request to {@link PullRequestService#merge(PullRequestMergeRequest) merge} a {@link PullRequest}.
 */
public class PullRequestMergeRequest extends AbstractPullRequestRequest {

    private final Map<String, Object> context;
    private final String message;
    private final String strategyId;
    private final int version;

    private PullRequestMergeRequest(Builder builder) {
        super(builder);

        context = builder.context.build();
        message = builder.message;
        strategyId = builder.strategyId;
        version = builder.version;
    }

    /**
     * Retrieves additional supplementary context for the merge request. This context is passed <i>as-is</i> to
     * all of the {@code MergeRequestCheck}s run before the merge is performed and is also included in the
     * {@link PullRequestMergedEvent PullRequestMergedEvent} raised after the merge
     * is completed.
     *
     * @return a map of additional context, which may be empty but is never {@code null}
     */
    @Nonnull
    public Map<String, Object> getContext() {
        return context;
    }

    /**
     * Retrieves the commit message to use when merging the pull request.
     * <p>
     * While this is part of the API for merging pull requests, the underlying SCM performing the merge ultimately
     * decides the exact shape of the final commit message. For example, when performing a <i>fast-forward</i> "merge"
     * in Git, any commit message provided here is <i>ignored</i> because the "merge" does not actually create a new
     * commit. Additionally, the SCM may, at its own discretion, augment the provided commit message with more detail.
     * For example, the Git SCM includes the summary from each merged commit in the message.
     * <p>
     * The system has a base message that is <i>always</i> applied when merging pull requests. If a message is provided
     * here, it will be appended to that base message.
     *
     * @return the commit message for the pull request
     */
    @Nullable
    public String getMessage() {
        return message;
    }

    /**
     * Retrieves the {@link PullRequestMergeStrategy#getId ID} of the strategy to use when merging the pull request.
     * If a strategy has not been specified, the {@link PullRequestMergeConfig#getDefaultStrategy default strategy}
     * for the {@link PullRequest#getToRef target repository} is used. Only {@link PullRequestMergeStrategy#isEnabled
     * enabled} strategies may be requested.
     *
     * @return the strategy to use when merging the pull request, or {@code null} to use the default
     * @since 4.9
     */
    @Nullable
    public String getStrategyId() {
        return strategyId;
    }

    /**
     * Retrieves the expected {@link PullRequest#getVersion version} for the pull request. If the pull request's
     * actual version does not match, a {@link PullRequestOutOfDateException} will be thrown to indicate that the
     * merge was requested on stale data.
     *
     * @return the pull request version to merge
     */
    public int getVersion() {
        return version;
    }

    public static class Builder extends AbstractBuilder<Builder> {

        private final ImmutableMap.Builder<String, Object> context;
        private final int version;

        private String message;
        private String strategyId;

        public Builder(@Nonnull PullRequest pullRequest) {
            super(pullRequest);

            this.version = pullRequest.getVersion();

            context = ImmutableMap.builder();
        }

        public Builder(int repositoryId, long pullRequestId, int version) {
            super(repositoryId, pullRequestId);

            this.version = version;

            context = ImmutableMap.builder();
        }

        @Nonnull
        public PullRequestMergeRequest build() {
            return new PullRequestMergeRequest(this);
        }

        @Nonnull
        public Builder context(@Nonnull String key, @Nonnull Object value) {
            context.put(key, value);

            return self();
        }

        @Nonnull
        public Builder context(@Nonnull Map<String, Object> value) {
            context.putAll(value);

            return self();
        }

        @Nonnull
        public Builder message(@Nullable String value) {
            message = value;

            return self();
        }

        /**
         * @param value the ID of the strategy to use when merging the pull request, or {@code null} to use the
         *              {@link PullRequest#getToRef target repository}'s default strategy
         * @return {@code this}
         * @since 4.9
         */
        @Nonnull
        public Builder strategyId(@Nullable String value) {
            strategyId = value;

            return self();
        }

        @Nonnull
        @Override
        protected Builder self() {
            return this;
        }
    }
}
