package it.com.atlassian.stash;

import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.bitbucket.test.RefRestrictionTestHelper;
import com.atlassian.pageobjects.elements.query.Poller;
import com.atlassian.webdriver.bitbucket.element.Avatar;
import com.atlassian.webdriver.bitbucket.element.GroupAvatar;
import com.atlassian.webdriver.bitbucket.element.PermissionDialog;
import com.atlassian.webdriver.bitbucket.page.admin.repo.BranchPermissionsPage;
import com.atlassian.webdriver.bitbucket.page.admin.repo.BranchPermissionsPage.BranchPermissionDialog;
import com.atlassian.webdriver.bitbucket.page.admin.repo.BranchPermissionsPage.PatternPermissionDialog;
import com.google.common.collect.ImmutableMap;
import com.jayway.restassured.RestAssured;
import org.hamcrest.Matchers;
import org.junit.*;

import javax.ws.rs.core.MediaType;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.atlassian.bitbucket.test.DefaultFuncTestData.getAdminPassword;
import static com.atlassian.bitbucket.test.DefaultFuncTestData.getAdminUser;
import static com.atlassian.webdriver.bitbucket.hamcrest.OptionMatchers.withId;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.*;

public class BranchPermissionsTest extends BaseBrowserFuncTest {

    private static final String ADMIN_USERNAME = getAdminUser();
    private static final Integer MAX_PERMISSIONS = 100;
    private static final String GROUPNAME = "branch-perms-group";
    private static final String TEST_PROJECT = DefaultFuncTestData.getProject1();
    private static final String TEST_REPO = DefaultFuncTestData.getProject1Repository1();
    private static final String USER_PREFIX = "branch-perms@";
    private static final String USERNAME = USER_PREFIX + "user";

    @AfterClass
    public static void setDown() {
        showSplashPage(true);
    }

    @BeforeClass
    public static void setUp() {
        showSplashPage(false);
    }

    @After
    public void after() {
        // Need to delete _all_ ref restrictions, lest we break subsequent tests...
        new RefRestrictionTestHelper(TEST_PROJECT, TEST_REPO).deleteAll();
    }

    @Test
    public void testAddingRemovingUsers() {
        createPermittedUser(USERNAME);
        BranchPermissionsPage permissions = goToBranchPermissions();

        BranchPermissionsPage.Row permission = createBranchPermission(permissions, "master", ADMIN_USERNAME, USERNAME);
        assertBranchPermission(permission, "master");
        assertBranchPermissionHasUsers(permission, ADMIN_USERNAME, USERNAME);
        assertBranchPermissionHasNoGroups(permission);

        //check anchoring
        permissions = BITBUCKET.visit(BranchPermissionsPage.class, TEST_PROJECT, TEST_REPO, "master");
        BranchPermissionDialog dialog = permissions.getOpenDialog();
        assertEquals("master", dialog.getEditSubject());
        permissions = dialog.cancel();

        permission = editBranchPermission(permissions, 0, ADMIN_USERNAME);
        assertBranchPermission(permission, "master");
        assertBranchPermissionHasUsers(permission, ADMIN_USERNAME);
        assertBranchPermissionHasNoGroups(permission);

        BranchPermissionsPage.Row advancedPermission = createPatternPermission(permissions, "release/*", ADMIN_USERNAME, USERNAME);
        assertBranchPermission(advancedPermission, "release/*");
        assertBranchPermissionHasUsers(advancedPermission, ADMIN_USERNAME, USERNAME);
        assertBranchPermissionHasNoGroups(advancedPermission);

        advancedPermission = editAdvanced(permissions, 1, USERNAME);
        assertBranchPermission(advancedPermission, "release/*");
        assertBranchPermissionHasUsers(advancedPermission, USERNAME);
        assertBranchPermissionHasNoGroups(advancedPermission);

        advancedPermission.delete();
        assertEquals("Row should have been deleted", 1, permissions.getRows().size());
        permission.delete();
        assertEquals("Row should have been deleted", 0, permissions.getRows().size());
    }

    @Test
    public void testAddingRemovingGroups() {
        testContext.group(GROUPNAME);
        BranchPermissionsPage permissions = goToBranchPermissions();

        BranchPermissionsPage.Row permission = createBranchPermission(permissions, "master", "stash-users", GROUPNAME);
        assertBranchPermission(permission, "master");
        assertBranchPermissionHasNoUsers(permission);
        assertBranchPermissionHasGroups(permission, "stash-users", GROUPNAME);

        permission = editBranchPermission(permissions, 0, GROUPNAME);
        assertBranchPermission(permission, "master");
        assertBranchPermissionHasNoUsers(permission);
        assertBranchPermissionHasGroups(permission, GROUPNAME);

        BranchPermissionsPage.Row advancedPermission = createPatternPermission(permissions, "release/*", "stash-users", GROUPNAME);
        assertBranchPermission(advancedPermission, "release/*");
        assertBranchPermissionHasNoUsers(advancedPermission);
        assertBranchPermissionHasGroups(advancedPermission, "stash-users", GROUPNAME);

        advancedPermission = editAdvanced(permissions, 1, "stash-users");
        assertBranchPermission(advancedPermission, "release/*");
        assertBranchPermissionHasNoUsers(advancedPermission);
        assertBranchPermissionHasGroups(advancedPermission, "stash-users");

        advancedPermission.delete();
        assertEquals("Row should have been deleted", 1, permissions.getRows().size());
        permission.delete();
        assertEquals("Row should have been deleted", 0, permissions.getRows().size());
    }

