package it.com.atlassian.stash.branch;

import com.atlassian.webdriver.bitbucket.page.admin.repo.BranchModelSettingsPage;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.specification.RequestSpecification;
import it.com.atlassian.stash.BaseBrowserFuncTest;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;

import static com.atlassian.pageobjects.elements.query.Poller.*;
import static com.atlassian.bitbucket.test.DefaultFuncTestData.*;
import static com.atlassian.webdriver.bitbucket.page.admin.repo.BranchModelSettingsPage.*;
import static javax.ws.rs.core.Response.Status.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;

public class BranchModelSettingsTest extends BaseBrowserFuncTest {

    private static final String BRANCHMODEL_REPO = "branchmodel_repo";
    private static final String EMPTY_REPO = "empty_repo";
    private static final String NON_EMPTY_REPO = "non_empty_repo";

    @BeforeClass
    public static void createData() throws IOException {
        // Default repository1 will have no branch model
        getClassTestContext().repository(getProject1(), BRANCHMODEL_REPO, new ClassPathResource("git/default-repository.zip"));
        deleteModel(BRANCHMODEL_REPO);
    }

    @After
    public void resetModel() {
        resetModel(BRANCHMODEL_REPO);
        deleteModel(BRANCHMODEL_REPO);
    }

    @Test
    public void testEnableBranchModelOnNonEmptyRepo() {
        testContext.repository(getProject1(), NON_EMPTY_REPO);
        BranchModelSettingsPage page = loginAsAdmin(BranchModelSettingsPage.class, getProject1(), NON_EMPTY_REPO);
        assertTrue(page.isEnabled());
        assertDefaultBranchModelValues(page);
        assertFalse(page.disable().isEnabled());
    }

    @Test
    public void testEnableBranchModelOnEmptyRepo() {
        testContext.repository(getProject1(), EMPTY_REPO);
        BranchModelSettingsPage page = loginAsAdmin(BranchModelSettingsPage.class, getProject1(), EMPTY_REPO);
        assertTrue(page.isEnabled());
        assertDefaultBranchModelValues(page);
        assertFalse(page.disable().isEnabled());
    }

    @Test
    public void testUpdateAndReenableBranchModel() {
        BranchModelSettingsPage page = loginAsAdmin(BranchModelSettingsPage.class, getProject1(), BRANCHMODEL_REPO);
        assertTrue(page.enable().isEnabled());
        page.getProductionSelector().open().selectItemById("refs/heads/master");
        page.getBranchTypeConfiguration(BUGFIX_TYPE_ID).setPrefix("bugz/");
        page.getBranchTypeConfiguration(HOTFIX_TYPE_ID).disable();

        page = page.update();
        assertTrue("Branch model should have been updated", page.isSuccessfulUpdate());
        waitUntilEquals("refs/heads/master", page.getProductionSelector().getSelectedValue());
        assertEquals("bugz/", page.getBranchTypeConfiguration(BUGFIX_TYPE_ID).getPrefix());
        waitUntilFalse(page.getBranchTypeConfiguration(HOTFIX_TYPE_ID).isEnabled());

        // disable and re-enable branch model, the custom values should pertain
        assertFalse(page.disable().isEnabled());
        assertTrue(page.enable().isEnabled());
        waitUntilEquals("refs/heads/master", page.getProductionSelector().getSelectedValue());
        assertEquals("bugz/", page.getBranchTypeConfiguration(BUGFIX_TYPE_ID).getPrefix());
        waitUntilFalse(page.getBranchTypeConfiguration(HOTFIX_TYPE_ID).isEnabled());
    }

    @Test
    public void testResetToDefault() {
        BranchModelSettingsPage page = loginAsAdmin(BranchModelSettingsPage.class, getProject1(), BRANCHMODEL_REPO);
        assertTrue(page.enable().isEnabled());
        page.getDevelopmentSelector().open().selectItemById("refs/heads/master");
        page.getProductionSelector().open().selectItemById("refs/heads/master");
        page.getBranchTypeConfiguration(FEATURE_TYPE_ID).setPrefix("featurez/");
        page.getBranchTypeConfiguration(RELEASE_TYPE_ID).disable();

        page = page.update();

        // update and reset to defaults - should be back to stock valuues after that
        page.resetToDefaults();
        assertDefaultBranchModelValues(page); // reset back to defaults but not saved
        page = page.update();
        assertDefaultBranchModelValues(page); // defaults are saved
    }

    @Test
    public void testNotAccessibleToUser() {
        loginAsRegularUser(WithError.class, getProject1(), BRANCHMODEL_REPO, 401, "Access is denied");
    }

