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

import com.atlassian.bitbucket.test.DefaultFuncTestData;
import com.atlassian.bitbucket.test.RepositoryTestHelper;
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.RestRefSyncAction;
import com.atlassian.bitbucket.test.rest.repository.sync.RestSynchronizeRefRequest;
import com.jayway.restassured.builder.ResponseSpecBuilder;
import com.jayway.restassured.path.json.JsonPath;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.core.io.ClassPathResource;

import javax.ws.rs.core.Response;
import java.util.concurrent.TimeUnit;

import static org.hamcrest.Matchers.equalTo;

/**
 * Runs tests for applying ref sync to diverged branches.
 * <p>
 * These tests are separate from {@link RefSyncResourceTest} because they require recreating the upstream repository
 * on each test, as well as the fork, due to the upstream push necessary to produce divergence.
 */
public class RefSyncDivergenceTest extends BaseRefSyncTest {

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    @Before
    public void setup() throws Exception {
        //Fork and upstream are both intentionally per-test since diverging the two modifies both
        prepareUpstream(testContext);
        prepareFork(testContext);

        divergeRefs();
    }

    @Test
    public void testDiscard() {
        refSyncHelper.synchronize(new RestSynchronizeRefRequest.Builder("refs/heads/master")
                .action(RestRefSyncAction.DISCARD)
                .status(Response.Status.NO_CONTENT) //The ref should be in sync after DISCARD completes
                .build());

        refSyncHelper.getStatus(new RestGetRefSyncStatusRequest.Builder()
                .specification(new ResponseSpecBuilder()
                        .expectBody("available", equalTo(true))
                        .expectBody("enabled", equalTo(true))
                        .expectBody("aheadRefs.size()", equalTo(0))
                        .expectBody("divergedRefs.size()", equalTo(0))
                        .expectBody("orphanedRefs.size()", equalTo(0))
                        .build())
                .build());
    }

    //This verifies both overall and per-ref status in a single test just to reduce setup/teardown overhead
    @Test
    public void testGetStatus() {
        //Full status
        refSyncHelper.getStatus(new RestGetRefSyncStatusRequest.Builder()
                .specification(new ResponseSpecBuilder()
                        .expectBody("available", equalTo(true))
                        .expectBody("enabled", equalTo(true))
                        .expectBody("aheadRefs.size()", equalTo(0))
                        .expectBody("divergedRefs.size()", equalTo(1))
                        .expectBody("divergedRefs[0].id", equalTo("refs/heads/master"))
                        .expectBody("divergedRefs[0].displayId", equalTo("master"))
                        .expectBody("divergedRefs[0].state", equalTo("DIVERGED"))
                        .expectBody("divergedRefs[0].tag", equalTo(false))
                        .expectBody("orphanedRefs.size()", equalTo(0))
                        .build())
                .build());

        //Specific ref status
        refSyncHelper.getStatus(new RestGetRefSyncStatusRequest.Builder()
                .refId("refs/heads/master")
                .specification(new ResponseSpecBuilder()
                        .expectBody("id", equalTo("refs/heads/master"))
                        .expectBody("displayId", equalTo("master"))
                        .expectBody("state", equalTo("DIVERGED"))
                        .expectBody("tag", equalTo(false))
                        .build())
                .build());
    }

    @Test
    public void testMerge() {
        refSyncHelper.synchronize(new RestSynchronizeRefRequest.Builder("refs/heads/master")
                .action(RestRefSyncAction.MERGE)
                .specification(new ResponseSpecBuilder()        //Merging in the latest from upstream still
                        .expectBody("id", equalTo("refs/heads/master")) //leaves changes that are only present in
                        .expectBody("displayId", equalTo("master"))     //the fork, so the ref's status becomes
                        .expectBody("state", equalTo("AHEAD"))          //AHEAD rather than in sync
                        .expectBody("tag", equalTo(false))
                        .build())
                .status(Response.Status.OK) //Status should be 200 instead of 204 since the ref is still out of sync
                .build());

        refSyncHelper.getStatus(new RestGetRefSyncStatusRequest.Builder()
                .specification(new ResponseSpecBuilder()
                        .expectBody("available", equalTo(true))
                        .expectBody("enabled", equalTo(true))
                        .expectBody("aheadRefs.size()", equalTo(1))
                        .expectBody("aheadRefs[0].id", equalTo("refs/heads/master"))
                        .expectBody("aheadRefs[0].displayId", equalTo("master"))
                        .expectBody("aheadRefs[0].state", equalTo("AHEAD"))
                        .expectBody("aheadRefs[0].tag", equalTo(false))
                        .expectBody("divergedRefs.size()", equalTo(0))
                        .expectBody("orphanedRefs.size()", equalTo(0))
                        .build())
                .build());
    }

    private void divergeRefs() throws Exception {
        //Enable ref sync
        JsonPath status = refSyncHelper.setEnabled(new RestEnableRefSyncRequest.Builder(true).build());

        //Make the fork diverge from upstream by completely rewriting upstream's branches
        RepositoryTestHelper.pushRep(folder, DefaultFuncTestData.getBaseURL(), OWNER, OWNER, OWNER_PROJECT,
                REPOSITORY_SLUG, new ClassPathResource("git/pull-requests.zip"));

        //Now, wait for ref sync to be processed. This should mark master as diverged, because
        //master from the other repository should be non-fast-forward to the fork's master
        refSyncHelper.waitForAutoSync(status.getLong("lastSync"), 45L, TimeUnit.SECONDS);
    }
}
