package com.atlassian.stash.internal.pull;

import com.atlassian.bitbucket.pull.PullRequestParticipant;
import com.atlassian.bitbucket.pull.PullRequestRole;
import com.atlassian.stash.internal.repository.InternalRepository;
import org.junit.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class InternalPullRequestTest {

    @Test
    public void testBuilderCopiesAllFields() throws Throwable {
        Date createdDate = new Date();
        String description = "Description";
        String title = "Title";
        PullRequestParticipant author = mock(InternalPullRequestParticipant.class);
        when(author.getRole()).thenReturn(PullRequestRole.AUTHOR);
        InternalRepository repository = mockRepository();
        InternalPullRequestRef fromRef = mockPullRequestRef(repository);
        InternalPullRequestRef toRef = mockPullRequestRef(repository);

        InternalPullRequest pullRequest = new InternalPullRequest.Builder()
                .participant(author)
                .createdDate(createdDate)
                .description(description)
                .fromRef(fromRef)
                .toRef(toRef)
                .title(title)
                .locked(true)
                .build();

        assertThat(pullRequest.getCreatedDate(), is(createdDate));
        assertThat(pullRequest.getDescription(), is(description));
        assertThat(pullRequest.getTitle(), is(title));
        assertThat(pullRequest.getFromRef(), is(fromRef));
        assertThat(pullRequest.getToRef(), is(toRef));
        assertThat(pullRequest.isLocked(), is(true));
        assertThat(pullRequest.getAuthor(), is(author));

        InternalPullRequest modifiedPullRequest = new InternalPullRequest.Builder(pullRequest)
                .title("newTitle")
                .build();

        try {
            assertFieldChanges(pullRequest, modifiedPullRequest, "getTitle");
        } catch (InvocationTargetException e) {
            // Ensure we throw the actual exception, not the wrapped one. Makes for better reporting.
            throw e.getTargetException();
        }
    }

    /**
     * Test that all the getters (methods beginning with 'get' and 'is') from {@code old} and {@code updated}
     * return the same value, bar the one with {@code methodName}
     * @param old the unmodified pull request
     * @param updated the updated pull request
     * @param methodName the method call expected to return a non-equal value
     */
    private void assertFieldChanges(InternalPullRequest old, InternalPullRequest updated, String methodName)
            throws Exception {
        for (Method m : old.getClass().getMethods()) {
            if ((m.getName().startsWith("get") || m.getName().startsWith("is")) && m.getParameterCount() == 0) {
                String message = m.getName() + " has%schanged";
                if (m.getName().equals(methodName)) {
                    assertThat(String.format(message, " not "), m.invoke(old), not(m.invoke(updated)));
                } else {
                    assertThat(String.format(message, " "), m.invoke(old), is(m.invoke(updated)));
                }
            }
        }
    }

    private static InternalPullRequestRef mockPullRequestRef(InternalRepository repository) {
        InternalPullRequestRef ref = mock(InternalPullRequestRef.class);
        when(ref.getRepository()).thenReturn(repository);
        return ref;
    }

    private static InternalRepository mockRepository() {
        InternalRepository repository = mock(InternalRepository.class);
        when(repository.getId()).thenReturn(1);
        return repository;
    }

}
