package it.com.atlassian.stash;

import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.webdriver.bitbucket.element.PermissionKind;
import com.atlassian.webdriver.bitbucket.element.PermissionRow;
import com.atlassian.webdriver.bitbucket.page.ErrorPage;
import com.atlassian.webdriver.bitbucket.page.RepositoryPermissionsPage;
import com.jayway.restassured.RestAssured;
import net.sf.json.JSONObject;
import org.junit.Before;
import org.junit.Test;

import javax.ws.rs.core.Response;

import static java.lang.String.format;
import static javax.ws.rs.core.Response.Status.OK;
import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;

public class RepositoryPermissionsAdminUiTest extends BaseBrowserFuncTest {

    private static final String PROJECT_KEY = "PERMPROJ";
    private static final String PROJECT_NAME = "Repo Perms Test Project";
    private static final String REPO_SLUG = "PERMREP";
    public static final String USER = "user";
    public static final String GROUP = "perm-users";

    @Before
    public void setup() {
        testContext.project(PROJECT_KEY, PROJECT_NAME, "Project for " + getClass().getSimpleName());
        testContext.repository(PROJECT_KEY, REPO_SLUG);
        testContext.group(GROUP, USER);
    }

    @Test
    public void shouldNotBeAbleToSeeRepoPermAdminPageWithoutRepoAdminPrivilege() throws Exception {
        testContext.repositoryPermission(PROJECT_KEY, REPO_SLUG, USER, Permission.REPO_WRITE);

        ErrorPage page = loginAs(USER, USER, ErrorPage.class, new RepositoryPermissionsPage(PROJECT_KEY, REPO_SLUG).getUrl());

        assertThat(page.getError(), is(UNAUTHORIZED));
    }

