package it.com.atlassian.stash.rest.repository.sync;

import com.atlassian.bitbucket.test.PermissionsTestHelper;
import com.atlassian.bitbucket.test.rest.RestAuthentication;
import com.atlassian.bitbucket.test.rest.repository.sync.RestEnableRefSyncRequest;
import com.atlassian.bitbucket.test.rest.repository.sync.RestGetRefSyncStatusRequest;
import com.atlassian.bitbucket.test.rest.repository.sync.RestRefSyncHelper;
import com.jayway.restassured.builder.ResponseSpecBuilder;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.ws.rs.core.Response;

import static org.hamcrest.Matchers.*;

public class RefSyncResourceTest extends BaseRefSyncTest {

    @BeforeClass
    public static void createUpstream() throws Exception {
        prepareUpstream(getClassTestContext());
    }

    @Before
    public void createFork() {
        //To ensure a consistent state, grant the user upstream permission and fork the repository for each test
        prepareFork(testContext);
    }

    @Test
    public void testGetStatusAnonymously() {
        //Ref sync status should not be available to anonymous users
        refSyncHelper.getStatus(new RestGetRefSyncStatusRequest.Builder()
                .authentication(RestAuthentication.anonymous())
                .status(Response.Status.UNAUTHORIZED)
                .specification(new ResponseSpecBuilder()
                        .expectBody("errors[0].message", equalTo("You are not permitted to access this resource"))
                        .build())
                .build());
    }

    @Test
    public void testSetEnabledAnonymously() {
        refSyncHelper.setEnabled(new RestEnableRefSyncRequest.Builder(true)
                .authentication(RestAuthentication.anonymous())
                .status(Response.Status.UNAUTHORIZED)
                .specification(new ResponseSpecBuilder()
                        .expectBody("errors[0].message", equalTo("You are not permitted to access this resource"))
                        .build())
                .build());
    }

    @Test
    public void testSetEnabledRequiresRepoAdmin() {
        //rs-user doesn't have REPO_ADMIN for rs-owner's repository. This also verifies that
        //permissions are checked before the availability of ref sync
        new RestRefSyncHelper(RestAuthentication.authenticated(USER), OWNER_PROJECT, REPOSITORY_SLUG)
                .setEnabled(new RestEnableRefSyncRequest.Builder(true)
                        .status(Response.Status.UNAUTHORIZED)
                        .specification(new ResponseSpecBuilder()
                                .expectBody("errors[0].message",
                                        equalTo("Only repository administrators can configure ref synchronization."))
                                .build())
                        .build());
    }

    @Test
    public void testSetEnabledWithoutReadAccess() {
        //rs-owner doesn't have REPO_READ on rs-user's fork
        refSyncHelper.setEnabled(new RestEnableRefSyncRequest.Builder(true)
                .authentication(RestAuthentication.authenticated(OWNER))
                .status(Response.Status.UNAUTHORIZED)
                .specification(new ResponseSpecBuilder()
                        .expectBody("errors[0].message", equalTo("You are not permitted to access this resource"))
                        .build())
                .build());
    }

    @Test
    public void testSetEnabledWithoutUpstreamReadAccess() {
        //Revoke rs-user's access to rs-owner's repository
        PermissionsTestHelper.revokeRepositoryPermissionForUser(OWNER_PROJECT, REPOSITORY_SLUG, USER);

        refSyncHelper.setEnabled(new RestEnableRefSyncRequest.Builder(true)
                .status(Response.Status.CONFLICT)
                .specification(new ResponseSpecBuilder()
                        .expectBody("errors[0].message", allOf(
                                containsString("personal fork and its owner"),
                                containsString("no longer has access")))
                        .build())
                .build());
    }

    @Test
    public void testSyncNotAvailableOnNonForks() {
        RestRefSyncHelper ownerHelper = new RestRefSyncHelper(RestAuthentication.authenticated(OWNER),
                OWNER_PROJECT, REPOSITORY_SLUG);

        //Even though the repository is not a fork, it should be possible to retrieve its state
        ownerHelper.getStatus(new RestGetRefSyncStatusRequest.Builder()
                .specification(new ResponseSpecBuilder()
                        .expectBody("available", equalTo(false))
                        .expectBody("enabled", equalTo(false))
                        .build())
                .build());

        //Disabling ref sync has to be possible in cases where it's not even available. For example,
        //a fork whose upstream repository is deleted becomes a non-fork, but may have had ref sync
        //enabled when the upstream was deleted. As a result, disabling ref sync should work
        ownerHelper.setEnabled(new RestEnableRefSyncRequest.Builder(false)
                .specification(new ResponseSpecBuilder()
                        .expectBody("available", equalTo(false))
                        .expectBody("enabled", equalTo(false))
                        .build())
                .build());

        //Enable ref sync on a non-fork shouldn't be possible and should return an error message that
        //says that's the reason it couldn't be enabled
        ownerHelper.setEnabled(new RestEnableRefSyncRequest.Builder(true)
                .specification(new ResponseSpecBuilder()
                        .expectBody("errors[0].message", containsString("only available for forks"))
                        .build())
                .status(Response.Status.CONFLICT)
                .build());
    }

    @Test
    public void testToggleRefSync() {
        refSyncHelper.getStatus(new RestGetRefSyncStatusRequest.Builder()
                .specification(new ResponseSpecBuilder()
                        .expectBody("available", equalTo(true))
                        .expectBody("enabled", equalTo(false))
                        .build())
                .build());

        long start = System.currentTimeMillis();

        refSyncHelper.setEnabled(new RestEnableRefSyncRequest.Builder(true)
                .specification(new ResponseSpecBuilder()
                        .expectBody("enabled", equalTo(true))
                        .expectBody("lastSync", greaterThanOrEqualTo(start))
                        .expectBody("aheadRefs", iterableWithSize(0))
                        .expectBody("divergedRefs", iterableWithSize(0))
                        .expectBody("orphanedRefs", iterableWithSize(0))
                        .build())
                .build());

        refSyncHelper.setEnabled(new RestEnableRefSyncRequest.Builder(false)
                .specification(new ResponseSpecBuilder()
                        .expectBody("enabled", equalTo(false))
                        .build())
                .build());
    }
}