    @Test
    public void testAddRemovingPermissionWithBothUsersAndGroups() throws Exception {
        createPermittedUser(USERNAME);
        BranchPermissionsPage permissions = goToBranchPermissions();

        BranchPermissionsPage.Row permission = createBranchPermission(permissions, "master", USERNAME, "stash-users",
                ADMIN_USERNAME);
        assertBranchPermission(permission, "master");
        assertBranchPermissionHasUsers(permission, USERNAME, ADMIN_USERNAME);
        assertBranchPermissionHasGroups(permission, "stash-users");

        permission = editBranchPermission(permissions, 0, "stash-users");
        assertBranchPermission(permission, "master");
        assertBranchPermissionHasNoUsers(permission);
        assertBranchPermissionHasGroups(permission, "stash-users");


        BranchPermissionsPage.Row advancedPermission = createPatternPermission(permissions, "release/*", USERNAME, "stash-users",
                ADMIN_USERNAME);
        assertBranchPermission(advancedPermission, "release/*");
        assertBranchPermissionHasUsers(advancedPermission, USERNAME, ADMIN_USERNAME);
        assertBranchPermissionHasGroups(advancedPermission, "stash-users");

        advancedPermission = editAdvanced(permissions, 1, USERNAME, ADMIN_USERNAME);
        assertBranchPermission(advancedPermission, "release/*");
        assertBranchPermissionHasUsers(advancedPermission, USERNAME, ADMIN_USERNAME);
        assertBranchPermissionHasNoGroups(advancedPermission);

        advancedPermission.delete();
        assertEquals("Row should have been deleted", 1, permissions.getRows().size());
        permission.delete();
        assertEquals("Row should have been deleted", 0, permissions.getRows().size());
    }

    @Test
    public void testFilterOutNotPermittedUsers() {
        createPermittedUser(USERNAME);
        // unlicensed user
        testContext.user(USER_PREFIX + "unlicensed", false)
                .repositoryPermission(TEST_PROJECT, TEST_REPO, USER_PREFIX + "unlicensed", Permission.REPO_WRITE);
        // no write permission (only repo read)
        testContext.user(USER_PREFIX + "nowrite")
                .globalPermission(USER_PREFIX + "nowrite", Permission.LICENSED_USER)
                .repositoryPermission(TEST_PROJECT, TEST_REPO, USER_PREFIX + "nowrite", Permission.REPO_READ);

        BranchPermissionDialog permissionDialog = goToBranchPermissions().addBranchPermissions();
        permissionDialog.setFirstCheckbox(true);
        Poller.waitUntil("Branch permissions should only be available to permitted users",
                permissionDialog.getFirstUsersAndGroups()
                        .clearFilter().type(USER_PREFIX).getOptions(), contains(withId(USERNAME)));

        //We need to ensure the dialog is closed and empty
        permissionDialog.getFirstUsersAndGroups().selectFirst().removeAll();

        Poller.waitUntil("Branch permissions in pattern mode should only be available to permitted users",
                permissionDialog.switchToPattern().getFirstUsersAndGroups()
                        .clearFilter().type(USER_PREFIX).getOptions(), contains(withId(USERNAME)));
    }

    @Test
    public void testCreateDuplicatePermission() {
        createPermittedUser(USERNAME);
        BranchPermissionsPage permissions = goToBranchPermissions();
        createPatternPermission(permissions, "release/*", true, true, true);
        assertTrue(createPatternPermissionExpectingError(permissions, "release/*"));
    }

    @Test
    public void testCreateMultiplePermissions() {
        createPermittedUser(USERNAME);
        BranchPermissionsPage permissions = goToBranchPermissions();
        createPatternPermission(permissions, "release/*", true, false, true);
        BranchPermissionsPage.Row permission = editBranchPermission(permissions, 0, "stash-users");
        assertBranchPermissionHasGroups(permission, "stash-users");
    }

    @Ignore // This test takes quite long to run and doesn't tell us much at the moment.
    @Test
    public void testRejectPermissionsWhenPageFull() {
        createPermittedUser(USERNAME);
        BranchPermissionsPage permissions = goToBranchPermissions();
        for (int i = 0; i < MAX_PERMISSIONS / 3; i++) {
            createPatternPermission(permissions, "pattern" + i, true, true, true);
        }

        assertTrue(createPatternPermissionExpectingError(permissions, "errorPattern"));
    }

