package it.com.atlassian.stash.pull;

import com.atlassian.bitbucket.pull.PullRequestState;
import com.atlassian.bitbucket.test.PullRequestTestHelper;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.webdriver.bitbucket.page.PullRequestOverviewPage;
import com.google.common.collect.Sets;
import it.com.atlassian.stash.BaseBrowserFuncTest;
import org.junit.BeforeClass;
import org.springframework.core.io.ClassPathResource;

import java.util.Arrays;
import java.util.Set;

public abstract class BasePullRequestFuncTest extends BaseBrowserFuncTest {

    protected static final String PROJECT_KEY = "PR_TEST";
    protected static final String REPO_SLUG = "pr-resources";
    protected static final String PROJECT_NAME = "PR Test Project";
    protected static final String REPO_NAME = "pr-resources";
    protected static final String PULL_REQUEST_DESCRIPTION = "Pull request description text.";

    protected static final String DECLINED_PULL_REQUEST_TITLE = "This is a declined PR";
    protected static final String MERGED_PULL_REQUEST_TITLE = "This is a merged PR";
    protected static final String OPEN_PULL_REQUEST_TITLE = "This is an open PR";

    protected static final String BRANCH_THAT_DIFFER_BY_ONE_FILE_SRC = "branch_that_differ_by_one_file_src";
    protected static final String BRANCH_THAT_HAS_DIFFERENT_BINARY_FILES_WITH_SAME_NAME_ADDED_SRC = "branch_that_has_different_binary_files_with_same_name_added_src";
    protected static final String BRANCH_THAT_HAS_DIFFERENT_BINARY_FILES_WITH_SAME_NAME_ADDED_TRGT = "branch_that_has_different_binary_files_with_same_name_added_trgt";
    protected static final String BRANCH_THAT_HAS_FILE_MOVED_TO_SAME_FOLDERS_SRC = "branch_that_has_file_moved_to_same_folders_src";
    protected static final String BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_SRC = "branch_that_has_file_permissions_changed_src";
    protected static final String BRANCH_THAT_HAS_FILE_PERMISSIONS_CHANGED_TRGT = "branch_that_has_file_permissions_changed_trgt";
    protected static final String BRANCH_THAT_HAS_SAME_BINARY_FILE_MODIFIED_SRC = "branch_that_has_same_binary_file_modified_src";
    protected static final String BRANCH_THAT_HAS_SAME_BINARY_FILE_MODIFIED_TRGT = "branch_that_has_same_binary_file_modified_trgt";
    protected static final String BRANCH_THAT_HAS_SAME_BINARY_FILE_MODIFIED_ON_BOTH_SRC = "branch_that_has_same_binary_file_modified_on_both_src";
    protected static final String BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_SRC = "branch_that_has_same_file_modified_and_moved_src";
    protected static final String BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_SRC_MERGE = "branch_that_has_same_file_modified_and_moved_src_merge";
    protected static final String BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_TRGT = "branch_that_has_same_file_modified_and_moved_trgt";
    protected static final String BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_TRGT_MERGE = "branch_that_has_same_file_modified_and_moved_trgt_merge";
    protected static final String BRANCH_THAT_HAS_SAME_FILE_RENAMED_ON_BOTH_SRC = "branch_that_has_same_file_renamed_on_both_src";
    protected static final String BRANCH_THAT_HAS_SAME_TEXT_FILE_MODIFIED_SRC = "branch_that_has_same_text_file_modified_src";
    protected static final String BRANCH_THAT_HAS_SAME_TEXT_FILE_MODIFIED_TRGT = "branch_that_has_same_text_file_modified_trgt";
    protected static final String BRANCH_WITH_AUTOMERGE_CONFLICT_SRC = "automerge_conflict/1.0";
    protected static final String BRANCH_WITH_AUTOMERGE_CONFLICT_TRGT = "automerge_conflict/2.0";
    protected static final String MASTER = "master";

