package it.com.atlassian.stash.rest.webhook;

import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.test.PermissionsTestHelper;
import com.atlassian.bitbucket.test.ProjectTestHelper;
import com.atlassian.bitbucket.test.RepositoryTestHelper;
import com.atlassian.bitbucket.test.rest.server.stub.webhook.StubWebHookSink;
import com.google.common.collect.ImmutableList;
import it.com.atlassian.stash.BaseRestTest;
import org.apache.commons.lang3.mutable.MutableInt;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;

import static com.atlassian.bitbucket.test.JsonMatcher.*;
import static com.atlassian.bitbucket.test.WebHookTestHelper.filterOnProjectWithId;
import static com.atlassian.bitbucket.test.WebHookTestHelper.filterOnRepositoryWithId;
import static it.com.atlassian.stash.rest.webhook.WebHookCallbackHelper.assertWebHookCallbackArrivedOn;
import static it.com.atlassian.stash.rest.webhook.WebHookCallbackHelper.assertWebHookCallbackDidntArrivedOn;
import static org.hamcrest.core.AllOf.allOf;

@RunWith(MockitoJUnitRunner.class)
public class WebHookCallbackFilteringTest extends BaseRestTest {
    private static final String PROJECT_1_KEY = WebHookCallbackFilteringTest.class.getSimpleName() + "1";
    private static final String USER_NAME = "user2";

    private static MutableInt project1Id = new MutableInt();

    private StubWebHookSink stubWebHookSink;

    @BeforeClass
    public static void classSetUp() throws Exception {
        getClassTestContext()
                .project(PROJECT_1_KEY, project1Id::setValue)
                .user(USER_NAME);
    }

    @Rule
    public TestName testName = new TestName();

    @Before
    public void startStubWebHook() {
        stubWebHookSink = new StubWebHookSink();
        stubWebHookSink.configureDefaultResponders();
    }

    @Before
    public void restoreUserPermissions() throws Exception {
        testContext.projectPermission(PROJECT_1_KEY, USER_NAME, Permission.PROJECT_READ);
    }

    @Test
    public void testFilteringProjectWebhookEventsOnProject() throws Exception {
        testContext.webHook(
                testName.getMethodName(),
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("project:*"), filterOnProjectWithId(project1Id.intValue()), USER_NAME, USER_NAME);

        ProjectTestHelper.updateProject(PROJECT_1_KEY, PROJECT_1_KEY, null, "New description");

        assertWebHookCallbackArrivedOn(stubWebHookSink,
                "project:modified",
                () -> allOf(
                        propEqualsIgnoreCase("eventKey", "project:modified"),
                        propNotNull("date"),
                        propEqualsIgnoreCase("actor.name", "admin"),
                        propEqualsIgnoreCase("old.key", PROJECT_1_KEY),
                        propEqualsIgnoreCase("new.description", "New description"),
                        propEqualsIgnoreCase("new.key", PROJECT_1_KEY)
                ));
    }

    @Test
    public void testFilteringProjectWebhookEventsOnProjectWithUnfilteredProjectOperations() throws Exception {
        testContext.webHook(
                testName.getMethodName(),
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("project:*"), filterOnProjectWithId(project1Id.intValue()), USER_NAME, USER_NAME);

        // triggers a project web hook event but not one we have filtered for - there should be no callback
        testContext.project(testName.getMethodName());

        assertWebHookCallbackDidntArrivedOn(stubWebHookSink);
    }

    @Test
    public void testFilteringProjectWebhookEventsOnProjectWhenUserLosesPermission() throws Exception {
        testContext.webHook(
                testName.getMethodName(),
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("project:*"), filterOnProjectWithId(project1Id.intValue()), USER_NAME, USER_NAME);

        PermissionsTestHelper.revokeProjectPermissionForUser(PROJECT_1_KEY, USER_NAME);

        ProjectTestHelper.updateProject(PROJECT_1_KEY, PROJECT_1_KEY, null, "New description");

        assertWebHookCallbackArrivedOn(stubWebHookSink,
                "project:modified",
                () -> allOf(
                        propEqualsIgnoreCase("eventKey", "project:modified"),
                        propNotNull("date"),
                        propEqualsIgnoreCase("actor.name", "admin"),
                        propEqualsIgnoreCase("old.key", PROJECT_1_KEY),
                        propEqualsIgnoreCase("new.description", "New description"),
                        propEqualsIgnoreCase("new.key", PROJECT_1_KEY)
                ));
    }