    private static void showSplashPage(boolean show) {
        String settingKey = "SHOW_FIRST_CONTACT_PAGE";
        Map<String, Object> settingsMap = ImmutableMap.<String, Object>builder()
                .put(settingKey, show)
                .build();

        RestAssured.expect()
                .statusCode(NO_CONTENT.getStatusCode())
                .log().ifStatusCodeMatches(not(equalTo(NO_CONTENT.getStatusCode())))
                .given()
                .auth().preemptive().basic(ADMIN_USERNAME, getAdminPassword())
                .contentType(MediaType.APPLICATION_JSON)
                .body(settingsMap)
                .when().post(DefaultFuncTestData.getRestURL() + "/users/" + ADMIN_USERNAME + "/settings");
    }

    private void addEntities(PermissionDialog addDialog, String... entities) {
        for (String entity : entities) {
            addDialog.getFirstUsersAndGroups().type(entity).selectOption(entity);
        }
    }

    private void assertBranchPermission(BranchPermissionsPage.Row row, String branchOrPattern) {
        assertEquals(branchOrPattern, row.getName());
    }

    private void assertBranchPermissionHasUsers(BranchPermissionsPage.Row permission, String... users) {
        List<String> avatars = permission.getUsers().stream().map(Avatar::getUsername).collect(Collectors.toList());
        assertThat(avatars, Matchers.hasItems(users));
        assertEquals(users.length, avatars.size());
    }

    private void assertBranchPermissionHasGroups(BranchPermissionsPage.Row permission, String... groups) {
        List<String> groupAvatars = permission.getGroups().stream().map(GroupAvatar::getGroupname)
                .collect(Collectors.toList());
        assertThat(groupAvatars, Matchers.hasItems(groups));
        assertEquals(groups.length, groupAvatars.size());
    }

    private void assertBranchPermissionHasNoUsers(BranchPermissionsPage.Row permission) {
        assertBranchPermissionHasUsers(permission);
    }

    private void assertBranchPermissionHasNoGroups(BranchPermissionsPage.Row permission) {
        assertBranchPermissionHasGroups(permission);
    }

    private BranchPermissionsPage.Row createBranchPermission(BranchPermissionsPage permissions, String branch,
                                                             String... entities) {
        BranchPermissionDialog addDialog = permissions.addBranchPermissions();
        addDialog.getBranchSelector().open().filterBy(branch).selectItemById("refs/heads/" + branch);
        addDialog.setFirstCheckbox(true);
        addEntities(addDialog, entities);
        addDialog.submit();
        return permissions.getRows().get(0);
    }

    private BranchPermissionsPage.Row createPatternPermission(BranchPermissionsPage permissions, String glob,
                                                              String... entities) {
        PatternPermissionDialog addDialog = permissions.addBranchPermissions().switchToPattern();
        addDialog.setGlob(glob);
        addDialog.setFirstCheckbox(true);
        addEntities(addDialog, entities);
        addDialog.submit();
        return permissions.getRows().get(permissions.getRows().size() - 1);
    }

    private BranchPermissionsPage.Row createPatternPermission(BranchPermissionsPage permissions, String glob,
                                                              boolean... checkBoxes) {
        PatternPermissionDialog addDialog = permissions.addBranchPermissions().switchToPattern();
        addDialog.setGlob(glob);
        addDialog.checkBoxes(checkBoxes);
        addDialog.submit();
        return permissions.getRows().get(permissions.getRows().size() - 1);
    }

    private boolean createPatternPermissionExpectingError(BranchPermissionsPage permissions, String glob) {
        PatternPermissionDialog addDialog = permissions.addBranchPermissions().switchToPattern();
        addDialog.setGlob(glob);
        addDialog.checkBoxes(true, true, true);
        return addDialog.submitExpectingError();
    }

    /**
     * Create user that branch permissions can be assigned to.
     *
     * @param username username to create
     */
    private void createPermittedUser(String username) {
        testContext.user(username)
                .globalPermission(username, Permission.LICENSED_USER)
                .repositoryPermission(TEST_PROJECT, TEST_REPO, username, Permission.REPO_WRITE);
    }

    private BranchPermissionsPage.Row editBranchPermission(BranchPermissionsPage permissions, int index,
                                                           String... entities) {
        BranchPermissionDialog editDialog = permissions.getRows().get(index).edit(BranchPermissionDialog.class);
        editDialog.getFirstUsersAndGroups().removeAll();
        addEntities(editDialog, entities);
        editDialog.submit();
        return permissions.getRows().get(index);
    }

    private BranchPermissionsPage.Row editAdvanced(BranchPermissionsPage permissions, int index, String... entities) {
        PatternPermissionDialog editDialog = permissions.getRows().get(index).edit(PatternPermissionDialog.class);
        editDialog.getFirstUsersAndGroups().removeAll();
        addEntities(editDialog, entities);
        editDialog.submit();
        return permissions.getRows().get(index);
    }

    private BranchPermissionsPage goToBranchPermissions() {
        return loginAs(getAdminUser(), getAdminPassword(), BranchPermissionsPage.class,
                TEST_PROJECT, TEST_REPO);
    }
}
