package com.atlassian.stash.internal.plugin.hooks.forcepush;

import com.atlassian.bitbucket.repository.*;
import com.atlassian.fugue.Iterables;
import com.atlassian.bitbucket.commit.Commit;
import com.atlassian.bitbucket.commit.CommitService;
import com.atlassian.bitbucket.commit.CommitsBetweenRequest;
import com.atlassian.bitbucket.hook.HookResponse;
import com.atlassian.bitbucket.hook.repository.RepositoryHookContext;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.i18n.SimpleI18nService;
import com.atlassian.stash.internal.io.LineFeedPrintWriter;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageImpl;
import com.atlassian.bitbucket.util.PageRequest;
import com.google.common.collect.Lists;
import org.junit.Test;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;

import static com.atlassian.bitbucket.repository.RefTestUtils.minimalBranch;
import static com.atlassian.bitbucket.repository.RefTestUtils.minimalTag;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ForcePushHookTest {

    @Test
    public void testShouldFailOnUpdateWithChanges() throws Exception {
        Iterable<Commit> deletedCommits = Lists.newArrayList(
                mock(Commit.class)
        );
        ForcePushHook hook = new ForcePushHook(getCommitService(deletedCommits), getI18nService());
        Collection<RefChange> refChanges = Lists.newArrayList(
                (RefChange) new SimpleRefChange.Builder().ref(minimalBranch("foo")).fromHash("hash1").toHash("hash2").type(RefChangeType.UPDATE).build()
        );
        assertFalse(hook.onReceive(getContext(), refChanges, getHookResponse()));
    }

    @Test
    public void testShouldPassOnDeleteWithChanges() throws Exception {
        Iterable<Commit> deletedCommits = Lists.newArrayList(
                mock(Commit.class)
        );
        ForcePushHook hook = new ForcePushHook(getCommitService(deletedCommits), getI18nService());
        Collection<RefChange> refChanges = Lists.newArrayList(
                (RefChange) new SimpleRefChange.Builder().ref(minimalBranch("foo")).fromHash("hash1").toHash("hash2").type(RefChangeType.DELETE).build()
        );
        assertTrue(hook.onReceive(getContext(), refChanges, getHookResponse()));
    }

    @Test
    public void testShouldPassOnAddWithChanges() throws Exception {
        Iterable<Commit> deletedCommits = Lists.newArrayList(
                mock(Commit.class)
        );
        ForcePushHook hook = new ForcePushHook(getCommitService(deletedCommits), getI18nService());
        Collection<RefChange> refChanges = Lists.newArrayList(
                (RefChange) new SimpleRefChange.Builder().ref(minimalBranch("foo")).fromHash("hash1").toHash("hash2").type(RefChangeType.ADD).build()
        );
        assertTrue(hook.onReceive(getContext(), refChanges, getHookResponse()));
    }

    @Test
    public void testNameShouldPassOnRefChangeWithNoChanges() throws Exception {
        ForcePushHook hook = new ForcePushHook(getCommitService(), getI18nService());
        Collection<RefChange> refChanges = Lists.newArrayList(
                (RefChange) new SimpleRefChange.Builder().ref(minimalBranch("foo")).fromHash("hash1").toHash("hash2").type(RefChangeType.UPDATE).build()
        );
        assertTrue(hook.onReceive(getContext(), refChanges, getHookResponse()));
    }

    @Test
    public void testNameShouldFailOnTagWithNoChanges() throws Exception {
        ForcePushHook hook = new ForcePushHook(getCommitService(), getI18nService());
        Collection<RefChange> refChanges = Lists.newArrayList(
                (RefChange) new SimpleRefChange.Builder().ref(minimalTag("foo")).fromHash("hash1").toHash("hash2").type(RefChangeType.UPDATE).build()
        );
        assertFalse(hook.onReceive(getContext(), refChanges, getHookResponse()));
    }

    @Test
    public void testShouldFailOnUpdateTagWithChanges() throws Exception {
        Iterable<Commit> deletedCommits = Lists.newArrayList(
                mock(Commit.class)
        );
        ForcePushHook hook = new ForcePushHook(getCommitService(deletedCommits), getI18nService());
        Collection<RefChange> refChanges = Lists.newArrayList(
            (RefChange) new SimpleRefChange.Builder().ref(minimalTag("foo")).fromHash("hash1").toHash("hash2").type(RefChangeType.UPDATE).build()
        );
        assertFalse(hook.onReceive(getContext(), refChanges, getHookResponse()));
    }

    @Test
    public void testShouldSetRejectionMessage() throws Exception {
        Iterable<Commit> deletedCommits = Lists.newArrayList(
                mock(Commit.class)
        );
        ForcePushHook hook = new ForcePushHook(getCommitService(deletedCommits), getI18nService());
        Collection<RefChange> refChanges = Lists.newArrayList(
                (RefChange) new SimpleRefChange.Builder().ref(minimalBranch("foo")).fromHash("hash1").toHash("hash2").type(RefChangeType.UPDATE).build()
        );
        HookResponse hookResponse = mock(HookResponse.class);
        StringWriter writer = new StringWriter();
        when(hookResponse.out()).thenReturn(new LineFeedPrintWriter(writer));
        hook.onReceive(getContext(), refChanges, hookResponse);
        assertTrue(writer.toString().contains("--\nbitbucket.plugins.hooks.forcepush.rejected\n--"));
    }

    private HookResponse getHookResponse() {
        HookResponse response = mock(HookResponse.class);
        when(response.out()).thenReturn(mock(PrintWriter.class));
        return response;
    }

    private RepositoryHookContext getContext() {
        RepositoryHookContext context = mock(RepositoryHookContext.class);
        when(context.getRepository()).thenReturn(mock(Repository.class));
        return context;
    }

    private CommitService getCommitService() {
        return getCommitService(Iterables.<Commit>emptyIterable());
    }

    private CommitService getCommitService(Iterable<Commit> deletedCommits) {
        CommitService commitService = mock(CommitService.class);
        Page<Commit> page = new PageImpl<>(null, deletedCommits, true);
        when(commitService.getCommitsBetween(any(CommitsBetweenRequest.class), any(PageRequest.class)))
                .thenReturn(page);
        return commitService;
    }

    private I18nService getI18nService() {
        return new SimpleI18nService(SimpleI18nService.Mode.RETURN_KEYS);
    }

}