package it.com.atlassian.stash.hosting;

import com.atlassian.stash.hosting.ScmTransport;
import com.atlassian.stash.hosting.SshKeyPair;
import com.atlassian.stash.hosting.SshScmTransport;
import com.atlassian.bitbucket.pull.PullRequestAction;
import com.atlassian.bitbucket.test.GitTestHelper;
import com.atlassian.bitbucket.test.ProcessFailedException;
import com.atlassian.bitbucket.test.ProcessTestHelper;
import com.atlassian.bitbucket.test.PullRequestTestHelper;
import com.atlassian.bitbucket.permission.Permission;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.response.Response;
import org.apache.commons.lang3.StringUtils;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;

import static javax.ws.rs.core.Response.Status.OK;
import static org.hamcrest.Matchers.equalTo;

public class SshAccessKeyHostingTest extends BaseHostingTest {

    private static final String PROJ_KEY = "Project" + SshAccessKeyHostingTest.class.getSimpleName();
    private static final String REPO_SLUG = "accrepo";
    private static final String OTHER_REPO_SLUG = "otherRepo";
    private static final String RESCOPING_TEST_REPO_SLUG = "rescopingTestRepo";
    private static final String USERNAME = "user";
    private static final String PASSWORD = "user";
    private static final String PULL_REQUEST_TITLE = "TEST_PR";
    private static final String PULL_REQUEST_DESCRIPTION = "PR Created as part of test: " + SshAccessKeyHostingTest.class.getSimpleName();
    protected static final String PULL_REQUEST_BRANCH = "branch_created_for_" + SshAccessKeyHostingTest.class.getSimpleName();
    protected static final String FQ_PULL_REQUEST_BRANCH = "refs/heads/" + PULL_REQUEST_BRANCH;
    protected static final String MASTER_BRANCH = "master";
    protected static final String FQ_MASTER_BRANCH = "refs/heads/" + MASTER_BRANCH;

    private static ScmTransport sshTransport;

    @BeforeClass
    public static void setupTestData() throws Exception {
        getClassTestContext()
                .project(PROJ_KEY, "SSH Access Keys Project")
                .repository(PROJ_KEY, REPO_SLUG)
                .repository(PROJ_KEY, OTHER_REPO_SLUG)
                .projectPermission(PROJ_KEY, USERNAME, Permission.PROJECT_WRITE);
        sshTransport = new SshScmTransport();
    }