    @Test
    public void testAutoMergeEnabling() {
        BranchModelSettingsPage page = loginAsAdmin(BranchModelSettingsPage.class, getProject1(), BRANCHMODEL_REPO);
        assertTrue("Branch Model should be enabled", page.enable().isEnabled());

        waitUntilFalse("Production branch should not be set", page.getProductionSelector().hasValue());
        waitUntilTrue("Release category should be enabled", page.getBranchTypeConfiguration(RELEASE_TYPE_ID).isEnabled());
        waitUntilFalse("Auto-merge should be off by default", page.isAutoMergeEnabled());

        waitUntilTrue("Auto-merge should be enabled", page.enableAutoMerge().isAutoMergeEnabled());

        // disabling release should disable auto-merge
        page.getBranchTypeConfiguration(RELEASE_TYPE_ID).disable();
        waitUntilFalse("Auto-merge should be disabled after disabling the Release category", page.isAutoMergeEnabled());

        // selecting a production branch should enable auto-merge
        page.getProductionSelector().open().selectItemById("refs/heads/master");
        waitUntilTrue("Auto-merge should be enabled", page.enableAutoMerge().isAutoMergeEnabled());

        // removing a branch should disable it again
        page.clearProductionSelector();
        waitUntilFalse("Auto-merge should be disabled after removing the production branch", page.isAutoMergeEnabled());

        page.getBranchTypeConfiguration(RELEASE_TYPE_ID).enable();
        page.enableAutoMerge();
        waitUntilTrue("Auto-merge should be re-enabled after re-enabling the Release category", page.update().isAutoMergeEnabled());
    }

    @Test
    public void testIllegalBranchPrefix() {
        BranchModelSettingsPage page = loginAsAdmin(BranchModelSettingsPage.class, getProject1(), BRANCHMODEL_REPO);
        assertTrue("Branch Model should be enabled", page.enable().isEnabled());
        page.getBranchTypeConfiguration(BUGFIX_TYPE_ID).setPrefix("  ");
        page.getBranchTypeConfiguration(FEATURE_TYPE_ID).setPrefix("--");
        assertFalse("Update should not be successful", page.update().isSuccessfulUpdate());
    }

    private static void assertDefaultBranchModelValues(BranchModelSettingsPage page) {
        waitUntil(page.getDevelopmentSelector().getSelectedValue(), containsString("DEFAULT"));
        waitUntilFalse(page.getProductionSelector().hasValue());
        waitUntilTrue(page.getBranchTypeConfiguration(BUGFIX_TYPE_ID).isEnabled());
        assertEquals("bugfix/", page.getBranchTypeConfiguration(BUGFIX_TYPE_ID).getPrefix());
        waitUntilTrue(page.getBranchTypeConfiguration(FEATURE_TYPE_ID).isEnabled());
        assertEquals("feature/", page.getBranchTypeConfiguration(FEATURE_TYPE_ID).getPrefix());
        waitUntilTrue(page.getBranchTypeConfiguration(HOTFIX_TYPE_ID).isEnabled());
        assertEquals("hotfix/", page.getBranchTypeConfiguration(HOTFIX_TYPE_ID).getPrefix());
        waitUntilTrue(page.getBranchTypeConfiguration(RELEASE_TYPE_ID).isEnabled());
        assertEquals("release/", page.getBranchTypeConfiguration(RELEASE_TYPE_ID).getPrefix());
        waitUntilTrue(page.isAutoMergeFieldEnabled());
        waitUntilFalse(page.isAutoMergeEnabled());
    }

    private static void deleteModel(String repositoryKey) {
        requestAs(getAdminUser(), getAdminPassword())
                .expect().statusCode(NO_CONTENT.getStatusCode())
                .log().ifStatusCodeMatches(not(NO_CONTENT.getStatusCode()))
                .delete(getBranchModelConfigurationPath(repositoryKey));
    }

    private static void resetModel(String repositoryKey) {
        Matcher<Integer> statusMatcher = Matchers.isOneOf(OK.getStatusCode(), CREATED.getStatusCode());
        requestAs(getAdminUser(), getAdminPassword())
                .expect().statusCode(statusMatcher)
                .log().ifStatusCodeMatches(not(statusMatcher))
                .with().body("{ \"useDefault\" : true }")
                .put(getBranchModelConfigurationPath(repositoryKey));
    }

    private static RequestSpecification requestAs(String username, String password) {
        return RestAssured.given().auth().preemptive().basic(username, password).contentType(ContentType.JSON);
    }

    private static String getBranchModelPath(String repoKey) {
        return getRepositoryRestURL("branch-utils", "latest", getProject1(), repoKey) + "/branchmodel";
    }

    private static String getBranchModelConfigurationPath(String repoKey) {
        return getBranchModelPath(repoKey) + "/configuration";
    }
}