    protected static final String AUTHOR_1 = "pr_author1";
    protected static final String AUTHOR_1_DISPLAY_NAME = "PR Test User <script>alert('xss')</script>";
    protected static final String AUTHOR_1_EMAIL_ADDRESS = "pr_author@fake-domain.org";

    protected static final String REVIEWER_1 = "pr_reviewer1";
    protected static final String REVIEWER_2 = "pr_reviewer2";
    protected static final String REVIEWER_UNLICENSED = "pr_reviewer_unlicensed";
    protected static final String REVIEWER_NO_REPOSITORY_ACCESS = "pr_reviewer_no_repo_access";

    private static final String GIT_PR_RESOURCES_ZIP = "git/pull-requests.zip";

    @BeforeClass
    public static void setUpClassTestData() throws Exception {
        getClassTestContext()
                .project(PROJECT_KEY, PROJECT_NAME)
                .repository(PROJECT_KEY, REPO_SLUG, new ClassPathResource(GIT_PR_RESOURCES_ZIP), dir -> {
                    // Create an extra branch for merging which can't be re-used
                    dir.git("branch", BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_SRC_MERGE, BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_SRC);
                    dir.git("branch", BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_TRGT_MERGE, BRANCH_THAT_HAS_SAME_FILE_MODIFIED_AND_MOVED_TRGT);
                    return null;
                })
                .user(AUTHOR_1, AUTHOR_1_DISPLAY_NAME, AUTHOR_1, AUTHOR_1_EMAIL_ADDRESS)
                .user(REVIEWER_1)
                .user(REVIEWER_2)
                .user(REVIEWER_UNLICENSED, false)
                .user(REVIEWER_NO_REPOSITORY_ACCESS)
                .noFeatureDiscovery(AUTHOR_1, AUTHOR_1)
                .projectPermission(PROJECT_KEY, AUTHOR_1, Permission.PROJECT_READ)
                .repositoryPermission(PROJECT_KEY, REPO_SLUG, AUTHOR_1, Permission.REPO_WRITE)
                .repositoryPermission(PROJECT_KEY, REPO_SLUG, REVIEWER_1, Permission.REPO_READ)
                .repositoryPermission(PROJECT_KEY, REPO_SLUG, REVIEWER_2, Permission.REPO_READ)
                .repositoryPermission(PROJECT_KEY, REPO_SLUG, REVIEWER_UNLICENSED, Permission.REPO_READ);
    }

    /**
     * Create a pull request.
     *
     * @param builder {@link PullRequestBuilder builder} for the pull request, which has default values for everything
     *                except sourceBranch and targetBranch
     * @return the pull request created
     */
    protected PullRequestTestHelper createPullRequest(PullRequestTestHelper.Builder builder) {
        return createPullRequest(builder, PullRequestState.OPEN);
    }

    /**
     * Create a pull request.
     *
     * @param builder {@link PullRequestBuilder builder} for the pull request, which has default values for everything
     *                except sourceBranch and targetBranch
     * @param state state of pull request
     * @return the pull request created
     */
    protected PullRequestTestHelper createPullRequest(PullRequestTestHelper.Builder builder, PullRequestState state) {
        switch (state) {
            case MERGED:
                builder.title(MERGED_PULL_REQUEST_TITLE);
                break;
            case DECLINED:
                builder.title(DECLINED_PULL_REQUEST_TITLE);
                break;
        }

        PullRequestTestHelper pullRequest = testContext.pullRequest(builder);
        switch (state) {
            case MERGED:
                pullRequest.mergeSafe();
                break;
            case DECLINED:
                pullRequest.declineSafe();
                break;
        }

        return pullRequest;
    }

    /**
     * Create a pull request from {@code sourceBranch} to {@code targetBranch} and log in to see the PR, with the
     * {@link #AUTHOR_1 PR author} receiving read permission to the target project beforehand.
     *
     * @param sourceBranch source branch
     * @param targetBranch target branch
     * @return overview page of the created pull request
     */
    protected PullRequestOverviewPage createPullRequestAndLogin(String sourceBranch, String targetBranch) {
        return createPullRequestAndLogin(sourceBranch, targetBranch, Permission.PROJECT_READ);
    }

