package it.com.atlassian.stash;

import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.test.*;
import com.atlassian.webdriver.bitbucket.element.BranchList;
import com.atlassian.webdriver.bitbucket.element.BranchList.BranchListEntry;
import com.atlassian.webdriver.bitbucket.element.BuildStatusDialog;
import com.atlassian.webdriver.bitbucket.element.UserAndGroupSelect;
import com.atlassian.webdriver.bitbucket.page.BranchListPage;
import com.atlassian.webdriver.bitbucket.page.PullRequestCreatePage;
import com.atlassian.webdriver.bitbucket.page.admin.repo.BranchPermissionsPage;
import com.atlassian.webdriver.bitbucket.page.compare.ComparePage;
import org.hamcrest.Matchers;
import org.junit.BeforeClass;
import org.junit.Test;

import java.time.Instant;
import java.util.List;

import static com.atlassian.webdriver.bitbucket.element.BranchList.BranchDeleteDialog;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

public class BranchListTest extends QARepoFuncTest {

    private static final String TEST_USER = "actionsTestUser";
    private static final String OCTOPUS_HEAD = "300629376775283536666f7838e6ffdfec42c7fa";

    @BeforeClass
    public static void disableBranchModel() {
        // disable branch model so it doesn't affect branch related operations - this is already tested elsewhere
        new BranchModelTestHelper(getProjectKey(), getRepoSlug()).disable();
    }

    @Test
    public void testViewListHappyPath() throws Exception {
        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());

        List<BranchListEntry> rows = page.getBranchList().getRows();
        assertThat(rows, hasSize(20));
        // The first branch should be the most recently updated
        checkRow(rows.get(0), "collapse-unicode", Instant.ofEpochSecond(1469602928), 1, 0, "master");

        // Test scroll loads more branches
        rows = page.getBranchList().loadNextPage().getRows();
        assertThat(rows, hasSize(38));

        // This row number was chosen because it does not have any preceding branches with identical timestamps. This
        // caused an issue when upgrading to git 2.7.0 because of changes to the way items where ordered when the sort
        // parameter is identical.
        int scrollLoadedBranch = 24;
        // Test that the name is correct
        checkRow(rows.get(scrollLoadedBranch), "!@#%", Instant.ofEpochSecond(1325570646L), 13, 308, "master");

