package com.atlassian.bitbucket.internal.mirroring.mirror.rest.webhook.cloud;

import com.atlassian.bitbucket.internal.mirroring.mirror.ExternalRepository;
import com.atlassian.bitbucket.internal.mirroring.mirror.MinimalExternalRepository;
import com.atlassian.bitbucket.internal.mirroring.mirror.rest.MirrorJsonEntity;
import com.atlassian.bitbucket.repository.MinimalRef;
import com.atlassian.bitbucket.repository.RefChange;
import com.atlassian.bitbucket.repository.RefChangeType;
import com.atlassian.bitbucket.repository.StandardRefType;
import com.atlassian.bitbucket.scm.git.GitRefPattern;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;

import java.util.List;
import java.util.Objects;

import static com.atlassian.bitbucket.internal.mirroring.mirror.rest.cloud.RestCloudTestUtils.assertRepository;
import static com.atlassian.bitbucket.repository.RefChangeType.ADD;
import static com.atlassian.bitbucket.repository.RefChangeType.UPDATE;
import static com.atlassian.bitbucket.repository.StandardRefType.BRANCH;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.junit.Assert.*;

@SuppressWarnings("unchecked")
public class RestCloudRepositoryPushWebHookEventTest {
    @Rule
    public MirrorJsonEntity emptyGitChangesPushJson = new MirrorJsonEntity("cloud/json/webhook-git-repo-push-empty-changes.json");
    @Rule
    public MirrorJsonEntity multipleGitChangesPushJson = new MirrorJsonEntity("cloud/json/webhook-git-repo-push-multiple-changes.json");
    @Rule
    public MirrorJsonEntity singleGitChangePushJson = new MirrorJsonEntity("cloud/json/webhook-git-repo-push-single-change.json");
    @Rule
    public MirrorJsonEntity singleHgChangePushJson = new MirrorJsonEntity("cloud/json/webhook-hg-repo-push-single-change.json");

    /**
     * This can happen when non-standard refs are changed on cloud e.g. not refs/heads/* or refs/tags/*
     * We get a web hook but the change list is empty
     */
    @Test
    public void testPushWithEmptyGitChanges() throws Exception {
        RestCloudRepositoryPushWebHookEvent event = new RestCloudRepositoryPushWebHookEvent(emptyGitChangesPushJson.getValue());

        MinimalExternalRepository repository = event.getRepository();
        assertNotNull(repository);
        assertFalse(repository instanceof ExternalRepository);
        assertEquals("{77d94df8-8cf7-4c2f-83a3-ae50acf0ecc6}", repository.getId());
        assertFalse(repository.isPublic());
        assertEquals("test", repository.getName());
        assertEquals("test", repository.getSlug());
        assertEquals("git", repository.getScmId());

        List<RefChange> changes = event.getChanges();
        assertNotNull(changes);
        assertEquals(0, changes.size());
    }

    @Test
    public void testPushWithMultipleGitChanges() throws Exception {
        RestCloudRepositoryPushWebHookEvent event = new RestCloudRepositoryPushWebHookEvent(multipleGitChangesPushJson.getValue());

        MinimalExternalRepository repository = event.getRepository();
        assertNotNull(repository);
        assertFalse(repository instanceof ExternalRepository);
        assertRepository(repository, "{77d94df8-8cf7-4c2f-83a3-ae50acf0ecc6}", false, "test", "test", "git");

        List<RefChange> changes = event.getChanges();
        assertNotNull(changes);
        assertEquals(11, changes.size());

        assertThat(changes, hasItems(
            refChange(UPDATE, BRANCH, "master", "081337d788b27b07c268c62722e70317cedca518", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo1", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo2", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo3", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo4", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo5", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo6", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo7", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo8", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo9", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553"),
            refChange(ADD, BRANCH, "foo10", "0000000000000000000000000000000000000000", "873f9240bc808e7a1a119c0e907d34fa95779553")));
    }

    @Test
    public void testPushWithSingleGitChange() throws Exception {
        RestCloudRepositoryPushWebHookEvent event = new RestCloudRepositoryPushWebHookEvent(singleGitChangePushJson.getValue());

        MinimalExternalRepository repository = event.getRepository();
        assertNotNull(repository);
        assertFalse(repository instanceof ExternalRepository);
        assertRepository(repository, "{77d94df8-8cf7-4c2f-83a3-ae50acf0ecc6}", false, "test", "test", "git");

        List<RefChange> changes = event.getChanges();
        assertNotNull(changes);
        assertEquals(1, changes.size());

        assertThat(changes, hasItems(
            refChange(UPDATE, BRANCH, "master", "e719bc67cba2edfa6279697c4d3cd1d769b6c439", "b6075009e072a69a27c11450e3ce141c0ee5555b")));
    }

    @Test
    public void testPushWithSingleHgChange() throws Exception {
        RestCloudRepositoryPushWebHookEvent event = new RestCloudRepositoryPushWebHookEvent(singleHgChangePushJson.getValue());

        MinimalExternalRepository repository = event.getRepository();
        assertNotNull(repository);
        assertFalse(repository instanceof ExternalRepository);
        assertRepository(repository, "{921c327a-36d1-4801-ae7f-708dd7e544b0}", false, "test4", "test4", "hg");

        List<RefChange> changes = event.getChanges();
        assertNotNull(changes);
        assertEquals(1, changes.size());

        assertThat(changes, hasItems(
            refChange(ADD, BRANCH, "default", "0000000000000000000000000000000000000000", "84b3c3915b1e5e3c66423f7543a7972e69a7a735", false)));
    }

    protected Matcher<RefChange> refChange(RefChangeType changeType, StandardRefType refType, String branch, String fromHash, String toHash) {
        return refChange(changeType, refType, branch, fromHash, toHash, true);
    }

    protected Matcher<RefChange> refChange(RefChangeType changeType, StandardRefType refType, String ref, String fromHash, String toHash, boolean isGit) {
        String fqRef;
        if (isGit) {
            switch (refType) {
                case BRANCH:
                    fqRef = GitRefPattern.HEADS.qualify(ref);
                    break;
                default:
                    fqRef = GitRefPattern.TAGS.qualify(ref);
                    break;
            }
        } else {
            fqRef = ref;
        }

        return new BaseMatcher<RefChange>() {
            @Override
            public void describeTo(Description description) {
                description.appendText(String.format(
                        "RefChange[type=%s, id=%s, displayId=%s, fromHash=%s, toHash=%s]",
                        changeType, fqRef, ref, fromHash, toHash));
            }

            @Override
            public boolean matches(Object item) {
                RefChange refChange = (RefChange) item;
                MinimalRef minimalRef = refChange.getRef();

                return Objects.equals(refChange.getType(), changeType)
                        && Objects.equals(fromHash, refChange.getFromHash())
                        && Objects.equals(toHash, refChange.getToHash())
                        && Objects.equals(fqRef, refChange.getRefId())
                        && Objects.equals(BRANCH, minimalRef.getType())
                        && Objects.equals(fqRef, minimalRef.getId())
                        && Objects.equals(ref, minimalRef.getDisplayId());
            }
        };
    }
}