    /**
     * Create a pull request from {@code sourceBranch} to {@code targetBranch} and log in to see the PR, with the
     * {@link #AUTHOR_1 PR author} receiving {@code creatorPermission} to the target project beforehand.
     *
     * @param sourceBranch source branch
     * @param targetBranch target branch
     * @param creatorPermission granted to the PR author user, must be a project permission
     * @return overview page of the created pull request
     */
    protected PullRequestOverviewPage createPullRequestAndLogin(String sourceBranch, String targetBranch,
                                                                Permission creatorPermission) {

        testContext.projectPermission(PROJECT_KEY, AUTHOR_1, creatorPermission);
        PullRequestTestHelper pullRequest = createPullRequest(new PullRequestBuilder(sourceBranch, targetBranch).build());

        long id = pullRequest.getId();
        return loginAs(AUTHOR_1, AUTHOR_1, PullRequestOverviewPage.class, pullRequest.getProjectKey(),
                pullRequest.getRepoSlug(), id);
    }

    protected void declinePullRequest(long pullRequestId) {
        declinePullRequest(pullRequestId, AUTHOR_1, AUTHOR_1);
    }

    protected void declinePullRequest(long pullRequestId, String declinerUsername, String declinerPassword) {
        if (pullRequestId == -1L) {
            return;
        }
        PullRequestTestHelper pullRequest = PullRequestTestHelper.get(AUTHOR_1, AUTHOR_1,
                PROJECT_KEY, REPO_SLUG, pullRequestId);

        PullRequestTestHelper.declinePullRequest(
                declinerUsername, declinerPassword,
                PROJECT_KEY, REPO_SLUG,
                pullRequestId, pullRequest.getVersion()
        );
    }

    public static class PullRequestBuilder {

        private String author = AUTHOR_1;
        private String description = PULL_REQUEST_DESCRIPTION;
        private Set<String> reviewers = Sets.newHashSet();
        private String sourceBranch;
        private String sourceProject = PROJECT_KEY;
        private String sourceRepo = REPO_SLUG;
        private String targetBranch;
        private String targetProject = sourceProject;
        private String targetRepo = sourceRepo;
        private String title = OPEN_PULL_REQUEST_TITLE;

        public PullRequestBuilder(String sourceBranch, String targetBranch) {
            this.sourceBranch = sourceBranch;
            this.targetBranch = targetBranch;
        }

        public PullRequestBuilder author(String author) {
            this.author = author;
            return this;
        }

        public PullRequestTestHelper.Builder build() {
            PullRequestTestHelper.Builder builder = new PullRequestTestHelper.Builder(author, author, title,
                    description, sourceProject, sourceRepo, sourceBranch, targetProject, targetRepo, targetBranch);

            reviewers.forEach(builder::reviewers);

            return builder;
        }

        public PullRequestBuilder description(String description) {
            this.description = description;
            return this;
        }

        public PullRequestBuilder reviewer(String reviewer) {
            reviewers.add(reviewer);
            return this;
        }

        public PullRequestBuilder reviewers(String... reviewers) {
            this.reviewers.addAll(Arrays.asList(reviewers));
            return this;
        }

        public PullRequestBuilder sourceBranch(String sourceBranch) {
            this.sourceBranch = sourceBranch;
            return this;
        }

        public PullRequestBuilder sourceProject(String sourceProject) {
            this.sourceProject = sourceProject;
            return this;
        }

        public PullRequestBuilder sourceRepo(String sourceRepo) {
            this.sourceRepo = sourceRepo;
            return this;
        }

        public PullRequestBuilder targetBranch(String targetBranch) {
            this.targetBranch = targetBranch;
            return this;
        }

        public PullRequestBuilder targetProject(String targetProject) {
            this.targetProject = targetProject;
            return this;
        }

        public PullRequestBuilder targetRepo(String targetRepo) {
            this.targetRepo = targetRepo;
            return this;
        }

        public PullRequestBuilder title(String title) {
            this.title = title;
            return this;
        }
    }
}