        // Test that clicking on the branch goes to the compare page with the correct prefilled branches
        ComparePage comparePage = rows.get(scrollLoadedBranch).click();
        assertEquals("!@#%", comparePage.getSourceTargetSelector().getSourceBranchSelector().getSelectedItemName());
        assertEquals("master", comparePage.getSourceTargetSelector().getTargetBranchSelector().getSelectedItemName());
    }

    @Test
    public void testSwitchToNonDefaultBranch() throws Exception {
        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());
        BranchList branchList = page.getBranchList();

        // check the default branch is initially selected
        assertNull(branchList.getBaseBranchRow());
        assertEquals("master", branchList.getDefaultBranchRow().getName());
        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        String branchName = "new-branch-BLT";
        BranchTestHelper.createBranch(getProjectKey(), getRepoSlug(), branchName, OCTOPUS_HEAD);

        // switch to the new branch
        page = page.switchTo(branchName);
        branchList = page.getBranchList();

        // check the branch is now the base branch
        BranchListEntry baseBranchRow = branchList.getBaseBranchRow();
        assertEquals(branchName, baseBranchRow.getName());
        assertEquals(branchName, page.getBranchSelector().getSelectedItemName());
        assertEquals("master", branchList.getDefaultBranchRow().getName());

        // delete the base branch and check the base branch is reset to the default branch
        page = baseBranchRow.clickDeleteBranch().clickConfirmAndBind();
        assertNull(branchList.getBaseBranchRow());
        assertEquals("master", page.getBranchSelector().getSelectedItemName());
        assertFalse("The branch was not deleted", RepositoryTestHelper.hasBranch(getProjectKey(), getRepoSlug(), branchName));
    }

    @Test
    public void testSwitchToTag() throws Exception {
        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());
        BranchList branchList = page.getBranchList();

        // check the default branch is initially selected
        assertNull(branchList.getBaseBranchRow());
        assertEquals("master", branchList.getDefaultBranchRow().getName());
        assertEquals("master", page.getBranchSelector().getSelectedItemName());

        String tagName = "tag#1";
        page.getBranchSelector().open().selectTags().selectItemByName(tagName);

        assertNull("There should be no base branch with a tag selected", branchList.getBaseBranchRow());

        // master is the default regardless of the base
        BranchListEntry defaultBranchRow = branchList.getDefaultBranchRow();
        assertEquals("master", defaultBranchRow.getName());
        // with tag#1 selected, master's ahead/behind should be relative to that
        assertEquals(Integer.valueOf(291), defaultBranchRow.getAheadCount());
        assertEquals(Integer.valueOf(1), defaultBranchRow.getBehindCount());
    }

    @Test
    public void testFilterList() throws Exception {
        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());

        List<BranchListEntry> rows = page.getBranchList().getRows();
        assertThat(rows, hasSize(20));

        rows = page.getBranchList().filter("%").getRows();

        assertThat(rows.stream().filter(branchListEntry -> branchListEntry.getName().contains("%")).count(), is(5L));

        rows = page.getBranchList().filter("Show me an empty branch list").getRows();
        assertThat(rows, is(empty()));
        assertThat(page.getBranchList().getMessageText(), containsString("No branches match"));
    }

    @Test
    public void testAnonymousAheadBehind() throws Exception {
        try {
            RepositoryTestHelper.setRepositoryPublicAccessEnabled(getProjectKey(), getRepoSlug(), true);

            BranchListPage page = anonymous(BranchListPage.class, getProjectKey(), getRepoSlug());
            checkRow(page.getBranchList().loadNextPage().getRow("%"), "%", Instant.ofEpochMilli(1325570451000L), 12, 308, "master");
        } finally {
            RepositoryTestHelper.setRepositoryPublicAccessEnabled(getProjectKey(), getRepoSlug(), false);
        }
    }

    @Test
    public void testCreatePullRequestAction() {
        PullRequestCreatePage createPullRequestPage = getBranchListEntry().clickCreatePullRequest();
        assertTrue("Should have skipped to the PR create screen", createPullRequestPage.isShowingPRForm());
        assertEquals("Source branch should have been set as the pull request's source", "octopus", createPullRequestPage.getCollapsedSourceBranch().getBranch());
        assertEquals("Base branch should have been set as the pull request's target", "master", createPullRequestPage.getCollapsedTargetBranch().getBranch());
    }

    @Test
    public void testActionsPermissions() {
        testContext.user(TEST_USER, TEST_USER);
        testContext.repositoryPermission(getProjectKey(), getRepoSlug(), TEST_USER, Permission.REPO_READ);
        BranchListPage page = loginAs(TEST_USER, TEST_USER, BranchListPage.class, getProjectKey(), getRepoSlug());
        List<BranchListEntry> rows = page.getBranchList().getRows();
        BranchListEntry row = rows.get(1);
        List<String> actions = row.getActionClasses();
        assertThat("User with REPO_READ should be able to copy branch names", actions, hasItem("branch-list-copy-branch-action"));
        assertThat("User with REPO_READ should be able to create pull requests", actions, hasItem("create-pull-request"));
        assertThat("User with REPO_READ should NOT be able to delete branches", actions, not(hasItem("delete-branch")));
        assertThat("User with REPO_READ should NOT be able to edit branch permissions", actions, not(hasItem("edit-permissions")));
    }

    @Test
    public void testBuildStatus() {
        BuildStatusTestHelper.addBuildStatus(OCTOPUS_HEAD, "TEST-1", BuildStatusTestHelper.State.SUCCESSFUL);

        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());
        assertTrue("Build status column should be visible when there is a status", page.getBranchList().isBuildStatusColumnVisible());

        List<BranchListEntry> rows = page.getBranchList().getRows();
        BranchListEntry row = rows.get(2);
        assertTrue("Build status should appear on the target row", row.hasBuildStatus());

        BuildStatusDialog buildStatus = row.clickBuildStatus();
        assertEquals("Build status dialog should show a successful build",
                BuildStatusTestHelper.State.SUCCESSFUL, buildStatus.getBuildStatusLines().get(0).getStatus());

        BranchListEntry nonTargetRow = rows.get(5);
        assertFalse("Build status should not appear on non-target rows", nonTargetRow.hasBuildStatus());

        RepositoryTestHelper.updateRepository(getProjectKey(), getRepoSlug(), "{\"public\": \"true\"}");
        page = anonymous(BranchListPage.class, getProjectKey(), getRepoSlug());
        assertFalse("No build status column should be visible to anonymous users", page.getBranchList().isBuildStatusColumnVisible());

        rows = page.getBranchList().getRows();
        row = rows.get(2);
        assertFalse("No build status column should be visible to anonymous users on the target row", row.hasBuildStatus());
    }

    @Test
    public void testCreateAndDeleteBranch() throws Exception {
        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());

        // First create a branch from octopus (as it is ahead of master)
        BranchListEntry branchRow = page.getBranchList().getRows().get(2);
        assertEquals("octopus", branchRow.getName());

        BranchTestHelper.createBranch(getProjectKey(), getRepoSlug(), "delete-me", OCTOPUS_HEAD);

        page = BITBUCKET.visit(BranchListPage.class, getProjectKey(), getRepoSlug());

        // Check the branch has been created
        branchRow = page.getBranchList().getRowOrFail("delete-me");

        // Then delete the branch
        BranchDeleteDialog dialog = branchRow.clickDeleteBranch();
        assertTrue("Branch delete dialog should warn when the branch is ahead", dialog.hasNotMergedWarning());
        page = dialog.clickConfirmAndBind();
        branchRow = page.getBranchList().getRows().get(0);
        assertEquals("collapse-unicode", branchRow.getName());
        assertFalse("Branch should have been successfully deleted", RepositoryTestHelper.hasBranch(getProjectKey(), getRepoSlug(), "delete-me"));
    }

    @Test
    public void testDeleteDialogTracksSelectedRefBranch() {
        String branchToDelete = "delete-me";
        String branchFrom = "octopus";
        try {
            BranchTestHelper.createBranch(getProjectKey(), getRepoSlug(), branchToDelete, OCTOPUS_HEAD);

            BranchListPage branchListPage = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());
            BranchDeleteDialog branchDeleteDialog = branchListPage.getBranchList()
                    .getRowOrFail(branchToDelete)
                    .clickDeleteBranch();

            assertTrue(branchDeleteDialog.hasNotMergedWarning());
            assertEquals("master", branchDeleteDialog.getNotMergedBranch());
            assertEquals(12, branchDeleteDialog.getNotMergedCommitCount());

            branchDeleteDialog.clickCancel();

            branchListPage = branchListPage.switchTo("undefined");
            branchDeleteDialog = branchListPage.getBranchList()
                    .getRowOrFail(branchToDelete)
                    .clickDeleteBranch();

            assertTrue(branchDeleteDialog.hasNotMergedWarning());
            assertEquals("undefined", branchDeleteDialog.getNotMergedBranch());
            assertEquals(20, branchDeleteDialog.getNotMergedCommitCount());

            branchDeleteDialog.clickCancel();

            branchListPage = branchListPage.switchTo(branchFrom);
            branchDeleteDialog = branchListPage.getBranchList()
                    .getRowOrFail(branchToDelete)
                    .clickDeleteBranch();

            assertFalse(branchDeleteDialog.hasNotMergedWarning());
        } finally {
            BranchTestHelper.deleteBranch(getProjectKey(), getRepoSlug(), branchToDelete);
        }
    }

    @Test
    public void testEditBranchPermissionAction() {
        BranchListEntry branchListEntry = getBranchListEntry();
        String branch = branchListEntry.getName();

        BranchPermissionsPage branchPermissionsPage = branchListEntry.clickEditBranchPermissions();
        BranchPermissionsPage.BranchPermissionDialog dialog = branchPermissionsPage.getOpenDialog();
        assertThat(dialog.getTitleText(), containsString("Add"));
        assertEquals(branch, dialog.getBranchSelector().getSelectedItemName());

        createBranchPermission(branch);

        branchPermissionsPage = getBranchListEntry().clickEditBranchPermissions();
        dialog = branchPermissionsPage.getOpenDialog();
        assertThat(dialog.getTitleText(), containsString("Edit"));
        assertThat(dialog.getEditSubject(), containsString(branch));
    }

    @Test
    public void testKeyboardNavigation() throws Exception {
        BranchTestHelper.createBranch(getProjectKey(), getRepoSlug(), "branch-me-via-keyboard", "61a403ebf2d16db2477e58c0ca0fd5c4385a81e8");
        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());

        page = page.focusFilter();
        assertTrue("Filter should be focused", page.getBranchList().isFilterFocused());

        page.getBranchList().filter("br");

        BranchListEntry focusedRow = page.getBranchList().getFocusedRow();
        assertEquals("new-branch", focusedRow.getName());

        page = page.moveToNextBranch();
        focusedRow = page.getBranchList().getFocusedRow();
        assertEquals("branch1", focusedRow.getName());

        page = page.moveToNextBranch().moveToNextBranch();
        focusedRow = page.getBranchList().getFocusedRow();
        assertEquals("branch20", focusedRow.getName());

        page = page.moveToPreviousBranch();
        focusedRow = page.getBranchList().getFocusedRow();
        assertEquals("branch-me-via-keyboard", focusedRow.getName());

        page = page.deleteFocusedBranch();
        focusedRow = page.getBranchList().getFocusedRow();
        assertEquals("branch20", focusedRow.getName());

        page = page.moveToPreviousBranch();
        focusedRow = page.getBranchList().getFocusedRow();
        assertEquals("branch1", focusedRow.getName());
        assertFalse("Branch should have been successfully deleted", RepositoryTestHelper.hasBranch(getProjectKey(), getRepoSlug(), "branch-me-via-keyboard"));

        assertEquals("branch1", page.openCompare().getSourceTargetSelector().getSourceBranchSelector().getSelectedItemName());
    }

    @Test
    public void testInvalidBranch() throws Exception {
        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug(), null, "doesnotexist");
        assertThat(page.getError().now(), Matchers.containsString("doesnotexist"));
    }

    private void createBranchPermission(String branch) {
        BranchPermissionsPage.BranchPermissionDialog addDialog = loginAsAdmin(BranchPermissionsPage.class, getProjectKey(), getRepoSlug()).addBranchPermissions();
        addDialog.getBranchSelector().open().filterBy(branch).selectItemById("refs/heads/" + branch);
        addDialog.checkBoxes(true);
        UserAndGroupSelect accessList = addDialog.getFirstUsersAndGroups();
        accessList.type(DefaultFuncTestData.getAdminUser()).selectOption(DefaultFuncTestData.getAdminUser());
        addDialog.submit();
    }

    private void checkRow(BranchListEntry row, String name, Instant updated, Integer ahead, Integer behind,
                          String baseRef) {
        assertEquals("branch name should match", name, row.getName());
        assertEquals("last updated should match", updated, row.getUpdated());
        assertEquals("ahead count should match", ahead, row.getAheadCount());
        assertEquals("behind count should match", behind, row.getBehindCount());
        if (row.isAheadBehindNotAvailable()) {
            // we can now be missing values due to the commit graph cache
            assertThat("ahead behind tooltip should refer to problem when unavailable",
                    row.getAheadBehindToolTip(),
                    containsString("Could not retrieve the ahead/behind information"));
        } else if (baseRef != null) {
            assertThat("ahead behind tooltip should contain base ref when available", row.getAheadBehindToolTip(), containsString(baseRef));
        } else {
            assertNull("ahead behind should not have a tooltip when blank", row.getAheadBehindToolTip());
        }
    }

    private BranchListEntry getBranchListEntry() {
        BranchListPage page = loginAsAdmin(BranchListPage.class, getProjectKey(), getRepoSlug());
        List<BranchListEntry> rows = page.getBranchList().getRows();
        return rows.get(2);
    }
}