    @Test(expected = ProcessFailedException.class)
    public void testNoAccessWithoutAccessKeys() throws IOException {
        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, REPO_SLUG);
    }

    @Test
    public void testProjectAccessKeyCanClone() throws IOException {
        testContext.accessKey(PROJ_KEY, Permission.PROJECT_READ, SshKeyPair.get().getPublicKey());

        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, REPO_SLUG);
    }

    @Test(expected = ProcessFailedException.class)
    public void testProjectReadAccessCannotPush() throws Exception {
        testContext.accessKey(PROJ_KEY, Permission.PROJECT_READ, SshKeyPair.get().getPublicKey());

        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, OTHER_REPO_SLUG);

        commitAndPush(PROJ_KEY, REPO_SLUG);
    }

    @Test
    public void testProjectWriteAccessCanPush() throws Exception {
        testContext.accessKey(PROJ_KEY, Permission.PROJECT_WRITE, SshKeyPair.get().getPublicKey());

        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, REPO_SLUG);

        commitAndPush(PROJ_KEY, REPO_SLUG);
    }

    @Test
    public void testRepoAccessKeyCanClone() throws IOException {
        testContext.accessKey(PROJ_KEY, REPO_SLUG, Permission.REPO_READ, SshKeyPair.get().getPublicKey());

        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, REPO_SLUG);
    }

    @Test(expected = ProcessFailedException.class)
    public void testRepoAccessKeyCannotCloneFromSiblingRepo() throws IOException {
        testContext.accessKey(PROJ_KEY, REPO_SLUG, Permission.REPO_READ, SshKeyPair.get().getPublicKey());

        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, OTHER_REPO_SLUG);
    }

    @Test(expected = ProcessFailedException.class)
    public void testRepoReadAccessCannotPush() throws Exception {
        testContext.accessKey(PROJ_KEY, REPO_SLUG, Permission.REPO_READ, SshKeyPair.get().getPublicKey());

        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, OTHER_REPO_SLUG);

        commitAndPush(PROJ_KEY, REPO_SLUG);
    }

    @Test
    public void testRepoWriteAccessCanPush() throws Exception {
        testContext.accessKey(PROJ_KEY, REPO_SLUG, Permission.REPO_WRITE, SshKeyPair.get().getPublicKey());

        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, REPO_SLUG);

        commitAndPush(PROJ_KEY, REPO_SLUG);
    }

    @Test
    public void testRescopingAfterPushByServiceUser() throws Exception {
        String publicKey = SshKeyPair.get().getPublicKey();
        testContext.repository(PROJ_KEY, RESCOPING_TEST_REPO_SLUG);
        testContext.accessKey(PROJ_KEY, RESCOPING_TEST_REPO_SLUG, Permission.REPO_WRITE, publicKey);

        sshTransport.clone(tempFolder.getRoot(), PROJ_KEY, RESCOPING_TEST_REPO_SLUG);
        commitAndPush(PROJ_KEY, RESCOPING_TEST_REPO_SLUG);

        createBranch(FQ_PULL_REQUEST_BRANCH);
        commitAndPush(PROJ_KEY, RESCOPING_TEST_REPO_SLUG);

        PullRequestTestHelper pullRequest = createPullRequest(FQ_PULL_REQUEST_BRANCH, FQ_MASTER_BRANCH, USERNAME);

        commitAndPush(PROJ_KEY, RESCOPING_TEST_REPO_SLUG);

        // rescoping happens asynchronously - wait until the RESCOPED activity appears or timeout after 5s
        long start = System.currentTimeMillis();
        Integer activityCount;
        do {
            Response response = RestAssured.given()
                    .auth().preemptive().basic(USERNAME, PASSWORD)
                    .contentType("application/json")
                    .when()
                    .get(pullRequest.getActivitiesUrl());
             activityCount = response.body().jsonPath().get("size");
        } while (activityCount != null && activityCount < 2 && (System.currentTimeMillis() - start) < 5000);

        String serviceUserName = String.format("Access key user (%s)", StringUtils.abbreviateMiddle(publicKey, "...", 64));
        RestAssured.expect().statusCode(OK.getStatusCode())
                .body("size", equalTo(2))
                .body("values[0].user.displayName", equalTo(serviceUserName))
                .body("values[0].action", equalTo(PullRequestAction.RESCOPED.name()))
                .body("values[1].user.name", equalTo(USERNAME))
                .body("values[1].action", equalTo(PullRequestAction.OPENED.name()))
                .log().ifError()
                .given()
                .auth().preemptive().basic(USERNAME, PASSWORD)
                .contentType("application/json")
                .when()
                .get(pullRequest.getActivitiesUrl());
    }

    private void createBranch(String branchName) {
        ProcessTestHelper.execute(tempFolder.getRoot(), GitTestHelper.getGitPath(), "checkout", "-b", branchName);
    }

    private PullRequestTestHelper createPullRequest(String sourceBranch, String destinationBranch, String... reviewers) {
        PullRequestTestHelper.Builder pullRequestBuilder = new PullRequestTestHelper.Builder(
                USERNAME, PASSWORD, PULL_REQUEST_TITLE, PULL_REQUEST_DESCRIPTION,
                PROJ_KEY, RESCOPING_TEST_REPO_SLUG, sourceBranch,
                PROJ_KEY, RESCOPING_TEST_REPO_SLUG, destinationBranch).reviewers(reviewers);
        return testContext.pullRequest(pullRequestBuilder);
    }

    private void commitAndPush(String projectKey, String repositorySlug) throws Exception {
        File localRepo = tempFolder.getRoot();

        // add a file, commit and push the repository to Stash
        HostingTestHelper.createAndAdd(localRepo, UUID.randomUUID() + ".txt", UUID.randomUUID().toString());
        HostingTestHelper.commit(localRepo, "test commit at " + new Date());
        sshTransport.pushAllRefs(localRepo, projectKey, repositorySlug);
    }
}