    @Test
    public void testFilteringRepositoryWebhookEventsOnProject() throws Exception {
        String testName = this.testName.getMethodName();

        testContext.webHook(
                testName,
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("repo:*"), filterOnProjectWithId(project1Id.intValue()), USER_NAME, USER_NAME);

        testContext.repository(PROJECT_1_KEY, testName);

        assertWebHookCallbackArrivedOn(stubWebHookSink,
                "repo:created",
                () -> allOf(
                        propEqualsIgnoreCase("eventKey", "repo:created"),
                        propNotNull("date"),
                        propEqualsIgnoreCase("actor.name", "admin"),
                        propEqualsIgnoreCase("repository.slug", testName),
                        propEqualsIgnoreCase("repository.project.key", PROJECT_1_KEY)
                ));
    }

    @Test
    public void testFilteringRepositoryWebhookEventsOnProjectWithUnfilteredProjectOperations() throws Exception {
        String testName = this.testName.getMethodName();

        testContext.webHook(
                testName,
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("repo:*"), filterOnProjectWithId(project1Id.intValue()), USER_NAME, USER_NAME);

        // triggers a repo web hook event but not one we have filtered for - there should be no callback
        testContext.project(testName).repository(testName, testName);

        assertWebHookCallbackDidntArrivedOn(stubWebHookSink);
    }

    @Test
    public void testFilteringRepositoryWebhookEventsOnProjectWhenUserLosesPermission() throws Exception {
        String testName = this.testName.getMethodName();

        testContext.webHook(
                testName,
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("repo:*"), filterOnProjectWithId(project1Id.intValue()), USER_NAME, USER_NAME);

        PermissionsTestHelper.revokeProjectPermissionForUser(PROJECT_1_KEY, USER_NAME);

        testContext.repository(PROJECT_1_KEY, testName);

        assertWebHookCallbackArrivedOn(stubWebHookSink,
                "repo:created",
                () -> allOf(
                        propEqualsIgnoreCase("eventKey", "repo:created"),
                        propNotNull("date"),
                        propEqualsIgnoreCase("actor.name", "admin"),
                        propEqualsIgnoreCase("repository.slug", testName),
                        propEqualsIgnoreCase("repository.project.key", PROJECT_1_KEY)));
    }

    @Test
    public void testFilteringRepositoryWebhookEventsOnRepository() throws Exception {
        String testName = this.testName.getMethodName();
        MutableInt repoId = new MutableInt();
        testContext.repository(PROJECT_1_KEY, testName, repoId::setValue);

        testContext.webHook(
                testName,
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("repo:*"), filterOnRepositoryWithId(repoId.intValue()), USER_NAME, USER_NAME);

        RepositoryTestHelper.setRepositoryPublicAccessEnabled(PROJECT_1_KEY, testName, true);

        assertWebHookCallbackArrivedOn(stubWebHookSink,
                "repo:modified",
                () -> allOf(
                        propEqualsIgnoreCase("eventKey", "repo:modified"),
                        propNotNull("date"),
                        propEqualsIgnoreCase("actor.name", "admin"),
                        propEqualsIgnoreCase("old.slug", testName),
                        propIs("old.public", false),
                        propEqualsIgnoreCase("new.slug", testName),
                        propIs("new.public", true)
                ));
    }

    @Test
    public void testFilteringRepositoryWebhookEventsOnRepositoryWithUnfilteredRepoOperations() throws Exception {
        String testName = this.testName.getMethodName();
        MutableInt repoId = new MutableInt();
        testContext.repository(PROJECT_1_KEY, testName, repoId::setValue);

        testContext.webHook(
                testName,
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("repo:*"), filterOnRepositoryWithId(repoId.intValue()), USER_NAME, USER_NAME);

        testContext.repository(PROJECT_1_KEY, testName + "2");

        assertWebHookCallbackDidntArrivedOn(stubWebHookSink);
    }

    @Test
    public void testFilteringRepositoryWebhookEventsOnRepositoryWhenUserLosesPermission() throws Exception {
        String testName = this.testName.getMethodName();
        MutableInt repoId = new MutableInt();
        testContext.repository(PROJECT_1_KEY, testName, repoId::setValue);

        testContext.webHook(
                testName,
                stubWebHookSink.getUrl(stubWebHookSink.getWebhookEventsPath()),
                ImmutableList.of("repo:*"), filterOnRepositoryWithId(repoId.intValue()), USER_NAME, USER_NAME);

        PermissionsTestHelper.revokeProjectPermissionForUser(PROJECT_1_KEY, USER_NAME);

        RepositoryTestHelper.setRepositoryPublicAccessEnabled(PROJECT_1_KEY, testName, true);

        assertWebHookCallbackArrivedOn(stubWebHookSink,
                "repo:modified",
                () -> allOf(
                        propEqualsIgnoreCase("eventKey", "repo:modified"),
                        propNotNull("date"),
                        propEqualsIgnoreCase("actor.name", "admin"),
                        propEqualsIgnoreCase("old.slug", testName),
                        propIs("old.public", false),
                        propEqualsIgnoreCase("new.slug", testName),
                        propIs("new.public", true)
                ));
    }
}