    @Test
    public void shouldDefaultToReadAccessWhenAddingUserPermission() {
        RepositoryPermissionsPage page = loginAsAdmin(RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        page.addUsers(USER);

        assertThat(page.getUserItem(USER).getHighestPermission(), is(PermissionKind.REPO_READ));
        assertUserHasRepoPermission(USER, Permission.REPO_READ, PROJECT_KEY, REPO_SLUG);
    }

    @Test
    public void shouldEditExistingUserPermission() throws Exception {
        testContext.repositoryPermission(PROJECT_KEY, REPO_SLUG, USER, Permission.REPO_WRITE);

        RepositoryPermissionsPage page = loginAsAdmin(RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        PermissionRow userPermissionRow = page.getUserItem(USER);
        assertThat(userPermissionRow.getHighestPermission(), is(PermissionKind.REPO_WRITE));
        userPermissionRow.clickPermission(PermissionKind.REPO_ADMIN);

        assertUserHasRepoPermission(USER, Permission.REPO_ADMIN, PROJECT_KEY, REPO_SLUG);
    }

    @Test
    public void shouldDeleteUserPermission() throws Exception {
        testContext.repositoryPermission(PROJECT_KEY, REPO_SLUG, USER, Permission.REPO_ADMIN);

        RepositoryPermissionsPage page = loginAsAdmin(RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        PermissionRow userPermissionRow = page.getUserItem(USER);
        assertThat(userPermissionRow.getHighestPermission(), is(PermissionKind.REPO_ADMIN));
        // clickRemove has an implicit wait for the row to disappear
        userPermissionRow.clickRemove();

        assertUserHasRepoPermission(USER, null, PROJECT_KEY, REPO_SLUG);
    }

    @Test
    public void shouldAddGroupPermission() throws Exception {
        RepositoryPermissionsPage page = loginAsAdmin(RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        page.addGroups(GROUP);

        assertThat(page.getGroupItem(GROUP).getHighestPermission(), is(PermissionKind.REPO_READ));
        assertUserHasRepoPermission(USER, Permission.REPO_READ, PROJECT_KEY, REPO_SLUG);
    }

    @Test
    public void shouldEditExistingGroupPermission() throws Exception {
        testContext.repositoryPermissionForGroup(PROJECT_KEY, REPO_SLUG, GROUP, Permission.REPO_WRITE);

        RepositoryPermissionsPage page = loginAsAdmin(RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        PermissionRow groupPermissionRow = page.getGroupItem(GROUP);
        assertThat(groupPermissionRow.getHighestPermission(), is(PermissionKind.REPO_WRITE));
        groupPermissionRow.clickPermission(PermissionKind.REPO_ADMIN);

        assertUserHasRepoPermission(USER, Permission.REPO_ADMIN, PROJECT_KEY, REPO_SLUG);
    }

    @Test
    public void shouldDeleteGroupPermission() throws Exception {
        testContext.repositoryPermissionForGroup(PROJECT_KEY, REPO_SLUG, GROUP, Permission.REPO_ADMIN);

        RepositoryPermissionsPage page = loginAsAdmin(RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        PermissionRow groupPermissionRow = page.getGroupItem(GROUP);
        assertThat(groupPermissionRow.getHighestPermission(), is(PermissionKind.REPO_ADMIN));
        // clickRemove has an implicit wait for the row to disappear
        groupPermissionRow.clickRemove();

        assertUserHasRepoPermission(USER, null, PROJECT_KEY, REPO_SLUG);
    }

    @Test
    public void shouldNotRemoveUsersOwnAccess() throws Exception {
        testContext.repositoryPermission(PROJECT_KEY, REPO_SLUG, USER, Permission.REPO_ADMIN);

        final PermissionRow row = loginAs(USER, USER, RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG)
                .getUserItem(USER)
                .clickRemoveAndExpectError();

        assertThat(row.getErrors(), hasItem(format("You cannot revoke permission on %s as it would downgrade your own permissions.", REPO_SLUG)));
    }

    @Test
    public void shouldMakeRepoPublicOrPrivate() {
        RepositoryPermissionsPage page = loginAsAdmin(RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        assertFalse("Repo should be private by default", page.isPublicAccessSelected());
        page = page.setPublicAccess(true);
        assertRepoIsPublic(true, PROJECT_KEY, REPO_SLUG);

        page.setPublicAccess(false);
        assertRepoIsPublic(false, PROJECT_KEY, REPO_SLUG);
    }

    @Test
    public void shouldDefaultToPublicIfProjectIsPublic() {
        setProjectToPublic(true, PROJECT_KEY);
        RepositoryPermissionsPage page = loginAsAdmin(RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        assertTrue("Repo should be public by default", page.isPublicAccessSelected());
        assertFalse("Public checkbox should be disabled", page.isPublicAccessEnabled());
    }

    /**
     * This method checks that they user has the permission that the admin UI says they have.
     */
    private void assertUserHasRepoPermission(String userId, Permission permission, String projectKey, String repoSlug) {
        if (permission == Permission.REPO_ADMIN) {
            // Verify that the user can admin the repository by sending them to the repo permission page
            loginAs(userId, userId, RepositoryPermissionsPage.class, PROJECT_KEY, REPO_SLUG);
        } else if (permission == Permission.REPO_READ || permission == null) {
            // Check if they can/can't see the repo.  (We don't check to see if they can do more than read it...)
            Response.Status expectedResponse;
            if (permission == Permission.REPO_READ) {
                expectedResponse = OK;
            } else {
                expectedResponse = UNAUTHORIZED;
            }
            RestAssured.given().auth().preemptive().basic(userId, userId)
                    .expect().statusCode(expectedResponse.getStatusCode())
                    .when().get(DefaultFuncTestData.getRestURL() + "/projects/" + projectKey + "/repos/" + repoSlug);
        } else {
            throw new UnsupportedOperationException("Unable to test for permission: " + permission);
        }
    }

    private void assertRepoIsPublic(boolean shouldRepoBePublic, String projectKey, String repoSlug) {
        RestAssured.given().auth().preemptive().basic(USER, USER)
                .expect().statusCode((shouldRepoBePublic ? OK : UNAUTHORIZED).getStatusCode())
                .when().get(DefaultFuncTestData.getRestURL() + "/projects/" + projectKey + "/repos/" + repoSlug);
    }

    private void setProjectToPublic(boolean shouldProjectBePublic, String projectKey) {
        JSONObject bodyObject = new JSONObject();
        bodyObject.put("public", shouldProjectBePublic);
        RestAssured.given()
                .auth().preemptive().basic(DefaultFuncTestData.getAdminUser(), DefaultFuncTestData.getAdminPassword())
                .body(bodyObject.toString()).contentType("application/json")
                .expect().statusCode(OK.getStatusCode())
                .when().put(DefaultFuncTestData.getRestURL() + "/projects/" + projectKey);
    }
}
